summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authortpearson <tpearson@283d02a7-25f6-0310-bc7c-ecb5cbfe19da>2010-03-01 18:37:05 +0000
committertpearson <tpearson@283d02a7-25f6-0310-bc7c-ecb5cbfe19da>2010-03-01 18:37:05 +0000
commit145364a8af6a1fec06556221e66d4b724a62fc9a (patch)
tree53bd71a544008c518034f208d64c932dc2883f50 /src
downloadrosegarden-145364a8af6a1fec06556221e66d4b724a62fc9a.tar.gz
rosegarden-145364a8af6a1fec06556221e66d4b724a62fc9a.zip
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
Diffstat (limited to 'src')
-rw-r--r--src/BaseFileList.txt102
-rw-r--r--src/CMakeLists.txt208
-rw-r--r--src/GUIFileList.txt975
-rw-r--r--src/MiscFileList.txt6
-rw-r--r--src/SequencerFileList.txt17
-rw-r--r--src/SoundFileList.txt98
-rw-r--r--src/TestFileList.txt6
-rw-r--r--src/base/AnalysisTypes.cpp1118
-rw-r--r--src/base/AnalysisTypes.h227
-rw-r--r--src/base/AudioDevice.cpp107
-rw-r--r--src/base/AudioDevice.h70
-rw-r--r--src/base/AudioLevel.cpp272
-rw-r--r--src/base/AudioLevel.h67
-rw-r--r--src/base/AudioPluginInstance.cpp256
-rw-r--r--src/base/AudioPluginInstance.h172
-rw-r--r--src/base/BaseProperties.cpp133
-rw-r--r--src/base/BaseProperties.h82
-rw-r--r--src/base/BasicQuantizer.cpp253
-rw-r--r--src/base/BasicQuantizer.h95
-rw-r--r--src/base/Clipboard.cpp387
-rw-r--r--src/base/Clipboard.h203
-rw-r--r--src/base/Colour.cpp175
-rw-r--r--src/base/Colour.h125
-rw-r--r--src/base/ColourMap.cpp266
-rw-r--r--src/base/ColourMap.h138
-rw-r--r--src/base/Composition.cpp2225
-rw-r--r--src/base/Composition.h1134
-rw-r--r--src/base/CompositionTimeSliceAdapter.cpp283
-rw-r--r--src/base/CompositionTimeSliceAdapter.h149
-rw-r--r--src/base/Configuration.cpp232
-rw-r--r--src/base/Configuration.h211
-rw-r--r--src/base/ControlParameter.cpp144
-rw-r--r--src/base/ControlParameter.h124
-rw-r--r--src/base/Controllable.h48
-rw-r--r--src/base/Device.cpp31
-rw-r--r--src/base/Device.h102
-rw-r--r--src/base/Equation.cpp69
-rw-r--r--src/base/Equation.h51
-rw-r--r--src/base/Event.cpp445
-rw-r--r--src/base/Event.h584
-rw-r--r--src/base/Exception.cpp46
-rw-r--r--src/base/Exception.h47
-rw-r--r--src/base/FastVector.h596
-rw-r--r--src/base/Instrument.cpp645
-rw-r--r--src/base/Instrument.h349
-rw-r--r--src/base/LayoutEngine.cpp63
-rw-r--r--src/base/LayoutEngine.h161
-rw-r--r--src/base/LegatoQuantizer.cpp141
-rw-r--r--src/base/LegatoQuantizer.h64
-rw-r--r--src/base/Marker.cpp55
-rw-r--r--src/base/Marker.h78
-rw-r--r--src/base/MidiDevice.cpp839
-rw-r--r--src/base/MidiDevice.h213
-rw-r--r--src/base/MidiProgram.cpp224
-rw-r--r--src/base/MidiProgram.h180
-rw-r--r--src/base/MidiTypes.cpp320
-rw-r--r--src/base/MidiTypes.h224
-rw-r--r--src/base/NotationQuantizer.cpp1205
-rw-r--r--src/base/NotationQuantizer.h93
-rw-r--r--src/base/NotationRules.h133
-rw-r--r--src/base/NotationTypes.cpp2436
-rw-r--r--src/base/NotationTypes.h1342
-rw-r--r--src/base/Profiler.cpp187
-rw-r--r--src/base/Profiler.h84
-rw-r--r--src/base/Property.cpp169
-rw-r--r--src/base/Property.h225
-rw-r--r--src/base/PropertyMap.cpp101
-rw-r--r--src/base/PropertyMap.h50
-rw-r--r--src/base/PropertyName.cpp86
-rw-r--r--src/base/PropertyName.h158
-rw-r--r--src/base/Quantizer.cpp496
-rw-r--r--src/base/Quantizer.h249
-rw-r--r--src/base/RealTime.cpp236
-rw-r--r--src/base/RealTime.h124
-rw-r--r--src/base/RefreshStatus.h76
-rw-r--r--src/base/RulerScale.cpp243
-rw-r--r--src/base/RulerScale.h166
-rw-r--r--src/base/ScriptAPI.cpp85
-rw-r--r--src/base/ScriptAPI.h128
-rw-r--r--src/base/Segment.cpp1294
-rw-r--r--src/base/Segment.h783
-rw-r--r--src/base/SegmentMatrixHelper.cpp56
-rw-r--r--src/base/SegmentMatrixHelper.h53
-rw-r--r--src/base/SegmentNotationHelper.cpp2129
-rw-r--r--src/base/SegmentNotationHelper.h591
-rw-r--r--src/base/SegmentPerformanceHelper.cpp472
-rw-r--r--src/base/SegmentPerformanceHelper.h126
-rw-r--r--src/base/Selection.cpp318
-rw-r--r--src/base/Selection.h263
-rw-r--r--src/base/Sets.cpp108
-rw-r--r--src/base/Sets.h698
-rw-r--r--src/base/SnapGrid.cpp192
-rw-r--r--src/base/SnapGrid.h183
-rw-r--r--src/base/SoftSynthDevice.cpp174
-rw-r--r--src/base/SoftSynthDevice.h70
-rw-r--r--src/base/Staff.cpp213
-rw-r--r--src/base/Staff.h149
-rw-r--r--src/base/StaffExportTypes.h75
-rw-r--r--src/base/Studio.cpp674
-rw-r--r--src/base/Studio.h208
-rw-r--r--src/base/Track.cpp201
-rw-r--r--src/base/Track.h162
-rw-r--r--src/base/TriggerSegment.cpp130
-rw-r--r--src/base/TriggerSegment.h100
-rw-r--r--src/base/ViewElement.cpp172
-rw-r--r--src/base/ViewElement.h164
-rw-r--r--src/base/XmlExportable.cpp197
-rw-r--r--src/base/XmlExportable.h55
-rw-r--r--src/base/test/Makefile57
-rw-r--r--src/base/test/accidentals.cpp60
-rw-r--r--src/base/test/colour.cpp222
-rw-r--r--src/base/test/colour.output76
-rw-r--r--src/base/test/pitch.cpp474
-rw-r--r--src/base/test/seq/Makefile6
-rw-r--r--src/base/test/seq/complainer.c74
-rw-r--r--src/base/test/seq/generator.c96
-rw-r--r--src/base/test/seq/queue-timer-jack.c166
-rw-r--r--src/base/test/seq/queue-timer.c123
-rw-r--r--src/base/test/test.cpp535
-rw-r--r--src/base/test/thread.cpp126
-rw-r--r--src/base/test/transpose.cpp83
-rw-r--r--src/base/test/utf8.cpp96
-rw-r--r--src/commands/edit/AddDotCommand.cpp98
-rw-r--r--src/commands/edit/AddDotCommand.h68
-rw-r--r--src/commands/edit/AddMarkerCommand.cpp67
-rw-r--r--src/commands/edit/AddMarkerCommand.h71
-rw-r--r--src/commands/edit/ChangeVelocityCommand.cpp68
-rw-r--r--src/commands/edit/ChangeVelocityCommand.h68
-rw-r--r--src/commands/edit/ClearTriggersCommand.cpp53
-rw-r--r--src/commands/edit/ClearTriggersCommand.h66
-rw-r--r--src/commands/edit/CollapseNotesCommand.cpp79
-rw-r--r--src/commands/edit/CollapseNotesCommand.h65
-rw-r--r--src/commands/edit/CopyCommand.cpp120
-rw-r--r--src/commands/edit/CopyCommand.h82
-rw-r--r--src/commands/edit/CutAndCloseCommand.cpp163
-rw-r--r--src/commands/edit/CutAndCloseCommand.h82
-rw-r--r--src/commands/edit/CutCommand.cpp59
-rw-r--r--src/commands/edit/CutCommand.h62
-rw-r--r--src/commands/edit/EraseCommand.cpp86
-rw-r--r--src/commands/edit/EraseCommand.h66
-rw-r--r--src/commands/edit/EventEditCommand.cpp64
-rw-r--r--src/commands/edit/EventEditCommand.h69
-rw-r--r--src/commands/edit/EventInsertionCommand.cpp58
-rw-r--r--src/commands/edit/EventInsertionCommand.h62
-rw-r--r--src/commands/edit/EventQuantizeCommand.cpp273
-rw-r--r--src/commands/edit/EventQuantizeCommand.h98
-rw-r--r--src/commands/edit/EventUnquantizeCommand.cpp106
-rw-r--r--src/commands/edit/EventUnquantizeCommand.h73
-rw-r--r--src/commands/edit/InsertTriggerNoteCommand.cpp132
-rw-r--r--src/commands/edit/InsertTriggerNoteCommand.h78
-rw-r--r--src/commands/edit/InvertCommand.cpp85
-rw-r--r--src/commands/edit/InvertCommand.h67
-rw-r--r--src/commands/edit/ModifyMarkerCommand.cpp95
-rw-r--r--src/commands/edit/ModifyMarkerCommand.h78
-rw-r--r--src/commands/edit/MoveAcrossSegmentsCommand.cpp76
-rw-r--r--src/commands/edit/MoveAcrossSegmentsCommand.h63
-rw-r--r--src/commands/edit/MoveCommand.cpp159
-rw-r--r--src/commands/edit/MoveCommand.h69
-rw-r--r--src/commands/edit/PasteEventsCommand.cpp321
-rw-r--r--src/commands/edit/PasteEventsCommand.h112
-rw-r--r--src/commands/edit/PasteSegmentsCommand.cpp153
-rw-r--r--src/commands/edit/PasteSegmentsCommand.h79
-rw-r--r--src/commands/edit/RemoveMarkerCommand.cpp83
-rw-r--r--src/commands/edit/RemoveMarkerCommand.h75
-rw-r--r--src/commands/edit/RescaleCommand.cpp138
-rw-r--r--src/commands/edit/RescaleCommand.h71
-rw-r--r--src/commands/edit/RetrogradeCommand.cpp121
-rw-r--r--src/commands/edit/RetrogradeCommand.h67
-rw-r--r--src/commands/edit/RetrogradeInvertCommand.cpp163
-rw-r--r--src/commands/edit/RetrogradeInvertCommand.h67
-rw-r--r--src/commands/edit/SelectionPropertyCommand.cpp128
-rw-r--r--src/commands/edit/SelectionPropertyCommand.h82
-rw-r--r--src/commands/edit/SetLyricsCommand.cpp192
-rw-r--r--src/commands/edit/SetLyricsCommand.h66
-rw-r--r--src/commands/edit/SetNoteTypeCommand.cpp87
-rw-r--r--src/commands/edit/SetNoteTypeCommand.h72
-rw-r--r--src/commands/edit/SetTriggerCommand.cpp74
-rw-r--r--src/commands/edit/SetTriggerCommand.h83
-rw-r--r--src/commands/edit/TransposeCommand.cpp83
-rw-r--r--src/commands/edit/TransposeCommand.h83
-rw-r--r--src/commands/matrix/MatrixEraseCommand.cpp70
-rw-r--r--src/commands/matrix/MatrixEraseCommand.h62
-rw-r--r--src/commands/matrix/MatrixInsertionCommand.cpp74
-rw-r--r--src/commands/matrix/MatrixInsertionCommand.h64
-rw-r--r--src/commands/matrix/MatrixModifyCommand.cpp81
-rw-r--r--src/commands/matrix/MatrixModifyCommand.h63
-rw-r--r--src/commands/matrix/MatrixPercussionInsertionCommand.cpp192
-rw-r--r--src/commands/matrix/MatrixPercussionInsertionCommand.h73
-rw-r--r--src/commands/notation/AddFingeringMarkCommand.cpp119
-rw-r--r--src/commands/notation/AddFingeringMarkCommand.h64
-rw-r--r--src/commands/notation/AddIndicationCommand.cpp171
-rw-r--r--src/commands/notation/AddIndicationCommand.h76
-rw-r--r--src/commands/notation/AddMarkCommand.cpp112
-rw-r--r--src/commands/notation/AddMarkCommand.h63
-rw-r--r--src/commands/notation/AddSlashesCommand.cpp53
-rw-r--r--src/commands/notation/AddSlashesCommand.h60
-rw-r--r--src/commands/notation/AddTextMarkCommand.cpp58
-rw-r--r--src/commands/notation/AddTextMarkCommand.h65
-rw-r--r--src/commands/notation/AutoBeamCommand.cpp48
-rw-r--r--src/commands/notation/AutoBeamCommand.h62
-rw-r--r--src/commands/notation/BeamCommand.cpp58
-rw-r--r--src/commands/notation/BeamCommand.h60
-rw-r--r--src/commands/notation/BreakCommand.cpp54
-rw-r--r--src/commands/notation/BreakCommand.h60
-rw-r--r--src/commands/notation/ChangeSlurPositionCommand.cpp58
-rw-r--r--src/commands/notation/ChangeSlurPositionCommand.h66
-rw-r--r--src/commands/notation/ChangeStemsCommand.cpp53
-rw-r--r--src/commands/notation/ChangeStemsCommand.h66
-rw-r--r--src/commands/notation/ChangeStyleCommand.cpp66
-rw-r--r--src/commands/notation/ChangeStyleCommand.h70
-rw-r--r--src/commands/notation/ChangeTiePositionCommand.cpp54
-rw-r--r--src/commands/notation/ChangeTiePositionCommand.h62
-rw-r--r--src/commands/notation/ClefInsertionCommand.cpp137
-rw-r--r--src/commands/notation/ClefInsertionCommand.h72
-rw-r--r--src/commands/notation/CollapseRestsCommand.cpp54
-rw-r--r--src/commands/notation/CollapseRestsCommand.h63
-rw-r--r--src/commands/notation/DeCounterpointCommand.cpp57
-rw-r--r--src/commands/notation/DeCounterpointCommand.h68
-rw-r--r--src/commands/notation/EraseEventCommand.cpp105
-rw-r--r--src/commands/notation/EraseEventCommand.h71
-rw-r--r--src/commands/notation/FixNotationQuantizeCommand.cpp87
-rw-r--r--src/commands/notation/FixNotationQuantizeCommand.h61
-rw-r--r--src/commands/notation/GraceCommand.cpp115
-rw-r--r--src/commands/notation/GraceCommand.h60
-rw-r--r--src/commands/notation/GuitarChordInsertionCommand.cpp59
-rw-r--r--src/commands/notation/GuitarChordInsertionCommand.h61
-rw-r--r--src/commands/notation/IncrementDisplacementsCommand.cpp57
-rw-r--r--src/commands/notation/IncrementDisplacementsCommand.h66
-rw-r--r--src/commands/notation/InterpretCommand.cpp602
-rw-r--r--src/commands/notation/InterpretCommand.h100
-rw-r--r--src/commands/notation/KeyInsertionCommand.cpp264
-rw-r--r--src/commands/notation/KeyInsertionCommand.h91
-rw-r--r--src/commands/notation/MakeAccidentalsCautionaryCommand.cpp68
-rw-r--r--src/commands/notation/MakeAccidentalsCautionaryCommand.h63
-rw-r--r--src/commands/notation/MakeChordCommand.cpp75
-rw-r--r--src/commands/notation/MakeChordCommand.h66
-rw-r--r--src/commands/notation/MakeNotesViableCommand.cpp57
-rw-r--r--src/commands/notation/MakeNotesViableCommand.h67
-rw-r--r--src/commands/notation/MakeRegionViableCommand.cpp48
-rw-r--r--src/commands/notation/MakeRegionViableCommand.h62
-rw-r--r--src/commands/notation/MultiKeyInsertionCommand.cpp80
-rw-r--r--src/commands/notation/MultiKeyInsertionCommand.h73
-rw-r--r--src/commands/notation/NormalizeRestsCommand.cpp52
-rw-r--r--src/commands/notation/NormalizeRestsCommand.h64
-rw-r--r--src/commands/notation/NoteInsertionCommand.cpp296
-rw-r--r--src/commands/notation/NoteInsertionCommand.h98
-rw-r--r--src/commands/notation/RemoveFingeringMarksCommand.cpp54
-rw-r--r--src/commands/notation/RemoveFingeringMarksCommand.h61
-rw-r--r--src/commands/notation/RemoveMarksCommand.cpp58
-rw-r--r--src/commands/notation/RemoveMarksCommand.h61
-rw-r--r--src/commands/notation/RemoveNotationQuantizeCommand.cpp69
-rw-r--r--src/commands/notation/RemoveNotationQuantizeCommand.h61
-rw-r--r--src/commands/notation/ResetDisplacementsCommand.cpp52
-rw-r--r--src/commands/notation/ResetDisplacementsCommand.h61
-rw-r--r--src/commands/notation/RespellCommand.cpp141
-rw-r--r--src/commands/notation/RespellCommand.h72
-rw-r--r--src/commands/notation/RestInsertionCommand.cpp65
-rw-r--r--src/commands/notation/RestInsertionCommand.h58
-rw-r--r--src/commands/notation/RestoreSlursCommand.cpp58
-rw-r--r--src/commands/notation/RestoreSlursCommand.h62
-rw-r--r--src/commands/notation/RestoreStemsCommand.cpp52
-rw-r--r--src/commands/notation/RestoreStemsCommand.h62
-rw-r--r--src/commands/notation/RestoreTiesCommand.cpp51
-rw-r--r--src/commands/notation/RestoreTiesCommand.h62
-rw-r--r--src/commands/notation/SetVisibilityCommand.cpp57
-rw-r--r--src/commands/notation/SetVisibilityCommand.h63
-rw-r--r--src/commands/notation/SustainInsertionCommand.cpp66
-rw-r--r--src/commands/notation/SustainInsertionCommand.h76
-rw-r--r--src/commands/notation/TextChangeCommand.cpp62
-rw-r--r--src/commands/notation/TextChangeCommand.h63
-rw-r--r--src/commands/notation/TextInsertionCommand.cpp63
-rw-r--r--src/commands/notation/TextInsertionCommand.h63
-rw-r--r--src/commands/notation/TieNotesCommand.cpp72
-rw-r--r--src/commands/notation/TieNotesCommand.h62
-rw-r--r--src/commands/notation/TupletCommand.cpp91
-rw-r--r--src/commands/notation/TupletCommand.h71
-rw-r--r--src/commands/notation/UnGraceCommand.cpp42
-rw-r--r--src/commands/notation/UnGraceCommand.h58
-rw-r--r--src/commands/notation/UnTupletCommand.cpp54
-rw-r--r--src/commands/notation/UnTupletCommand.h62
-rw-r--r--src/commands/notation/UntieNotesCommand.cpp52
-rw-r--r--src/commands/notation/UntieNotesCommand.h62
-rw-r--r--src/commands/segment/AddTempoChangeCommand.cpp66
-rw-r--r--src/commands/segment/AddTempoChangeCommand.h76
-rw-r--r--src/commands/segment/AddTimeSignatureAndNormalizeCommand.cpp78
-rw-r--r--src/commands/segment/AddTimeSignatureAndNormalizeCommand.h53
-rw-r--r--src/commands/segment/AddTimeSignatureCommand.cpp78
-rw-r--r--src/commands/segment/AddTimeSignatureCommand.h71
-rw-r--r--src/commands/segment/AddTracksCommand.cpp137
-rw-r--r--src/commands/segment/AddTracksCommand.h77
-rw-r--r--src/commands/segment/AddTriggerSegmentCommand.cpp90
-rw-r--r--src/commands/segment/AddTriggerSegmentCommand.h72
-rw-r--r--src/commands/segment/AudioSegmentAutoSplitCommand.cpp191
-rw-r--r--src/commands/segment/AudioSegmentAutoSplitCommand.h71
-rw-r--r--src/commands/segment/AudioSegmentDistributeCommand.cpp156
-rw-r--r--src/commands/segment/AudioSegmentDistributeCommand.h86
-rw-r--r--src/commands/segment/AudioSegmentInsertCommand.cpp136
-rw-r--r--src/commands/segment/AudioSegmentInsertCommand.h77
-rw-r--r--src/commands/segment/AudioSegmentRescaleCommand.cpp210
-rw-r--r--src/commands/segment/AudioSegmentRescaleCommand.h81
-rw-r--r--src/commands/segment/AudioSegmentResizeFromStartCommand.cpp87
-rw-r--r--src/commands/segment/AudioSegmentResizeFromStartCommand.h66
-rw-r--r--src/commands/segment/AudioSegmentSplitCommand.cpp155
-rw-r--r--src/commands/segment/AudioSegmentSplitCommand.h65
-rw-r--r--src/commands/segment/ChangeCompositionLengthCommand.cpp64
-rw-r--r--src/commands/segment/ChangeCompositionLengthCommand.h70
-rw-r--r--src/commands/segment/CreateTempoMapFromSegmentCommand.cpp166
-rw-r--r--src/commands/segment/CreateTempoMapFromSegmentCommand.h69
-rw-r--r--src/commands/segment/CutRangeCommand.cpp47
-rw-r--r--src/commands/segment/CutRangeCommand.h53
-rw-r--r--src/commands/segment/DeleteRangeCommand.cpp127
-rw-r--r--src/commands/segment/DeleteRangeCommand.h84
-rw-r--r--src/commands/segment/DeleteTracksCommand.cpp161
-rw-r--r--src/commands/segment/DeleteTracksCommand.h68
-rw-r--r--src/commands/segment/DeleteTriggerSegmentCommand.cpp78
-rw-r--r--src/commands/segment/DeleteTriggerSegmentCommand.h66
-rw-r--r--src/commands/segment/EraseSegmentsStartingInRangeCommand.cpp99
-rw-r--r--src/commands/segment/EraseSegmentsStartingInRangeCommand.h67
-rw-r--r--src/commands/segment/InsertRangeCommand.cpp63
-rw-r--r--src/commands/segment/InsertRangeCommand.h47
-rw-r--r--src/commands/segment/ModifyDefaultTempoCommand.cpp48
-rw-r--r--src/commands/segment/ModifyDefaultTempoCommand.h66
-rw-r--r--src/commands/segment/MoveTracksCommand.cpp76
-rw-r--r--src/commands/segment/MoveTracksCommand.h66
-rw-r--r--src/commands/segment/OpenOrCloseRangeCommand.cpp181
-rw-r--r--src/commands/segment/OpenOrCloseRangeCommand.h84
-rw-r--r--src/commands/segment/PasteConductorDataCommand.cpp128
-rw-r--r--src/commands/segment/PasteConductorDataCommand.h67
-rw-r--r--src/commands/segment/PasteRangeCommand.cpp97
-rw-r--r--src/commands/segment/PasteRangeCommand.h52
-rw-r--r--src/commands/segment/PasteToTriggerSegmentCommand.cpp129
-rw-r--r--src/commands/segment/PasteToTriggerSegmentCommand.h73
-rw-r--r--src/commands/segment/RemoveTempoChangeCommand.cpp59
-rw-r--r--src/commands/segment/RemoveTempoChangeCommand.h75
-rw-r--r--src/commands/segment/RemoveTimeSignatureCommand.cpp60
-rw-r--r--src/commands/segment/RemoveTimeSignatureCommand.h74
-rw-r--r--src/commands/segment/RenameTrackCommand.cpp75
-rw-r--r--src/commands/segment/RenameTrackCommand.h67
-rw-r--r--src/commands/segment/SegmentAutoSplitCommand.cpp205
-rw-r--r--src/commands/segment/SegmentAutoSplitCommand.h66
-rw-r--r--src/commands/segment/SegmentChangePlayableRangeCommand.cpp77
-rw-r--r--src/commands/segment/SegmentChangePlayableRangeCommand.h67
-rw-r--r--src/commands/segment/SegmentChangeQuantizationCommand.cpp115
-rw-r--r--src/commands/segment/SegmentChangeQuantizationCommand.h73
-rw-r--r--src/commands/segment/SegmentChangeTransposeCommand.cpp72
-rw-r--r--src/commands/segment/SegmentChangeTransposeCommand.h65
-rw-r--r--src/commands/segment/SegmentColourCommand.cpp65
-rw-r--r--src/commands/segment/SegmentColourCommand.h66
-rw-r--r--src/commands/segment/SegmentColourMapCommand.cpp64
-rw-r--r--src/commands/segment/SegmentColourMapCommand.h71
-rw-r--r--src/commands/segment/SegmentCommand.cpp42
-rw-r--r--src/commands/segment/SegmentCommand.h59
-rw-r--r--src/commands/segment/SegmentCommandRepeat.cpp59
-rw-r--r--src/commands/segment/SegmentCommandRepeat.h81
-rw-r--r--src/commands/segment/SegmentEraseCommand.cpp108
-rw-r--r--src/commands/segment/SegmentEraseCommand.h70
-rw-r--r--src/commands/segment/SegmentInsertCommand.cpp124
-rw-r--r--src/commands/segment/SegmentInsertCommand.h76
-rw-r--r--src/commands/segment/SegmentJoinCommand.cpp175
-rw-r--r--src/commands/segment/SegmentJoinCommand.h65
-rw-r--r--src/commands/segment/SegmentLabelCommand.cpp73
-rw-r--r--src/commands/segment/SegmentLabelCommand.h67
-rw-r--r--src/commands/segment/SegmentQuickCopyCommand.cpp71
-rw-r--r--src/commands/segment/SegmentQuickCopyCommand.h68
-rw-r--r--src/commands/segment/SegmentReconfigureCommand.cpp114
-rw-r--r--src/commands/segment/SegmentReconfigureCommand.h81
-rw-r--r--src/commands/segment/SegmentRecordCommand.cpp67
-rw-r--r--src/commands/segment/SegmentRecordCommand.h67
-rw-r--r--src/commands/segment/SegmentRepeatToCopyCommand.cpp106
-rw-r--r--src/commands/segment/SegmentRepeatToCopyCommand.h62
-rw-r--r--src/commands/segment/SegmentRescaleCommand.cpp148
-rw-r--r--src/commands/segment/SegmentRescaleCommand.h76
-rw-r--r--src/commands/segment/SegmentResizeFromStartCommand.cpp85
-rw-r--r--src/commands/segment/SegmentResizeFromStartCommand.h69
-rw-r--r--src/commands/segment/SegmentSingleRepeatToCopyCommand.cpp73
-rw-r--r--src/commands/segment/SegmentSingleRepeatToCopyCommand.h65
-rw-r--r--src/commands/segment/SegmentSplitByPitchCommand.cpp280
-rw-r--r--src/commands/segment/SegmentSplitByPitchCommand.h83
-rw-r--r--src/commands/segment/SegmentSplitByRecordingSrcCommand.cpp153
-rw-r--r--src/commands/segment/SegmentSplitByRecordingSrcCommand.h70
-rw-r--r--src/commands/segment/SegmentSplitCommand.cpp185
-rw-r--r--src/commands/segment/SegmentSplitCommand.h65
-rw-r--r--src/commands/segment/SegmentSyncClefCommand.cpp67
-rw-r--r--src/commands/segment/SegmentSyncClefCommand.h55
-rw-r--r--src/commands/segment/SegmentSyncCommand.cpp103
-rw-r--r--src/commands/segment/SegmentSyncCommand.h66
-rw-r--r--src/commands/segment/SegmentTransposeCommand.cpp123
-rw-r--r--src/commands/segment/SegmentTransposeCommand.h64
-rw-r--r--src/commands/segment/SetTriggerSegmentBasePitchCommand.cpp74
-rw-r--r--src/commands/segment/SetTriggerSegmentBasePitchCommand.h63
-rw-r--r--src/commands/segment/SetTriggerSegmentBaseVelocityCommand.cpp74
-rw-r--r--src/commands/segment/SetTriggerSegmentBaseVelocityCommand.h63
-rw-r--r--src/commands/segment/SetTriggerSegmentDefaultRetuneCommand.cpp75
-rw-r--r--src/commands/segment/SetTriggerSegmentDefaultRetuneCommand.h64
-rw-r--r--src/commands/segment/SetTriggerSegmentDefaultTimeAdjustCommand.cpp74
-rw-r--r--src/commands/segment/SetTriggerSegmentDefaultTimeAdjustCommand.h64
-rw-r--r--src/commands/studio/AddControlParameterCommand.cpp75
-rw-r--r--src/commands/studio/AddControlParameterCommand.h75
-rw-r--r--src/commands/studio/CreateOrDeleteDeviceCommand.cpp161
-rw-r--r--src/commands/studio/CreateOrDeleteDeviceCommand.h88
-rw-r--r--src/commands/studio/ModifyControlParameterCommand.cpp75
-rw-r--r--src/commands/studio/ModifyControlParameterCommand.h74
-rw-r--r--src/commands/studio/ModifyDeviceCommand.cpp198
-rw-r--r--src/commands/studio/ModifyDeviceCommand.h109
-rw-r--r--src/commands/studio/ModifyDeviceMappingCommand.cpp147
-rw-r--r--src/commands/studio/ModifyDeviceMappingCommand.h71
-rw-r--r--src/commands/studio/ModifyInstrumentMappingCommand.cpp78
-rw-r--r--src/commands/studio/ModifyInstrumentMappingCommand.h76
-rw-r--r--src/commands/studio/ReconnectDeviceCommand.cpp98
-rw-r--r--src/commands/studio/ReconnectDeviceCommand.h70
-rw-r--r--src/commands/studio/RemoveControlParameterCommand.cpp75
-rw-r--r--src/commands/studio/RemoveControlParameterCommand.h73
-rw-r--r--src/commands/studio/RenameDeviceCommand.cpp52
-rw-r--r--src/commands/studio/RenameDeviceCommand.h71
-rw-r--r--src/document/BasicCommand.cpp171
-rw-r--r--src/document/BasicCommand.h112
-rw-r--r--src/document/BasicSelectionCommand.cpp66
-rw-r--r--src/document/BasicSelectionCommand.h67
-rw-r--r--src/document/ConfigGroups.cpp53
-rw-r--r--src/document/ConfigGroups.h56
-rw-r--r--src/document/MultiViewCommandHistory.cpp386
-rw-r--r--src/document/MultiViewCommandHistory.h152
-rw-r--r--src/document/RoseXmlHandler.cpp2368
-rw-r--r--src/document/RoseXmlHandler.h192
-rw-r--r--src/document/RosegardenGUIDoc.cpp3117
-rw-r--r--src/document/RosegardenGUIDoc.h733
-rw-r--r--src/document/XmlStorableEvent.cpp188
-rw-r--r--src/document/XmlStorableEvent.h75
-rw-r--r--src/document/XmlSubHandler.cpp37
-rw-r--r--src/document/XmlSubHandler.h58
-rw-r--r--src/document/io/CsoundExporter.cpp154
-rw-r--r--src/document/io/CsoundExporter.h63
-rw-r--r--src/document/io/HydrogenLoader.cpp74
-rw-r--r--src/document/io/HydrogenLoader.h83
-rw-r--r--src/document/io/HydrogenXMLHandler.cpp403
-rw-r--r--src/document/io/HydrogenXMLHandler.h132
-rw-r--r--src/document/io/LilyPondExporter.cpp2419
-rw-r--r--src/document/io/LilyPondExporter.h262
-rw-r--r--src/document/io/MupExporter.cpp453
-rw-r--r--src/document/io/MupExporter.h89
-rw-r--r--src/document/io/MusicXmlExporter.cpp555
-rw-r--r--src/document/io/MusicXmlExporter.h87
-rw-r--r--src/document/io/RG21Loader.cpp797
-rw-r--r--src/document/io/RG21Loader.h162
-rw-r--r--src/gui/application/LircClient.cpp100
-rw-r--r--src/gui/application/LircClient.h71
-rw-r--r--src/gui/application/LircCommander.cpp170
-rw-r--r--src/gui/application/LircCommander.h112
-rw-r--r--src/gui/application/RosegardenApplication.cpp145
-rw-r--r--src/gui/application/RosegardenApplication.h97
-rw-r--r--src/gui/application/RosegardenDCOP.h50
-rw-r--r--src/gui/application/RosegardenGUIApp.cpp8073
-rw-r--r--src/gui/application/RosegardenGUIApp.cpp.orig8043
-rw-r--r--src/gui/application/RosegardenGUIApp.h1691
-rw-r--r--src/gui/application/RosegardenGUIView.cpp2041
-rw-r--r--src/gui/application/RosegardenGUIView.h347
-rw-r--r--src/gui/application/RosegardenIface.cpp82
-rw-r--r--src/gui/application/RosegardenIface.h130
-rw-r--r--src/gui/application/SetWaitCursor.cpp95
-rw-r--r--src/gui/application/SetWaitCursor.h58
-rw-r--r--src/gui/application/StartupTester.cpp248
-rw-r--r--src/gui/application/StartupTester.h88
-rw-r--r--src/gui/application/main.cpp741
-rw-r--r--src/gui/configuration/AudioConfigurationPage.cpp323
-rw-r--r--src/gui/configuration/AudioConfigurationPage.h107
-rw-r--r--src/gui/configuration/AudioPropertiesPage.cpp184
-rw-r--r--src/gui/configuration/AudioPropertiesPage.h89
-rw-r--r--src/gui/configuration/ColourConfigurationPage.cpp165
-rw-r--r--src/gui/configuration/ColourConfigurationPage.h87
-rw-r--r--src/gui/configuration/ConfigurationPage.cpp37
-rw-r--r--src/gui/configuration/ConfigurationPage.h104
-rw-r--r--src/gui/configuration/DocumentMetaConfigurationPage.cpp366
-rw-r--r--src/gui/configuration/DocumentMetaConfigurationPage.h76
-rw-r--r--src/gui/configuration/GeneralConfigurationPage.cpp429
-rw-r--r--src/gui/configuration/GeneralConfigurationPage.h116
-rw-r--r--src/gui/configuration/HeadersConfigurationPage.cpp294
-rw-r--r--src/gui/configuration/HeadersConfigurationPage.h80
-rw-r--r--src/gui/configuration/LatencyConfigurationPage.cpp157
-rw-r--r--src/gui/configuration/LatencyConfigurationPage.h87
-rw-r--r--src/gui/configuration/MIDIConfigurationPage.cpp400
-rw-r--r--src/gui/configuration/MIDIConfigurationPage.h104
-rw-r--r--src/gui/configuration/MatrixConfigurationPage.cpp68
-rw-r--r--src/gui/configuration/MatrixConfigurationPage.h69
-rw-r--r--src/gui/configuration/NotationConfigurationPage.cpp741
-rw-r--r--src/gui/configuration/NotationConfigurationPage.h117
-rw-r--r--src/gui/configuration/TabbedConfigurationPage.cpp79
-rw-r--r--src/gui/configuration/TabbedConfigurationPage.h78
-rw-r--r--src/gui/dialogs/AddTracksDialog.cpp110
-rw-r--r--src/gui/dialogs/AddTracksDialog.h57
-rw-r--r--src/gui/dialogs/AudioManagerDialog.cpp1257
-rw-r--r--src/gui/dialogs/AudioManagerDialog.h206
-rw-r--r--src/gui/dialogs/AudioPlayingDialog.cpp55
-rw-r--r--src/gui/dialogs/AudioPlayingDialog.h56
-rw-r--r--src/gui/dialogs/AudioPluginDialog.cpp916
-rw-r--r--src/gui/dialogs/AudioPluginDialog.h167
-rw-r--r--src/gui/dialogs/AudioSplitDialog.cpp339
-rw-r--r--src/gui/dialogs/AudioSplitDialog.h88
-rw-r--r--src/gui/dialogs/BeatsBarsDialog.cpp66
-rw-r--r--src/gui/dialogs/BeatsBarsDialog.h63
-rw-r--r--src/gui/dialogs/ClefDialog.cpp273
-rw-r--r--src/gui/dialogs/ClefDialog.h93
-rw-r--r--src/gui/dialogs/CompositionLengthDialog.cpp84
-rw-r--r--src/gui/dialogs/CompositionLengthDialog.h64
-rw-r--r--src/gui/dialogs/ConfigureDialog.cpp118
-rw-r--r--src/gui/dialogs/ConfigureDialog.h58
-rw-r--r--src/gui/dialogs/ConfigureDialogBase.cpp76
-rw-r--r--src/gui/dialogs/ConfigureDialogBase.h69
-rw-r--r--src/gui/dialogs/CountdownBar.cpp68
-rw-r--r--src/gui/dialogs/CountdownBar.h59
-rw-r--r--src/gui/dialogs/CountdownDialog.cpp159
-rw-r--r--src/gui/dialogs/CountdownDialog.h87
-rw-r--r--src/gui/dialogs/DocumentConfigureDialog.cpp151
-rw-r--r--src/gui/dialogs/DocumentConfigureDialog.h60
-rw-r--r--src/gui/dialogs/EventEditDialog.cpp528
-rw-r--r--src/gui/dialogs/EventEditDialog.h113
-rw-r--r--src/gui/dialogs/EventFilterDialog.cpp476
-rw-r--r--src/gui/dialogs/EventFilterDialog.h170
-rw-r--r--src/gui/dialogs/EventParameterDialog.cpp185
-rw-r--r--src/gui/dialogs/EventParameterDialog.h80
-rw-r--r--src/gui/dialogs/ExportDeviceDialog.cpp66
-rw-r--r--src/gui/dialogs/ExportDeviceDialog.h60
-rw-r--r--src/gui/dialogs/FileLocateDialog.cpp104
-rw-r--r--src/gui/dialogs/FileLocateDialog.h66
-rw-r--r--src/gui/dialogs/FileMergeDialog.cpp84
-rw-r--r--src/gui/dialogs/FileMergeDialog.h63
-rw-r--r--src/gui/dialogs/FloatEdit.cpp72
-rw-r--r--src/gui/dialogs/FloatEdit.h68
-rw-r--r--src/gui/dialogs/IdentifyTextCodecDialog.cpp173
-rw-r--r--src/gui/dialogs/IdentifyTextCodecDialog.h71
-rw-r--r--src/gui/dialogs/ImportDeviceDialog.cpp389
-rw-r--r--src/gui/dialogs/ImportDeviceDialog.h110
-rw-r--r--src/gui/dialogs/InterpretDialog.cpp123
-rw-r--r--src/gui/dialogs/InterpretDialog.h65
-rw-r--r--src/gui/dialogs/IntervalDialog.cpp367
-rw-r--r--src/gui/dialogs/IntervalDialog.h94
-rw-r--r--src/gui/dialogs/KeySignatureDialog.cpp402
-rw-r--r--src/gui/dialogs/KeySignatureDialog.h118
-rw-r--r--src/gui/dialogs/LilyPondOptionsDialog.cpp363
-rw-r--r--src/gui/dialogs/LilyPondOptionsDialog.h86
-rw-r--r--src/gui/dialogs/LyricEditDialog.cpp253
-rw-r--r--src/gui/dialogs/LyricEditDialog.h78
-rw-r--r--src/gui/dialogs/MakeOrnamentDialog.cpp73
-rw-r--r--src/gui/dialogs/MakeOrnamentDialog.h62
-rw-r--r--src/gui/dialogs/ManageMetronomeDialog.cpp508
-rw-r--r--src/gui/dialogs/ManageMetronomeDialog.h94
-rw-r--r--src/gui/dialogs/MarkerModifyDialog.cpp113
-rw-r--r--src/gui/dialogs/MarkerModifyDialog.h84
-rw-r--r--src/gui/dialogs/PasteNotationDialog.cpp101
-rw-r--r--src/gui/dialogs/PasteNotationDialog.h72
-rw-r--r--src/gui/dialogs/PitchDialog.cpp57
-rw-r--r--src/gui/dialogs/PitchDialog.h58
-rw-r--r--src/gui/dialogs/PitchPickerDialog.cpp58
-rw-r--r--src/gui/dialogs/PitchPickerDialog.h57
-rw-r--r--src/gui/dialogs/QuantizeDialog.cpp68
-rw-r--r--src/gui/dialogs/QuantizeDialog.h60
-rw-r--r--src/gui/dialogs/RescaleDialog.cpp131
-rw-r--r--src/gui/dialogs/RescaleDialog.h68
-rw-r--r--src/gui/dialogs/ShowSequencerStatusDialog.cpp79
-rw-r--r--src/gui/dialogs/ShowSequencerStatusDialog.h54
-rw-r--r--src/gui/dialogs/SimpleEventEditDialog.cpp1061
-rw-r--r--src/gui/dialogs/SimpleEventEditDialog.h134
-rw-r--r--src/gui/dialogs/SplitByPitchDialog.cpp111
-rw-r--r--src/gui/dialogs/SplitByPitchDialog.h67
-rw-r--r--src/gui/dialogs/SplitByRecordingSrcDialog.cpp114
-rw-r--r--src/gui/dialogs/SplitByRecordingSrcDialog.h62
-rw-r--r--src/gui/dialogs/TempoDialog.cpp475
-rw-r--r--src/gui/dialogs/TempoDialog.h128
-rw-r--r--src/gui/dialogs/TextEventDialog.cpp593
-rw-r--r--src/gui/dialogs/TextEventDialog.h129
-rw-r--r--src/gui/dialogs/TimeDialog.cpp80
-rw-r--r--src/gui/dialogs/TimeDialog.h67
-rw-r--r--src/gui/dialogs/TimeSignatureDialog.cpp316
-rw-r--r--src/gui/dialogs/TimeSignatureDialog.h99
-rw-r--r--src/gui/dialogs/TransportDialog.cpp1164
-rw-r--r--src/gui/dialogs/TransportDialog.h231
-rw-r--r--src/gui/dialogs/TriggerSegmentDialog.cpp181
-rw-r--r--src/gui/dialogs/TriggerSegmentDialog.h71
-rw-r--r--src/gui/dialogs/TupletDialog.cpp365
-rw-r--r--src/gui/dialogs/TupletDialog.h99
-rw-r--r--src/gui/dialogs/UnusedAudioSelectionDialog.cpp92
-rw-r--r--src/gui/dialogs/UnusedAudioSelectionDialog.h62
-rw-r--r--src/gui/dialogs/UseOrnamentDialog.cpp264
-rw-r--r--src/gui/dialogs/UseOrnamentDialog.h82
-rw-r--r--src/gui/editors/eventlist/EventView.cpp1606
-rw-r--r--src/gui/editors/eventlist/EventView.h205
-rw-r--r--src/gui/editors/eventlist/EventViewItem.cpp68
-rw-r--r--src/gui/editors/eventlist/EventViewItem.h101
-rw-r--r--src/gui/editors/eventlist/TrivialVelocityDialog.cpp48
-rw-r--r--src/gui/editors/eventlist/TrivialVelocityDialog.h48
-rw-r--r--src/gui/editors/guitar/Chord.cpp113
-rw-r--r--src/gui/editors/guitar/Chord.h106
-rw-r--r--src/gui/editors/guitar/ChordMap.cpp223
-rw-r--r--src/gui/editors/guitar/ChordMap.h87
-rw-r--r--src/gui/editors/guitar/ChordXmlHandler.cpp154
-rw-r--r--src/gui/editors/guitar/ChordXmlHandler.h78
-rw-r--r--src/gui/editors/guitar/Fingering.cpp152
-rw-r--r--src/gui/editors/guitar/Fingering.h95
-rw-r--r--src/gui/editors/guitar/FingeringBox.cpp293
-rw-r--r--src/gui/editors/guitar/FingeringBox.h106
-rw-r--r--src/gui/editors/guitar/FingeringListBoxItem.cpp36
-rw-r--r--src/gui/editors/guitar/FingeringListBoxItem.h46
-rw-r--r--src/gui/editors/guitar/GuitarChordEditorDialog.cpp109
-rw-r--r--src/gui/editors/guitar/GuitarChordEditorDialog.h67
-rw-r--r--src/gui/editors/guitar/GuitarChordSelectorDialog.cpp475
-rw-r--r--src/gui/editors/guitar/GuitarChordSelectorDialog.h120
-rw-r--r--src/gui/editors/guitar/NoteSymbols.cpp486
-rw-r--r--src/gui/editors/guitar/NoteSymbols.h192
-rw-r--r--src/gui/editors/matrix/MatrixCanvasView.cpp302
-rw-r--r--src/gui/editors/matrix/MatrixCanvasView.h162
-rw-r--r--src/gui/editors/matrix/MatrixElement.cpp160
-rw-r--r--src/gui/editors/matrix/MatrixElement.h138
-rw-r--r--src/gui/editors/matrix/MatrixEraser.cpp110
-rw-r--r--src/gui/editors/matrix/MatrixEraser.h69
-rw-r--r--src/gui/editors/matrix/MatrixHLayout.cpp220
-rw-r--r--src/gui/editors/matrix/MatrixHLayout.h150
-rw-r--r--src/gui/editors/matrix/MatrixMover.cpp481
-rw-r--r--src/gui/editors/matrix/MatrixMover.h112
-rw-r--r--src/gui/editors/matrix/MatrixPainter.cpp370
-rw-r--r--src/gui/editors/matrix/MatrixPainter.h105
-rw-r--r--src/gui/editors/matrix/MatrixParameterBox.cpp99
-rw-r--r--src/gui/editors/matrix/MatrixParameterBox.h76
-rw-r--r--src/gui/editors/matrix/MatrixResizer.cpp333
-rw-r--r--src/gui/editors/matrix/MatrixResizer.h102
-rw-r--r--src/gui/editors/matrix/MatrixSelector.cpp629
-rw-r--r--src/gui/editors/matrix/MatrixSelector.h177
-rw-r--r--src/gui/editors/matrix/MatrixStaff.cpp232
-rw-r--r--src/gui/editors/matrix/MatrixStaff.h111
-rw-r--r--src/gui/editors/matrix/MatrixTool.cpp79
-rw-r--r--src/gui/editors/matrix/MatrixTool.h74
-rw-r--r--src/gui/editors/matrix/MatrixToolBox.cpp87
-rw-r--r--src/gui/editors/matrix/MatrixToolBox.h60
-rw-r--r--src/gui/editors/matrix/MatrixVLayout.cpp100
-rw-r--r--src/gui/editors/matrix/MatrixVLayout.h91
-rw-r--r--src/gui/editors/matrix/MatrixView.cpp3076
-rw-r--r--src/gui/editors/matrix/MatrixView.h692
-rw-r--r--src/gui/editors/matrix/PianoKeyboard.cpp299
-rw-r--r--src/gui/editors/matrix/PianoKeyboard.h133
-rw-r--r--src/gui/editors/matrix/QCanvasMatrixDiamond.cpp82
-rw-r--r--src/gui/editors/matrix/QCanvasMatrixDiamond.h61
-rw-r--r--src/gui/editors/matrix/QCanvasMatrixRectangle.cpp44
-rw-r--r--src/gui/editors/matrix/QCanvasMatrixRectangle.h60
-rw-r--r--src/gui/editors/notation/ClefInserter.cpp132
-rw-r--r--src/gui/editors/notation/ClefInserter.h83
-rw-r--r--src/gui/editors/notation/FontViewFrame.cpp252
-rw-r--r--src/gui/editors/notation/FontViewFrame.h77
-rw-r--r--src/gui/editors/notation/GuitarChordInserter.cpp185
-rw-r--r--src/gui/editors/notation/GuitarChordInserter.h96
-rw-r--r--src/gui/editors/notation/HeadersGroup.cpp160
-rw-r--r--src/gui/editors/notation/HeadersGroup.h144
-rw-r--r--src/gui/editors/notation/NotationCanvasView.cpp485
-rw-r--r--src/gui/editors/notation/NotationCanvasView.h218
-rw-r--r--src/gui/editors/notation/NotationChord.cpp335
-rw-r--r--src/gui/editors/notation/NotationChord.h90
-rw-r--r--src/gui/editors/notation/NotationElement.cpp198
-rw-r--r--src/gui/editors/notation/NotationElement.h176
-rw-r--r--src/gui/editors/notation/NotationEraser.cpp115
-rw-r--r--src/gui/editors/notation/NotationEraser.h81
-rw-r--r--src/gui/editors/notation/NotationGroup.cpp979
-rw-r--r--src/gui/editors/notation/NotationGroup.h133
-rw-r--r--src/gui/editors/notation/NotationHLayout.cpp2110
-rw-r--r--src/gui/editors/notation/NotationHLayout.h446
-rw-r--r--src/gui/editors/notation/NotationProperties.cpp85
-rw-r--r--src/gui/editors/notation/NotationProperties.h108
-rw-r--r--src/gui/editors/notation/NotationSelectionPaster.cpp89
-rw-r--r--src/gui/editors/notation/NotationSelectionPaster.h72
-rw-r--r--src/gui/editors/notation/NotationSelector.cpp957
-rw-r--r--src/gui/editors/notation/NotationSelector.h197
-rw-r--r--src/gui/editors/notation/NotationStaff.cpp2300
-rw-r--r--src/gui/editors/notation/NotationStaff.h488
-rw-r--r--src/gui/editors/notation/NotationStrings.cpp301
-rw-r--r--src/gui/editors/notation/NotationStrings.h121
-rw-r--r--src/gui/editors/notation/NotationTool.cpp57
-rw-r--r--src/gui/editors/notation/NotationTool.h93
-rw-r--r--src/gui/editors/notation/NotationToolBox.cpp102
-rw-r--r--src/gui/editors/notation/NotationToolBox.h65
-rw-r--r--src/gui/editors/notation/NotationVLayout.cpp731
-rw-r--r--src/gui/editors/notation/NotationVLayout.h122
-rw-r--r--src/gui/editors/notation/NotationView.cpp7552
-rw-r--r--src/gui/editors/notation/NotationView.h1131
-rw-r--r--src/gui/editors/notation/NoteCharacter.cpp133
-rw-r--r--src/gui/editors/notation/NoteCharacter.h93
-rw-r--r--src/gui/editors/notation/NoteCharacterNames.cpp123
-rw-r--r--src/gui/editors/notation/NoteCharacterNames.h120
-rw-r--r--src/gui/editors/notation/NoteFont.cpp650
-rw-r--r--src/gui/editors/notation/NoteFont.h184
-rw-r--r--src/gui/editors/notation/NoteFontFactory.cpp236
-rw-r--r--src/gui/editors/notation/NoteFontFactory.h71
-rw-r--r--src/gui/editors/notation/NoteFontMap.cpp1088
-rw-r--r--src/gui/editors/notation/NoteFontMap.h333
-rw-r--r--src/gui/editors/notation/NoteFontViewer.cpp125
-rw-r--r--src/gui/editors/notation/NoteFontViewer.h68
-rw-r--r--src/gui/editors/notation/NoteInserter.cpp722
-rw-r--r--src/gui/editors/notation/NoteInserter.h166
-rw-r--r--src/gui/editors/notation/NotePixmapFactory.cpp3689
-rw-r--r--src/gui/editors/notation/NotePixmapFactory.h358
-rw-r--r--src/gui/editors/notation/NotePixmapPainter.h148
-rw-r--r--src/gui/editors/notation/NotePixmapParameters.cpp151
-rw-r--r--src/gui/editors/notation/NotePixmapParameters.h161
-rw-r--r--src/gui/editors/notation/NoteStyle.cpp485
-rw-r--r--src/gui/editors/notation/NoteStyle.h142
-rw-r--r--src/gui/editors/notation/NoteStyleFactory.cpp124
-rw-r--r--src/gui/editors/notation/NoteStyleFactory.h61
-rw-r--r--src/gui/editors/notation/NoteStyleFileReader.cpp193
-rw-r--r--src/gui/editors/notation/NoteStyleFileReader.h59
-rw-r--r--src/gui/editors/notation/RestInserter.cpp150
-rw-r--r--src/gui/editors/notation/RestInserter.h76
-rw-r--r--src/gui/editors/notation/SystemFont.cpp165
-rw-r--r--src/gui/editors/notation/SystemFont.h63
-rw-r--r--src/gui/editors/notation/SystemFontQt.cpp78
-rw-r--r--src/gui/editors/notation/SystemFontQt.h49
-rw-r--r--src/gui/editors/notation/SystemFontXft.cpp193
-rw-r--r--src/gui/editors/notation/SystemFontXft.h58
-rw-r--r--src/gui/editors/notation/TextInserter.cpp169
-rw-r--r--src/gui/editors/notation/TextInserter.h78
-rw-r--r--src/gui/editors/notation/TrackHeader.cpp450
-rw-r--r--src/gui/editors/notation/TrackHeader.h219
-rw-r--r--src/gui/editors/parameters/AudioInstrumentParameterPanel.cpp437
-rw-r--r--src/gui/editors/parameters/AudioInstrumentParameterPanel.h107
-rw-r--r--src/gui/editors/parameters/InstrumentParameterBox.cpp265
-rw-r--r--src/gui/editors/parameters/InstrumentParameterBox.h126
-rw-r--r--src/gui/editors/parameters/InstrumentParameterPanel.cpp61
-rw-r--r--src/gui/editors/parameters/InstrumentParameterPanel.h78
-rw-r--r--src/gui/editors/parameters/MIDIInstrumentParameterPanel.cpp1175
-rw-r--r--src/gui/editors/parameters/MIDIInstrumentParameterPanel.h137
-rw-r--r--src/gui/editors/parameters/RosegardenParameterArea.cpp227
-rw-r--r--src/gui/editors/parameters/RosegardenParameterArea.h108
-rw-r--r--src/gui/editors/parameters/RosegardenParameterBox.cpp89
-rw-r--r--src/gui/editors/parameters/RosegardenParameterBox.h92
-rw-r--r--src/gui/editors/parameters/SegmentParameterBox.cpp1214
-rw-r--r--src/gui/editors/parameters/SegmentParameterBox.h174
-rw-r--r--src/gui/editors/parameters/TrackParameterBox.cpp1022
-rw-r--r--src/gui/editors/parameters/TrackParameterBox.h161
-rw-r--r--src/gui/editors/segment/ControlEditorDialog.cpp446
-rw-r--r--src/gui/editors/segment/ControlEditorDialog.h122
-rw-r--r--src/gui/editors/segment/ControlParameterEditDialog.cpp325
-rw-r--r--src/gui/editors/segment/ControlParameterEditDialog.h92
-rw-r--r--src/gui/editors/segment/ControlParameterItem.cpp34
-rw-r--r--src/gui/editors/segment/ControlParameterItem.h65
-rw-r--r--src/gui/editors/segment/MarkerEditor.cpp594
-rw-r--r--src/gui/editors/segment/MarkerEditor.h124
-rw-r--r--src/gui/editors/segment/MarkerEditorViewItem.cpp51
-rw-r--r--src/gui/editors/segment/MarkerEditorViewItem.h70
-rw-r--r--src/gui/editors/segment/PlayList.cpp254
-rw-r--r--src/gui/editors/segment/PlayList.h93
-rw-r--r--src/gui/editors/segment/PlayListDialog.cpp76
-rw-r--r--src/gui/editors/segment/PlayListDialog.h71
-rw-r--r--src/gui/editors/segment/PlayListView.cpp66
-rw-r--r--src/gui/editors/segment/PlayListView.h52
-rw-r--r--src/gui/editors/segment/PlayListViewItem.cpp42
-rw-r--r--src/gui/editors/segment/PlayListViewItem.h47
-rw-r--r--src/gui/editors/segment/TrackButtons.cpp1149
-rw-r--r--src/gui/editors/segment/TrackButtons.h228
-rw-r--r--src/gui/editors/segment/TrackEditor.cpp827
-rw-r--r--src/gui/editors/segment/TrackEditor.h248
-rw-r--r--src/gui/editors/segment/TrackEditorIface.cpp33
-rw-r--r--src/gui/editors/segment/TrackEditorIface.h55
-rw-r--r--src/gui/editors/segment/TrackHeader.cpp64
-rw-r--r--src/gui/editors/segment/TrackHeader.h65
-rw-r--r--src/gui/editors/segment/TrackLabel.cpp203
-rw-r--r--src/gui/editors/segment/TrackLabel.h122
-rw-r--r--src/gui/editors/segment/TrackVUMeter.cpp77
-rw-r--r--src/gui/editors/segment/TrackVUMeter.h65
-rw-r--r--src/gui/editors/segment/TriggerManagerItem.cpp60
-rw-r--r--src/gui/editors/segment/TriggerManagerItem.h72
-rw-r--r--src/gui/editors/segment/TriggerSegmentManager.cpp576
-rw-r--r--src/gui/editors/segment/TriggerSegmentManager.h116
-rw-r--r--src/gui/editors/segment/segmentcanvas/AudioPreviewPainter.cpp316
-rw-r--r--src/gui/editors/segment/segmentcanvas/AudioPreviewPainter.h79
-rw-r--r--src/gui/editors/segment/segmentcanvas/AudioPreviewThread.cpp267
-rw-r--r--src/gui/editors/segment/segmentcanvas/AudioPreviewThread.h99
-rw-r--r--src/gui/editors/segment/segmentcanvas/AudioPreviewUpdater.cpp149
-rw-r--r--src/gui/editors/segment/segmentcanvas/AudioPreviewUpdater.h90
-rw-r--r--src/gui/editors/segment/segmentcanvas/CompositionColourCache.cpp62
-rw-r--r--src/gui/editors/segment/segmentcanvas/CompositionColourCache.h69
-rw-r--r--src/gui/editors/segment/segmentcanvas/CompositionItem.cpp34
-rw-r--r--src/gui/editors/segment/segmentcanvas/CompositionItem.h67
-rw-r--r--src/gui/editors/segment/segmentcanvas/CompositionItemHelper.cpp150
-rw-r--r--src/gui/editors/segment/segmentcanvas/CompositionItemHelper.h61
-rw-r--r--src/gui/editors/segment/segmentcanvas/CompositionItemImpl.cpp67
-rw-r--r--src/gui/editors/segment/segmentcanvas/CompositionItemImpl.h74
-rw-r--r--src/gui/editors/segment/segmentcanvas/CompositionModel.cpp43
-rw-r--r--src/gui/editors/segment/segmentcanvas/CompositionModel.h179
-rw-r--r--src/gui/editors/segment/segmentcanvas/CompositionModelImpl.cpp1328
-rw-r--r--src/gui/editors/segment/segmentcanvas/CompositionModelImpl.h239
-rw-r--r--src/gui/editors/segment/segmentcanvas/CompositionRect.cpp42
-rw-r--r--src/gui/editors/segment/segmentcanvas/CompositionRect.h108
-rw-r--r--src/gui/editors/segment/segmentcanvas/CompositionView.cpp1591
-rw-r--r--src/gui/editors/segment/segmentcanvas/CompositionView.h366
-rw-r--r--src/gui/editors/segment/segmentcanvas/PreviewRect.cpp34
-rw-r--r--src/gui/editors/segment/segmentcanvas/PreviewRect.h62
-rw-r--r--src/gui/editors/segment/segmentcanvas/SegmentEraser.cpp88
-rw-r--r--src/gui/editors/segment/segmentcanvas/SegmentEraser.h67
-rw-r--r--src/gui/editors/segment/segmentcanvas/SegmentItemPreview.cpp37
-rw-r--r--src/gui/editors/segment/segmentcanvas/SegmentItemPreview.h91
-rw-r--r--src/gui/editors/segment/segmentcanvas/SegmentJoiner.cpp73
-rw-r--r--src/gui/editors/segment/segmentcanvas/SegmentJoiner.h70
-rw-r--r--src/gui/editors/segment/segmentcanvas/SegmentMover.cpp348
-rw-r--r--src/gui/editors/segment/segmentcanvas/SegmentMover.h78
-rw-r--r--src/gui/editors/segment/segmentcanvas/SegmentOrderer.cpp48
-rw-r--r--src/gui/editors/segment/segmentcanvas/SegmentOrderer.h59
-rw-r--r--src/gui/editors/segment/segmentcanvas/SegmentPencil.cpp295
-rw-r--r--src/gui/editors/segment/segmentcanvas/SegmentPencil.h83
-rw-r--r--src/gui/editors/segment/segmentcanvas/SegmentResizer.cpp393
-rw-r--r--src/gui/editors/segment/segmentcanvas/SegmentResizer.h87
-rw-r--r--src/gui/editors/segment/segmentcanvas/SegmentSelector.cpp532
-rw-r--r--src/gui/editors/segment/segmentcanvas/SegmentSelector.h109
-rw-r--r--src/gui/editors/segment/segmentcanvas/SegmentSplitter.cpp175
-rw-r--r--src/gui/editors/segment/segmentcanvas/SegmentSplitter.h83
-rw-r--r--src/gui/editors/segment/segmentcanvas/SegmentTool.cpp115
-rw-r--r--src/gui/editors/segment/segmentcanvas/SegmentTool.h105
-rw-r--r--src/gui/editors/segment/segmentcanvas/SegmentToolBox.cpp102
-rw-r--r--src/gui/editors/segment/segmentcanvas/SegmentToolBox.h63
-rw-r--r--src/gui/editors/tempo/TempoListItem.cpp52
-rw-r--r--src/gui/editors/tempo/TempoListItem.h72
-rw-r--r--src/gui/editors/tempo/TempoView.cpp839
-rw-r--r--src/gui/editors/tempo/TempoView.h172
-rw-r--r--src/gui/general/ActiveItem.cpp32
-rw-r--r--src/gui/general/ActiveItem.h55
-rw-r--r--src/gui/general/BarLine.cpp165
-rw-r--r--src/gui/general/BarLine.h64
-rw-r--r--src/gui/general/BaseTool.cpp89
-rw-r--r--src/gui/general/BaseTool.h112
-rw-r--r--src/gui/general/BaseToolBox.cpp58
-rw-r--r--src/gui/general/BaseToolBox.h69
-rw-r--r--src/gui/general/CanvasCursor.cpp52
-rw-r--r--src/gui/general/CanvasCursor.h55
-rw-r--r--src/gui/general/CanvasItemGC.cpp64
-rw-r--r--src/gui/general/CanvasItemGC.h85
-rw-r--r--src/gui/general/CategoryElement.cpp61
-rw-r--r--src/gui/general/CategoryElement.h71
-rw-r--r--src/gui/general/ClefIndex.cpp100
-rw-r--r--src/gui/general/ClefIndex.h59
-rw-r--r--src/gui/general/EditTool.cpp143
-rw-r--r--src/gui/general/EditTool.h166
-rw-r--r--src/gui/general/EditToolBox.cpp56
-rw-r--r--src/gui/general/EditToolBox.h65
-rw-r--r--src/gui/general/EditView.cpp1717
-rw-r--r--src/gui/general/EditView.h405
-rw-r--r--src/gui/general/EditViewBase.cpp711
-rw-r--r--src/gui/general/EditViewBase.h396
-rw-r--r--src/gui/general/EditViewTimeSigNotifier.h56
-rw-r--r--src/gui/general/GUIPalette.cpp311
-rw-r--r--src/gui/general/GUIPalette.h185
-rw-r--r--src/gui/general/HZoomable.cpp32
-rw-r--r--src/gui/general/HZoomable.h53
-rw-r--r--src/gui/general/LinedStaff.cpp1217
-rw-r--r--src/gui/general/LinedStaff.h759
-rw-r--r--src/gui/general/LinedStaffManager.cpp33
-rw-r--r--src/gui/general/LinedStaffManager.h61
-rw-r--r--src/gui/general/MidiPitchLabel.cpp74
-rw-r--r--src/gui/general/MidiPitchLabel.h57
-rw-r--r--src/gui/general/PixmapFunctions.cpp271
-rw-r--r--src/gui/general/PixmapFunctions.h107
-rw-r--r--src/gui/general/PresetElement.cpp68
-rw-r--r--src/gui/general/PresetElement.h82
-rw-r--r--src/gui/general/PresetGroup.cpp269
-rw-r--r--src/gui/general/PresetGroup.h105
-rw-r--r--src/gui/general/PresetHandlerDialog.cpp281
-rw-r--r--src/gui/general/PresetHandlerDialog.h107
-rw-r--r--src/gui/general/ProgressReporter.cpp53
-rw-r--r--src/gui/general/ProgressReporter.h80
-rw-r--r--src/gui/general/RosegardenCanvasView.cpp485
-rw-r--r--src/gui/general/RosegardenCanvasView.h197
-rw-r--r--src/gui/general/RosegardenScrollView.cpp416
-rw-r--r--src/gui/general/RosegardenScrollView.h183
-rw-r--r--src/gui/general/Spline.cpp130
-rw-r--r--src/gui/general/Spline.h71
-rw-r--r--src/gui/general/StaffLine.cpp64
-rw-r--r--src/gui/general/StaffLine.h78
-rw-r--r--src/gui/kdeext/KLedButton.cpp60
-rw-r--r--src/gui/kdeext/KLedButton.h76
-rw-r--r--src/gui/kdeext/KStartupLogo.cpp159
-rw-r--r--src/gui/kdeext/KStartupLogo.h70
-rw-r--r--src/gui/kdeext/KTmpStatusMsg.cpp70
-rw-r--r--src/gui/kdeext/KTmpStatusMsg.h88
-rw-r--r--src/gui/kdeext/QCanvasGroupableItem.cpp279
-rw-r--r--src/gui/kdeext/QCanvasGroupableItem.h201
-rw-r--r--src/gui/kdeext/QCanvasSimpleSprite.cpp217
-rw-r--r--src/gui/kdeext/QCanvasSimpleSprite.h133
-rw-r--r--src/gui/kdeext/RGLed.cpp729
-rw-r--r--src/gui/kdeext/klearlook.cpp4095
-rw-r--r--src/gui/kdeext/klearlook.h344
-rw-r--r--src/gui/rulers/ChordNameRuler.cpp523
-rw-r--r--src/gui/rulers/ChordNameRuler.h146
-rw-r--r--src/gui/rulers/ControlChangeCommand.cpp50
-rw-r--r--src/gui/rulers/ControlChangeCommand.h55
-rw-r--r--src/gui/rulers/ControlItem.cpp195
-rw-r--r--src/gui/rulers/ControlItem.h79
-rw-r--r--src/gui/rulers/ControlRuler.cpp539
-rw-r--r--src/gui/rulers/ControlRuler.h182
-rw-r--r--src/gui/rulers/ControlRulerEventEraseCommand.cpp58
-rw-r--r--src/gui/rulers/ControlRulerEventEraseCommand.h54
-rw-r--r--src/gui/rulers/ControlRulerEventInsertCommand.cpp67
-rw-r--r--src/gui/rulers/ControlRulerEventInsertCommand.h56
-rw-r--r--src/gui/rulers/ControlSelector.cpp72
-rw-r--r--src/gui/rulers/ControlSelector.h60
-rw-r--r--src/gui/rulers/ControlTool.h39
-rw-r--r--src/gui/rulers/ControllerEventAdapter.cpp83
-rw-r--r--src/gui/rulers/ControllerEventAdapter.h53
-rw-r--r--src/gui/rulers/ControllerEventsRuler.cpp499
-rw-r--r--src/gui/rulers/ControllerEventsRuler.h118
-rw-r--r--src/gui/rulers/DefaultVelocityColour.cpp55
-rw-r--r--src/gui/rulers/DefaultVelocityColour.h54
-rw-r--r--src/gui/rulers/ElementAdapter.h46
-rw-r--r--src/gui/rulers/LoopRuler.cpp363
-rw-r--r--src/gui/rulers/LoopRuler.h148
-rw-r--r--src/gui/rulers/MarkerRuler.cpp490
-rw-r--r--src/gui/rulers/MarkerRuler.h121
-rw-r--r--src/gui/rulers/PercussionPitchRuler.cpp204
-rw-r--r--src/gui/rulers/PercussionPitchRuler.h91
-rw-r--r--src/gui/rulers/PitchRuler.cpp55
-rw-r--r--src/gui/rulers/PitchRuler.h78
-rw-r--r--src/gui/rulers/PropertyBox.cpp77
-rw-r--r--src/gui/rulers/PropertyBox.h74
-rw-r--r--src/gui/rulers/PropertyControlRuler.cpp441
-rw-r--r--src/gui/rulers/PropertyControlRuler.h120
-rw-r--r--src/gui/rulers/PropertyViewRuler.cpp175
-rw-r--r--src/gui/rulers/PropertyViewRuler.h102
-rw-r--r--src/gui/rulers/RawNoteRuler.cpp573
-rw-r--r--src/gui/rulers/RawNoteRuler.h128
-rw-r--r--src/gui/rulers/StandardRuler.cpp172
-rw-r--r--src/gui/rulers/StandardRuler.h108
-rw-r--r--src/gui/rulers/TempoColour.cpp55
-rw-r--r--src/gui/rulers/TempoColour.h60
-rw-r--r--src/gui/rulers/TempoRuler.cpp1091
-rw-r--r--src/gui/rulers/TempoRuler.h180
-rw-r--r--src/gui/rulers/TextRuler.cpp157
-rw-r--r--src/gui/rulers/TextRuler.h112
-rw-r--r--src/gui/rulers/VelocityColour.cpp120
-rw-r--r--src/gui/rulers/VelocityColour.h106
-rw-r--r--src/gui/rulers/ViewElementAdapter.cpp56
-rw-r--r--src/gui/rulers/ViewElementAdapter.h59
-rw-r--r--src/gui/seqmanager/AudioSegmentMmapper.cpp133
-rw-r--r--src/gui/seqmanager/AudioSegmentMmapper.h61
-rw-r--r--src/gui/seqmanager/CompositionMmapper.cpp174
-rw-r--r--src/gui/seqmanager/CompositionMmapper.h75
-rw-r--r--src/gui/seqmanager/ControlBlockMmapper.cpp226
-rw-r--r--src/gui/seqmanager/ControlBlockMmapper.h83
-rw-r--r--src/gui/seqmanager/MetronomeMmapper.cpp268
-rw-r--r--src/gui/seqmanager/MetronomeMmapper.h87
-rw-r--r--src/gui/seqmanager/MidiFilterDialog.cpp229
-rw-r--r--src/gui/seqmanager/MidiFilterDialog.h71
-rw-r--r--src/gui/seqmanager/SegmentMmapper.cpp562
-rw-r--r--src/gui/seqmanager/SegmentMmapper.h112
-rw-r--r--src/gui/seqmanager/SegmentMmapperFactory.cpp96
-rw-r--r--src/gui/seqmanager/SegmentMmapperFactory.h63
-rw-r--r--src/gui/seqmanager/SequenceManager.cpp2141
-rw-r--r--src/gui/seqmanager/SequenceManager.h322
-rw-r--r--src/gui/seqmanager/SequencerMapper.cpp105
-rw-r--r--src/gui/seqmanager/SequencerMapper.h113
-rw-r--r--src/gui/seqmanager/SpecialSegmentMmapper.cpp56
-rw-r--r--src/gui/seqmanager/SpecialSegmentMmapper.h59
-rw-r--r--src/gui/seqmanager/TempoSegmentMmapper.cpp77
-rw-r--r--src/gui/seqmanager/TempoSegmentMmapper.h60
-rw-r--r--src/gui/seqmanager/TimeSigSegmentMmapper.cpp72
-rw-r--r--src/gui/seqmanager/TimeSigSegmentMmapper.h62
-rw-r--r--src/gui/studio/AudioMixerWindow.cpp1734
-rw-r--r--src/gui/studio/AudioMixerWindow.h191
-rw-r--r--src/gui/studio/AudioPlugin.cpp78
-rw-r--r--src/gui/studio/AudioPlugin.h117
-rw-r--r--src/gui/studio/AudioPluginClipboard.cpp32
-rw-r--r--src/gui/studio/AudioPluginClipboard.h52
-rw-r--r--src/gui/studio/AudioPluginManager.cpp307
-rw-r--r--src/gui/studio/AudioPluginManager.h118
-rw-r--r--src/gui/studio/AudioPluginOSCGUI.cpp234
-rw-r--r--src/gui/studio/AudioPluginOSCGUI.h77
-rw-r--r--src/gui/studio/AudioPluginOSCGUIManager.cpp711
-rw-r--r--src/gui/studio/AudioPluginOSCGUIManager.h104
-rw-r--r--src/gui/studio/BankEditorDialog.cpp1713
-rw-r--r--src/gui/studio/BankEditorDialog.h211
-rw-r--r--src/gui/studio/ChangeRecordDeviceCommand.cpp66
-rw-r--r--src/gui/studio/ChangeRecordDeviceCommand.h54
-rw-r--r--src/gui/studio/DeviceEditorDialog.cpp406
-rw-r--r--src/gui/studio/DeviceEditorDialog.h87
-rw-r--r--src/gui/studio/DeviceManagerDialog.cpp833
-rw-r--r--src/gui/studio/DeviceManagerDialog.h121
-rw-r--r--src/gui/studio/MidiBankListViewItem.cpp98
-rw-r--r--src/gui/studio/MidiBankListViewItem.h70
-rw-r--r--src/gui/studio/MidiDeviceListViewItem.cpp88
-rw-r--r--src/gui/studio/MidiDeviceListViewItem.h69
-rw-r--r--src/gui/studio/MidiKeyMapListViewItem.cpp56
-rw-r--r--src/gui/studio/MidiKeyMapListViewItem.h59
-rw-r--r--src/gui/studio/MidiKeyMappingEditor.cpp197
-rw-r--r--src/gui/studio/MidiKeyMappingEditor.h78
-rw-r--r--src/gui/studio/MidiMixerVUMeter.cpp53
-rw-r--r--src/gui/studio/MidiMixerVUMeter.h61
-rw-r--r--src/gui/studio/MidiMixerWindow.cpp742
-rw-r--r--src/gui/studio/MidiMixerWindow.h125
-rw-r--r--src/gui/studio/MidiProgramsEditor.cpp631
-rw-r--r--src/gui/studio/MidiProgramsEditor.h119
-rw-r--r--src/gui/studio/MixerWindow.cpp75
-rw-r--r--src/gui/studio/MixerWindow.h77
-rw-r--r--src/gui/studio/NameSetEditor.cpp190
-rw-r--r--src/gui/studio/NameSetEditor.h90
-rw-r--r--src/gui/studio/OSCMessage.cpp87
-rw-r--r--src/gui/studio/OSCMessage.h75
-rw-r--r--src/gui/studio/RemapInstrumentDialog.cpp184
-rw-r--r--src/gui/studio/RemapInstrumentDialog.h84
-rw-r--r--src/gui/studio/StudioControl.cpp582
-rw-r--r--src/gui/studio/StudioControl.h152
-rw-r--r--src/gui/studio/SynthPluginManagerDialog.cpp360
-rw-r--r--src/gui/studio/SynthPluginManagerDialog.h98
-rw-r--r--src/gui/studio/TimerCallbackAssistant.cpp57
-rw-r--r--src/gui/studio/TimerCallbackAssistant.h61
-rw-r--r--src/gui/ui/RosegardenTransport.ui4361
-rw-r--r--src/gui/ui/audiomanager.rc67
-rw-r--r--src/gui/ui/bankeditor.rc22
-rw-r--r--src/gui/ui/clefinserter.rc11
-rw-r--r--src/gui/ui/controleditor.rc5
-rw-r--r--src/gui/ui/devicemanager.rc5
-rw-r--r--src/gui/ui/eventlist.rc105
-rw-r--r--src/gui/ui/markereditor.rc37
-rw-r--r--src/gui/ui/markerruler.rc14
-rw-r--r--src/gui/ui/matrix.rc301
-rw-r--r--src/gui/ui/matrixeraser.rc15
-rw-r--r--src/gui/ui/matrixmover.rc15
-rw-r--r--src/gui/ui/matrixpainter.rc22
-rw-r--r--src/gui/ui/matrixresizer.rc15
-rw-r--r--src/gui/ui/matrixselector.rc15
-rw-r--r--src/gui/ui/midimixer.rc34
-rw-r--r--src/gui/ui/mixer.rc65
-rw-r--r--src/gui/ui/notation.rc853
-rw-r--r--src/gui/ui/notationeraser.rc12
-rw-r--r--src/gui/ui/notationselector.rc26
-rw-r--r--src/gui/ui/noteinserter.rc23
-rw-r--r--src/gui/ui/restinserter.rc13
-rw-r--r--src/gui/ui/rosegardenui.rc440
-rw-r--r--src/gui/ui/temporuler.rc19
-rw-r--r--src/gui/ui/tempoview.rc96
-rw-r--r--src/gui/ui/textinserter.rc11
-rw-r--r--src/gui/ui/triggermanager.rc40
-rw-r--r--src/gui/widgets/AudioFaderBox.cpp294
-rw-r--r--src/gui/widgets/AudioFaderBox.h114
-rw-r--r--src/gui/widgets/AudioListItem.h97
-rw-r--r--src/gui/widgets/AudioListView.cpp67
-rw-r--r--src/gui/widgets/AudioListView.h44
-rw-r--r--src/gui/widgets/AudioRouteMenu.cpp381
-rw-r--r--src/gui/widgets/AudioRouteMenu.h94
-rw-r--r--src/gui/widgets/AudioVUMeter.cpp103
-rw-r--r--src/gui/widgets/AudioVUMeter.h96
-rw-r--r--src/gui/widgets/BigArrowButton.h47
-rw-r--r--src/gui/widgets/CollapsingFrame.cpp148
-rw-r--r--src/gui/widgets/CollapsingFrame.h75
-rw-r--r--src/gui/widgets/ColourTable.cpp131
-rw-r--r--src/gui/widgets/ColourTable.h72
-rw-r--r--src/gui/widgets/ColourTableItem.cpp52
-rw-r--r--src/gui/widgets/ColourTableItem.h60
-rw-r--r--src/gui/widgets/CurrentProgressDialog.cpp84
-rw-r--r--src/gui/widgets/CurrentProgressDialog.h81
-rw-r--r--src/gui/widgets/DiatonicPitchChooser.cpp244
-rw-r--r--src/gui/widgets/DiatonicPitchChooser.h103
-rw-r--r--src/gui/widgets/Fader.cpp567
-rw-r--r--src/gui/widgets/Fader.h137
-rw-r--r--src/gui/widgets/HSpinBox.cpp81
-rw-r--r--src/gui/widgets/HSpinBox.h67
-rw-r--r--src/gui/widgets/Label.cpp2
-rw-r--r--src/gui/widgets/Label.h63
-rw-r--r--src/gui/widgets/MidiFaderWidget.cpp41
-rw-r--r--src/gui/widgets/MidiFaderWidget.h72
-rw-r--r--src/gui/widgets/PitchChooser.cpp113
-rw-r--r--src/gui/widgets/PitchChooser.h73
-rw-r--r--src/gui/widgets/PitchDragLabel.cpp269
-rw-r--r--src/gui/widgets/PitchDragLabel.h99
-rw-r--r--src/gui/widgets/PluginControl.cpp228
-rw-r--r--src/gui/widgets/PluginControl.h104
-rw-r--r--src/gui/widgets/ProgressBar.cpp44
-rw-r--r--src/gui/widgets/ProgressBar.h56
-rw-r--r--src/gui/widgets/ProgressDialog.cpp209
-rw-r--r--src/gui/widgets/ProgressDialog.h98
-rw-r--r--src/gui/widgets/QDeferScrollView.cpp52
-rw-r--r--src/gui/widgets/QDeferScrollView.h75
-rw-r--r--src/gui/widgets/QuantizeParameters.cpp497
-rw-r--r--src/gui/widgets/QuantizeParameters.h117
-rw-r--r--src/gui/widgets/RosegardenPopupMenu.h43
-rw-r--r--src/gui/widgets/Rotary.cpp560
-rw-r--r--src/gui/widgets/Rotary.h167
-rw-r--r--src/gui/widgets/ScrollBox.cpp159
-rw-r--r--src/gui/widgets/ScrollBox.h89
-rw-r--r--src/gui/widgets/ScrollBoxDialog.cpp68
-rw-r--r--src/gui/widgets/ScrollBoxDialog.h71
-rw-r--r--src/gui/widgets/SpinBox.cpp73
-rw-r--r--src/gui/widgets/SpinBox.h65
-rw-r--r--src/gui/widgets/TextFloat.cpp112
-rw-r--r--src/gui/widgets/TextFloat.h64
-rw-r--r--src/gui/widgets/TimeWidget.cpp668
-rw-r--r--src/gui/widgets/TimeWidget.h125
-rw-r--r--src/gui/widgets/TristateCheckBox.cpp43
-rw-r--r--src/gui/widgets/TristateCheckBox.h69
-rw-r--r--src/gui/widgets/VUMeter.cpp694
-rw-r--r--src/gui/widgets/VUMeter.h154
-rw-r--r--src/gui/widgets/WheelyButton.cpp35
-rw-r--r--src/gui/widgets/WheelyButton.h68
-rw-r--r--src/gui/widgets/ZoomSlider.cpp34
-rw-r--r--src/gui/widgets/ZoomSlider.h175
-rwxr-xr-xsrc/helpers/rosegarden-audiofile-importer270
-rwxr-xr-xsrc/helpers/rosegarden-lilypondview395
-rwxr-xr-xsrc/helpers/rosegarden-project-package839
-rw-r--r--src/misc/Debug.cpp396
-rw-r--r--src/misc/Debug.h166
-rw-r--r--src/misc/Strings.cpp110
-rw-r--r--src/misc/Strings.h38
-rw-r--r--src/misc/stableheaders.h208
-rw-r--r--src/sequencer/ControlBlockMmapper.cpp81
-rw-r--r--src/sequencer/ControlBlockMmapper.h94
-rw-r--r--src/sequencer/MmappedSegment.cpp702
-rw-r--r--src/sequencer/MmappedSegment.h185
-rw-r--r--src/sequencer/RosegardenSequencerApp.cpp1850
-rw-r--r--src/sequencer/RosegardenSequencerApp.h531
-rw-r--r--src/sequencer/RosegardenSequencerIface.h364
-rw-r--r--src/sequencer/SequencerMmapper.cpp146
-rw-r--r--src/sequencer/SequencerMmapper.h103
-rw-r--r--src/sequencer/main.cpp246
-rw-r--r--src/sound/AlsaDriver.cpp5476
-rw-r--r--src/sound/AlsaDriver.h561
-rw-r--r--src/sound/AlsaPort.cpp192
-rw-r--r--src/sound/AlsaPort.h86
-rw-r--r--src/sound/AudioCache.cpp139
-rw-r--r--src/sound/AudioCache.h98
-rw-r--r--src/sound/AudioFile.cpp75
-rw-r--r--src/sound/AudioFile.h216
-rw-r--r--src/sound/AudioFileManager.cpp1257
-rw-r--r--src/sound/AudioFileManager.h327
-rw-r--r--src/sound/AudioFileTimeStretcher.cpp268
-rw-r--r--src/sound/AudioFileTimeStretcher.h76
-rw-r--r--src/sound/AudioPlayQueue.cpp501
-rw-r--r--src/sound/AudioPlayQueue.h168
-rw-r--r--src/sound/AudioProcess.cpp2463
-rw-r--r--src/sound/AudioProcess.h390
-rw-r--r--src/sound/AudioTimeStretcher.cpp667
-rw-r--r--src/sound/AudioTimeStretcher.h221
-rw-r--r--src/sound/Audit.cpp30
-rw-r--r--src/sound/Audit.h60
-rw-r--r--src/sound/BWFAudioFile.cpp171
-rw-r--r--src/sound/BWFAudioFile.h94
-rw-r--r--src/sound/ControlBlock.cpp181
-rw-r--r--src/sound/ControlBlock.h128
-rw-r--r--src/sound/DSSIPluginFactory.cpp396
-rw-r--r--src/sound/DSSIPluginFactory.h72
-rw-r--r--src/sound/DSSIPluginInstance.cpp1208
-rw-r--r--src/sound/DSSIPluginInstance.h193
-rw-r--r--src/sound/DummyDriver.h166
-rw-r--r--src/sound/ExternalTransport.h67
-rw-r--r--src/sound/JackDriver.cpp2480
-rw-r--r--src/sound/JackDriver.h297
-rw-r--r--src/sound/LADSPAPluginFactory.cpp841
-rw-r--r--src/sound/LADSPAPluginFactory.h104
-rw-r--r--src/sound/LADSPAPluginInstance.cpp435
-rw-r--r--src/sound/LADSPAPluginInstance.h137
-rw-r--r--src/sound/MP3AudioFile.cpp329
-rw-r--r--src/sound/MP3AudioFile.h128
-rw-r--r--src/sound/MappedCommon.h68
-rw-r--r--src/sound/MappedComposition.cpp216
-rw-r--r--src/sound/MappedComposition.h93
-rw-r--r--src/sound/MappedDevice.cpp250
-rw-r--r--src/sound/MappedDevice.h103
-rw-r--r--src/sound/MappedEvent.cpp593
-rw-r--r--src/sound/MappedEvent.h546
-rw-r--r--src/sound/MappedInstrument.cpp153
-rw-r--r--src/sound/MappedInstrument.h106
-rw-r--r--src/sound/MappedRealTime.cpp62
-rw-r--r--src/sound/MappedRealTime.h56
-rw-r--r--src/sound/MappedStudio.cpp1719
-rw-r--r--src/sound/MappedStudio.h552
-rw-r--r--src/sound/Midi.h184
-rw-r--r--src/sound/MidiEvent.cpp289
-rw-r--r--src/sound/MidiEvent.h141
-rw-r--r--src/sound/MidiFile.cpp2261
-rw-r--r--src/sound/MidiFile.h173
-rw-r--r--src/sound/MidiMapping.xml133
-rw-r--r--src/sound/PeakFile.cpp1033
-rw-r--r--src/sound/PeakFile.h196
-rw-r--r--src/sound/PeakFileManager.cpp327
-rw-r--r--src/sound/PeakFileManager.h162
-rw-r--r--src/sound/PlayableAudioFile.cpp1086
-rw-r--r--src/sound/PlayableAudioFile.h219
-rw-r--r--src/sound/PluginFactory.cpp120
-rw-r--r--src/sound/PluginFactory.h97
-rw-r--r--src/sound/PluginIdentifier.cpp72
-rw-r--r--src/sound/PluginIdentifier.h50
-rw-r--r--src/sound/RIFFAudioFile.cpp686
-rw-r--r--src/sound/RIFFAudioFile.h168
-rw-r--r--src/sound/RecordableAudioFile.cpp164
-rw-r--r--src/sound/RecordableAudioFile.h68
-rw-r--r--src/sound/RingBuffer.h572
-rw-r--r--src/sound/RosegardenMidiRecord.mcopclass5
-rw-r--r--src/sound/RunnablePluginInstance.cpp42
-rw-r--r--src/sound/RunnablePluginInstance.h114
-rw-r--r--src/sound/SF2PatchExtractor.cpp217
-rw-r--r--src/sound/SF2PatchExtractor.h58
-rw-r--r--src/sound/SampleWindow.h192
-rw-r--r--src/sound/Scavenger.h211
-rw-r--r--src/sound/SequencerDataBlock.cpp361
-rw-r--r--src/sound/SequencerDataBlock.h140
-rw-r--r--src/sound/SoundDriver.cpp391
-rw-r--r--src/sound/SoundDriver.h529
-rw-r--r--src/sound/SoundDriverFactory.cpp66
-rw-r--r--src/sound/SoundDriverFactory.h37
-rw-r--r--src/sound/SoundFile.cpp295
-rw-r--r--src/sound/SoundFile.h155
-rw-r--r--src/sound/WAVAudioFile.cpp255
-rw-r--r--src/sound/WAVAudioFile.h93
-rw-r--r--src/test/accidentals.cpp88
-rw-r--r--src/test/dummy.cpp6
-rw-r--r--src/test/segmenttransposecommand.cpp161
-rw-r--r--src/test/transpose.cpp154
1205 files changed, 289781 insertions, 0 deletions
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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <bownie@bownie.com>
+
+ This file is Copyright 2002
+ Randall Farmer <rfarme@simons-rock.edu>
+
+ 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 <iostream>
+#include <string>
+#include <map>
+#include <algorithm>
+#include <cmath> // 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<Int>(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<int, ChordData>
+ (
+ (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<timeT, TimeSignature> 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<Int>(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<timeT, HarmonyGuess>(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<double, ChordLabel> instantiated,
+// pair<double, ChordLabel> 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<PitchProfile, ChordLabel>(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<int> 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<int> 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<int> 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<timeT, TimeSignature> 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<Int>(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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <bownie@bownie.com>
+
+ This file is Copyright 2002
+ Randall Farmer <rfarme@simons-rock.edu>
+
+ 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 <string>
+#include <map>
+#include <set>
+#include <vector>
+
+#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<int, ChordData> 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<double, ChordLabel> ChordPossibility;
+ typedef std::vector<ChordPossibility> HarmonyGuess;
+ typedef std::vector<std::pair<timeT, HarmonyGuess> > HarmonyGuessList;
+ struct cp_less : public std::binary_function<ChordPossibility, ChordPossibility, bool>
+ {
+ 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<std::pair<PitchProfile, ChordLabel> > 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<ChordProgression> 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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <bownie@bownie.com>
+
+ 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 <cstdio>
+
+
+#if (__GNUC__ < 3)
+#include <strstream>
+#define stringstream strstream
+#else
+#include <sstream>
+#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 << " <device id=\"" << m_id
+ << "\" name=\"" << m_name
+ << "\" type=\"audio\">" << std::endl;
+
+ for (iit = m_instruments.begin(); iit != m_instruments.end(); ++iit)
+ audioDevice << (*iit)->toXmlString();
+
+ audioDevice << " </device>"
+#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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <bownie@bownie.com>
+
+ 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 <string>
+
+#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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <bownie@bownie.com>
+
+ 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 <cmath>
+#include <iostream>
+#include <map>
+#include <vector>
+
+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<float> LevelList;
+static std::map<int, LevelList> 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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <bownie@bownie.com>
+
+ 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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <bownie@bownie.com>
+
+ 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 <iostream>
+#include <cstring>
+
+#if (__GNUC__ < 3)
+#include <strstream>
+#define stringstream strstream
+#else
+#include <sstream>
+#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 << " <synth ";
+ } else {
+ plugin << " <plugin"
+ << " position=\""
+ << m_position
+ << "\" ";
+ }
+
+ plugin << "identifier=\""
+ << encode(m_identifier)
+ << "\" bypassed=\"";
+
+ if (m_bypass)
+ plugin << "true\" ";
+ else
+ plugin << "false\" ";
+
+ if (m_program != "") {
+ plugin << "program=\"" << encode(m_program) << "\"";
+ }
+
+ plugin << ">" << std::endl;
+
+ for (unsigned int i = 0; i < m_ports.size(); i++)
+ {
+ plugin << " <port id=\""
+ << m_ports[i]->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 << " <configure key=\""
+ << encode(i->first) << "\" value=\""
+ << encode(i->second) << "\"/>" << std::endl;
+ }
+
+ if (m_position == Instrument::SYNTH_PLUGIN_POSITION) {
+ plugin << " </synth>";
+ } else {
+ plugin << " </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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <bownie@bownie.com>
+
+ 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 <vector>
+#include <string>
+#include <map>
+
+#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<PluginPortInstance*>::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<std::string, std::string> 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<PluginPortInstance*> 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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <bownie@bownie.com>
+
+ 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 <vector>
+
+#if (__GNUC__ < 3)
+#include <strstream>
+#define stringstream strstream
+#else
+#include <sstream>
+#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<PropertyName> 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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <bownie@bownie.com>
+
+ 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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <bownie@bownie.com>
+
+ 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 <iostream>
+#include <cmath>
+#include <cstdio> // for sprintf
+#include <ctime>
+
+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<timeT>
+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<timeT>
+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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <bownie@bownie.com>
+
+ 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<timeT> 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<timeT> 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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <bownie@bownie.com>
+
+ 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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <bownie@bownie.com>
+
+ 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 <set>
+#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<Segment *, Segment::SegmentCmp> 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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <bownie@bownie.com>
+
+ This file is Copyright 2003
+ Mark Hymers <markh@linuxfromscratch.org>
+
+ 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 <strstream>
+#define stringstream strstream
+#else
+#include <sstream>
+#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 << "<colour red=\"" << m_r
+ << "\" green=\"" << m_g
+ << "\" blue=\"" << m_b
+#if (__GNUC__ < 3)
+ << "\"/>" << 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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <bownie@bownie.com>
+
+ This file is Copyright 2003
+ Mark Hymers <markh@linuxfromscratch.org>
+
+ 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 <string>
+
+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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <bownie@bownie.com>
+
+ This file is Copyright 2003
+ Mark Hymers <markh@linuxfromscratch.org>
+
+ 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 <string>
+
+#if (__GNUC__ < 3)
+#include <strstream>
+#define stringstream strstream
+#else
+#include <sstream>
+#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 << " <colourmap name=\"" << XmlExportable::encode(name)
+ << "\">" << std::endl;
+
+ for (RCMap::const_iterator pos = m_map.begin(); pos != m_map.end(); ++pos)
+ {
+ output << " " << " <colourpair id=\"" << pos->first
+ << "\" name=\"" << XmlExportable::encode(pos->second.second)
+ << "\" " << pos->second.first.dataToXmlString() << "/>" << std::endl;
+ }
+
+#if (__GNUC__ < 3)
+ output << " </colourmap>" << std::endl << std::ends;
+#else
+ output << " </colourmap>" << 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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <bownie@bownie.com>
+
+ This file is Copyright 2003
+ Mark Hymers <markh@linuxfromscratch.org>
+
+ 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 <utility>
+#include <map>
+#include <string>
+#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<unsigned int, std::pair<Colour, std::string>, std::less<unsigned int> > 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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <bownie@bownie.com>
+
+ 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 <iostream>
+#include <iomanip>
+#include <algorithm>
+#include <cmath>
+#include <typeinfo>
+
+#if (__GNUC__ < 3)
+#include <strstream>
+#define stringstream strstream
+#else
+#include <sstream>
+#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<Bool>(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<Segment*>(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<Segment *> simultaneous;
+ std::multimap<timeT, Segment *> 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<timeT, Segment *>::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<Segment *, int> indices;
+ std::set<int> used;
+ std::multimap<timeT, Segment *> 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<timeT, Segment *>::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<TriggerSegmentId, TriggerSegmentRec::SegmentRuntimeIdSet> 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<Int>(BaseProperties::TRIGGER_SEGMENT_ID);
+ refs[id].insert((*i)->getRuntimeId());
+ }
+ }
+ }
+
+ for (std::map<TriggerSegmentId,
+ TriggerSegmentRec::SegmentRuntimeIdSet>::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<Int>(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<Int>(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<timeT, timeT>
+Composition::getBarRangeForTime(timeT t) const
+{
+ return getBarRange(getBarNumber(t));
+}
+
+
+std::pair<timeT, timeT>
+Composition::getBarRange(int n) const
+{
+ calculateBarPositions();
+
+ Event dummy("dummy", 0);
+ dummy.set<Int>(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<Int>(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<Int>(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<timeT, timeT>(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<timeT, TimeSignature>
+Composition::getTimeSignatureChange(int n) const
+{
+ return std::pair<timeT, TimeSignature>
+ (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<Int>(TempoProperty));
+
+ if ((*i)->has(TargetTempoProperty)) {
+
+ tempoT target = (tempoT)((*i)->get<Int>(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<Int>(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<timeT, tempoT> tc = getTempoChange(n);
+ if (tc.first == time) {
+ if (tc.second == m_minTempo || tc.second == m_maxTempo) {
+ fullTempoUpdate = true;
+ } else {
+ std::pair<bool, tempoT> tr = getTempoRamping(n);
+ if (tr.first &&
+ (tr.second == m_minTempo || tr.second == m_maxTempo)) {
+ fullTempoUpdate = true;
+ }
+ }
+ }
+ }
+
+ Event *tempoEvent = new Event(TempoEventType, time);
+ tempoEvent->set<Int>(TempoProperty, tempo);
+
+ if (targetTempo >= 0) {
+ tempoEvent->set<Int>(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<timeT, tempoT>
+Composition::getTempoChange(int n) const
+{
+ return std::pair<timeT, tempoT>
+ (m_tempoSegment[n]->getAbsoluteTime(),
+ tempoT(m_tempoSegment[n]->get<Int>(TempoProperty)));
+}
+
+std::pair<bool, tempoT>
+Composition::getTempoRamping(int n, bool calculate) const
+{
+ tempoT target = -1;
+ if (m_tempoSegment[n]->has(TargetTempoProperty)) {
+ target = m_tempoSegment[n]->get<Int>(TargetTempoProperty);
+ }
+ bool ramped = (target >= 0);
+ if (target == 0) {
+ if (calculate) {
+ if (m_tempoSegment.size() > n+1) {
+ target = m_tempoSegment[n+1]->get<Int>(TempoProperty);
+ }
+ }
+ }
+ if (target < 0 || (calculate && (target == 0))) {
+ target = m_tempoSegment[n]->get<Int>(TempoProperty);
+ }
+ return std::pair<bool, tempoT>(ramped, target);
+}
+
+void
+Composition::removeTempoChange(int n)
+{
+ tempoT oldTempo = m_tempoSegment[n]->get<Int>(TempoProperty);
+ tempoT oldTarget = -1;
+
+ if (m_tempoSegment[n]->has(TargetTempoProperty)) {
+ oldTarget = m_tempoSegment[n]->get<Int>(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<Int>(TempoProperty);
+ tempoT target = -1;
+ if ((*i)->has(TargetTempoProperty)) {
+ target = (*i)->get<Int>(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<Int>(TempoProperty)),
+ nextTempoTime - (*i)->getAbsoluteTime(),
+ target);
+ } else {
+ elapsed = getTempoTimestamp(*i) +
+ time2RealTime(t - (*i)->getAbsoluteTime(),
+ tempoT((*i)->get<Int>(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<Int>(TempoProperty)),
+ nextTempoTime - (*i)->getAbsoluteTime(),
+ target);
+ } else {
+ elapsed = (*i)->getAbsoluteTime() +
+ realTime2Time(t - getTempoTimestamp(*i),
+ (tempoT)((*i)->get<Int>(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<Int>(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<Int>(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 <<std::endl;
+ std::cerr << "that's sqrt( (" << ((2.0*n*a*2.0*n*a)) << ") + "
+ << (8*(b-a)*t*n) << " )" << endl;
+
+ std::cerr << "so our original expression was " << rt << " = "
+ << a << "t + (t^2 * (" << b << " - " << a << ")) / " << 2*n << std::endl;
+#endif
+
+ return realTime2Time(rt, tempo);
+ }
+
+ double term3 = sqrt(term2);
+
+ // We only want the positive root
+ if (term3 > 0) term3 = -term3;
+
+ double result = - (term1 + term3) / (2 * (b - a));
+
+#ifdef DEBUG_TEMPO_STUFF
+ std::cerr << "Composition::realTime2Time:" <<endl;
+ std::cerr << "n = " << n << ", b = " << b << ", a = " << a << ", t = " << t <<std::endl;
+ std::cerr << "+/-sqrt(term2) = " << term3 << std::endl;
+ std::cerr << "result = " << result << endl;
+#endif
+
+ return long(result + 0.1);
+}
+
+bool
+Composition::getTempoTarget(ReferenceSegment::const_iterator i,
+ tempoT &target,
+ timeT &targetTime) const
+{
+ target = -1;
+ targetTime = 0;
+ bool have = false;
+
+ if ((*i)->has(TargetTempoProperty)) {
+ target = (*i)->get<Int>(TargetTempoProperty);
+ if (target >= 0) {
+ ReferenceSegment::const_iterator j(i);
+ if (++j != m_tempoSegment.end()) {
+ if (target == 0) target = (*j)->get<Int>(TempoProperty);
+ targetTime = (*j)->getAbsoluteTime();
+ } else {
+ targetTime = getEndMarker();
+ if (targetTime < (*i)->getAbsoluteTime()) {
+ target = -1;
+ }
+ }
+ if (target > 0) have = true;
+ }
+ }
+
+ return have;
+}
+
+RealTime
+Composition::getTempoTimestamp(const Event *e)
+{
+ RealTime res;
+ e->get<RealTimeT>(TempoTimestampProperty, res);
+ return res;
+}
+
+void
+Composition::setTempoTimestamp(Event *e, RealTime t)
+{
+ e->setMaybe<RealTimeT>(TempoTimestampProperty, t);
+}
+
+void
+Composition::getMusicalTimeForAbsoluteTime(timeT absTime,
+ int &bar, int &beat,
+ int &fraction, int &remainder)
+{
+ bar = getBarNumber(absTime);
+
+ TimeSignature timeSig = getTimeSignatureAt(absTime);
+ timeT barStart = getBarStart(bar);
+ timeT beatDuration = timeSig.getBeatDuration();
+ beat = (absTime - barStart) / beatDuration + 1;
+
+ remainder = (absTime - barStart) % beatDuration;
+ timeT fractionDuration = Note(Note::Shortest).getDuration();
+ fraction = remainder / fractionDuration;
+ remainder = remainder % fractionDuration;
+}
+
+void
+Composition::getMusicalTimeForDuration(timeT absTime, timeT duration,
+ int &bars, int &beats,
+ int &fractions, int &remainder)
+{
+ TimeSignature timeSig = getTimeSignatureAt(absTime);
+ timeT barDuration = timeSig.getBarDuration();
+ timeT beatDuration = timeSig.getBeatDuration();
+
+ bars = duration / barDuration;
+ beats = (duration % barDuration) / beatDuration;
+ remainder = (duration % barDuration) % beatDuration;
+ timeT fractionDuration = Note(Note::Shortest).getDuration();
+ fractions = remainder / fractionDuration;
+ remainder = remainder % fractionDuration;
+}
+
+timeT
+Composition::getAbsoluteTimeForMusicalTime(int bar, int beat,
+ int fraction, int remainder)
+{
+ timeT t = getBarStart(bar - 1);
+ TimeSignature timesig = getTimeSignatureAt(t);
+ t += (beat-1) * timesig.getBeatDuration();
+ t += Note(Note::Shortest).getDuration() * fraction;
+ t += remainder;
+ return t;
+}
+
+timeT
+Composition::getDurationForMusicalTime(timeT absTime,
+ int bars, int beats,
+ int fractions, int remainder)
+{
+ TimeSignature timeSig = getTimeSignatureAt(absTime);
+ timeT barDuration = timeSig.getBarDuration();
+ timeT beatDuration = timeSig.getBeatDuration();
+ timeT t = bars * barDuration + beats * beatDuration + fractions *
+ Note(Note::Shortest).getDuration() + remainder;
+ return t;
+}
+
+void
+Composition::setPosition(timeT position)
+{
+ m_position = position;
+}
+
+void Composition::setPlayMetronome(bool value)
+{
+ m_playMetronome = value;
+ notifyMetronomeChanged();
+}
+
+void Composition::setRecordMetronome(bool value)
+{
+ m_recordMetronome = value;
+ notifyMetronomeChanged();
+}
+
+
+
+#ifdef TRACK_DEBUG
+// track debug convenience function
+//
+static void dumpTracks(Composition::trackcontainer& tracks)
+{
+ Composition::trackiterator it = tracks.begin();
+ for (; it != tracks.end(); ++it) {
+ std::cerr << "tracks[" << (*it).first << "] = "
+ << (*it).second << std::endl;
+ }
+}
+#endif
+
+Track* Composition::getTrackById(TrackId track) const
+{
+ trackconstiterator i = m_tracks.find(track);
+
+ if (i != m_tracks.end())
+ return (*i).second;
+
+ std::cerr << "Composition::getTrackById("
+ << track << ") - WARNING - track id not found, this is probably a BUG "
+ << __FILE__ << ":" << __LINE__ << std::endl;
+ std::cerr << "Available track ids are: " << std::endl;
+ for (trackconstiterator i = m_tracks.begin(); i != m_tracks.end(); ++i) {
+ std::cerr << (*i).second->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 << "<composition recordtracks=\"";
+ for (recordtrackiterator i = m_recordTracks.begin();
+ i != m_recordTracks.end(); ) {
+ composition << *i;
+ if (++i != m_recordTracks.end()) {
+ composition << ",";
+ }
+ }
+ composition << "\" pointer=\"" << m_position;
+ composition << "\" defaultTempo=\"";
+ composition << std::setiosflags(std::ios::fixed)
+ << std::setprecision(4)
+ << getTempoQpm(m_defaultTempo);
+ composition << "\" compositionDefaultTempo=\"";
+ composition << m_defaultTempo;
+
+ if (m_loopStart != m_loopEnd)
+ {
+ composition << "\" loopstart=\"" << m_loopStart;
+ composition << "\" loopend=\"" << m_loopEnd;
+ }
+
+ composition << "\" startMarker=\"" << m_startMarker;
+ composition << "\" endMarker=\"" << m_endMarker;
+
+ // Add the Solo if set
+ //
+ if (m_solo)
+ composition << "\" solo=\"" << m_solo;
+
+ composition << "\" selected=\"" << m_selectedTrack;
+ composition << "\" playmetronome=\"" << m_playMetronome;
+ composition << "\" recordmetronome=\"" << m_recordMetronome;
+ composition << "\" nexttriggerid=\"" << m_nextTriggerSegmentId;
+ 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 << " <timesignature time=\"" << (*i)->getAbsoluteTime()
+ << "\" numerator=\""
+ << (*i)->get<Int>(TimeSignature::NumeratorPropertyName)
+ << "\" denominator=\""
+ << (*i)->get<Int>(TimeSignature::DenominatorPropertyName)
+ << "\"";
+
+ bool common = false;
+ (*i)->get<Bool>(TimeSignature::ShowAsCommonTimePropertyName, common);
+ if (common) composition << " common=\"true\"";
+
+ bool hidden = false;
+ (*i)->get<Bool>(TimeSignature::IsHiddenPropertyName, hidden);
+ if (hidden) composition << " hidden=\"true\"";
+
+ bool hiddenBars = false;
+ (*i)->get<Bool>(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<Int>(TempoProperty));
+ tempoT target = -1;
+ if ((*i)->has(TargetTempoProperty)) {
+ target = tempoT((*i)->get<Int>(TargetTempoProperty));
+ }
+ composition << " <tempo time=\"" << (*i)->getAbsoluteTime()
+ << "\" bph=\"" << ((tempo * 6) / 10000)
+ << "\" tempo=\"" << tempo;
+ if (target >= 0) {
+ composition << "\" target=\"" << target;
+ }
+ composition << "\"/>" << endl;
+ }
+
+ composition << endl;
+
+ composition << "<metadata>" << endl
+ << m_metadata.toXmlString() << endl
+ << "</metadata>" << endl << endl;
+
+ composition << "<markers>" << endl;
+ for (markerconstiterator mIt = m_markers.begin();
+ mIt != m_markers.end(); ++mIt)
+ {
+ composition << (*mIt)->toXmlString();
+ }
+ composition << "</markers>" << endl;
+
+
+#if (__GNUC__ < 3)
+ composition << "</composition>" << std::ends;
+#else
+ composition << "</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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <bownie@bownie.com>
+
+ 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 <set>
+#include <map>
+
+#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<Segment*, Segment::SegmentCmp> segmentcontainer;
+ typedef segmentcontainer::iterator iterator;
+ typedef segmentcontainer::const_iterator const_iterator;
+
+ typedef std::map<TrackId, Track*> trackcontainer;
+ typedef trackcontainer::iterator trackiterator;
+ typedef trackcontainer::const_iterator trackconstiterator;
+
+ typedef std::vector<Marker*> markercontainer;
+ typedef markercontainer::iterator markeriterator;
+ typedef markercontainer::const_iterator markerconstiterator;
+
+ typedef std::set<TriggerSegmentRec *, TriggerSegmentCmp> triggersegmentcontainer;
+ typedef triggersegmentcontainer::iterator triggersegmentcontaineriterator;
+ typedef triggersegmentcontainer::const_iterator triggersegmentcontainerconstiterator;
+
+ typedef std::set<TrackId> 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<timeT, timeT> 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<timeT, timeT> 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<timeT, TimeSignature> 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<timeT, tempoT> 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<bool, tempoT> 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<String>(CompositionMetadataKeys::Copyright,
+ "");
+ }
+ void setCopyrightNote(const std::string &cr) {
+ m_metadata.set<String>(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<Int>(BarNumberProperty) <
+ e2.get<Int>(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<Event *> // not a set: want random access for bars
+ {
+ typedef FastVector<Event *> 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<CompositionObserver *> 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<RefreshStatus> 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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <bownie@bownie.com>
+
+ This file is Copyright 2002
+ Randall Farmer <rfarme@simons-rock.edu>
+ 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 <list>
+#include <utility>
+
+#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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <bownie@bownie.com>
+
+ This file is Copyright 2002
+ Randall Farmer <rfarme@simons-rock.edu>
+ 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 <list>
+#include <utility>
+
+#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<TrackId> 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<Segment *> segmentlist;
+ typedef std::vector<Segment::iterator> 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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <bownie@bownie.com>
+
+ 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 <string>
+#include <algorithm>
+
+#include "Configuration.h"
+
+#if (__GNUC__ < 3)
+#include <strstream>
+#define stringstream strstream
+#else
+#include <sstream>
+#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<std::string>
+Configuration::getPropertyNames()
+{
+ std::vector<std::string> 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 << "<property name=\""
+ << encode(i->first.getName()) << "\" value=\""
+ << encode(get<String>(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<PropertyName> getFixedKeys() {
+ std::vector<PropertyName> 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<Int>(ZoomLevel, 0);
+ set<String>(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 << "<configuration>" << endl;
+
+ config << " <" << ZoomLevel << " type=\"Int\">" << get<Int>(ZoomLevel)
+ << "</" << ZoomLevel << ">\n";
+
+ config << " <" << TransportMode << " type=\"String\">" << get<String>(TransportMode)
+ << "</" << TransportMode << ">\n";
+
+ config << "</configuration>" << 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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <bownie@bownie.com>
+
+ 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 <string>
+#include <vector>
+
+#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 <PropertyType P>
+ void
+ set(const PropertyName &name,
+ typename PropertyDefn<P>::basic_type value);
+
+ /**
+ * get() with a default value
+ */
+ template <PropertyType P>
+ typename PropertyDefn<P>::basic_type
+ get(const PropertyName &name,
+ typename PropertyDefn<P>::basic_type defaultVal) const;
+
+ /**
+ * regular get()
+ */
+ template <PropertyType P>
+ typename PropertyDefn<P>::basic_type get(const PropertyName &name) const;
+
+ // For exporting -- doesn't write the <configuration> 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<std::string> 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<PropertyName> getFixedKeys();
+}
+
+class DocumentConfiguration : public Configuration
+{
+public:
+ DocumentConfiguration();
+ DocumentConfiguration(const DocumentConfiguration &);
+ ~DocumentConfiguration();
+
+ DocumentConfiguration& operator=(const DocumentConfiguration &);
+
+ // for exporting -- doesn't write the <configuration> 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 <PropertyType P>
+void
+Configuration::set(const PropertyName &name,
+ typename PropertyDefn<P>::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<PropertyStore<P> *>(sb))->setData(value);
+
+ } else {
+
+ PropertyStoreBase *p = new PropertyStore<P>(value);
+ insert(PropertyPair(name, p));
+
+ }
+
+}
+
+template <PropertyType P>
+typename PropertyDefn<P>::basic_type
+Configuration::get(const PropertyName &name,
+ typename PropertyDefn<P>::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<PropertyStore<P> *>(sb))->getData();
+ } else {
+ throw BadType(name.getName(),
+ PropertyDefn<P>::typeName(), sb->getTypeName(),
+ __FILE__, __LINE__);
+ }
+}
+
+template <PropertyType P>
+typename PropertyDefn<P>::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<PropertyStore<P> *>(sb))->getData();
+ } else {
+ throw BadType(name.getName(),
+ PropertyDefn<P>::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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <bownie@bownie.com>
+
+ 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 <strstream>
+#define stringstream strstream
+#else
+#include <sstream>
+#endif
+
+#include "ControlParameter.h"
+#include "MidiTypes.h"
+
+namespace Rosegarden
+{
+
+ControlParameter::ControlParameter():
+ m_name("<unnamed>"),
+ m_type(Rosegarden::Controller::EventType),
+ m_description("<none>"),
+ 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 << " <control name=\"" << encode(m_name)
+ << "\" type=\"" << encode(m_type)
+ << "\" description=\"" << encode(m_description)
+ << "\" min=\"" << m_min
+ << "\" max=\"" << m_max
+ << "\" default=\"" << m_default
+ << "\" controllervalue=\"" << int(m_controllerValue)
+ << "\" colourindex=\"" << m_colourIndex
+ << "\" ipbposition=\"" << m_ipbPosition;
+
+#if (__GNUC__ < 3)
+ 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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <bownie@bownie.com>
+
+ 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 <string>
+
+#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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <bownie@bownie.com>
+
+ 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<ControlParameter> 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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <bownie@bownie.com>
+
+ 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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <bownie@bownie.com>
+
+ 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 <string>
+#include <vector>
+
+// 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<Instrument *> 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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <bownie@bownie.com>
+
+ 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<int>(m*x) + c; break;
+ case M: m = static_cast<double>(y - c) / static_cast<double>(x); break;
+ case X: x = static_cast<int>(static_cast<float>(y - c) / m); break;
+ case C: c = y - static_cast<int>(m*x); break;
+ }
+}
+
+void Equation::solveForYByEndPoints(Point a, Point b, double x, double &y)
+{
+ double m, c, y1, x1;
+
+ m = static_cast<double>(b.y - a.y) / static_cast<double>(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<double>(b.y - a.y) / static_cast<double>(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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <bownie@bownie.com>
+
+ 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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <bownie@bownie.com>
+
+ 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 <cstdio>
+#include <cctype>
+#include <iostream>
+#include "Event.h"
+#include "XmlExportable.h"
+
+#if (__GNUC__ < 3)
+#include <strstream>
+#define stringstream strstream
+#else
+#include <sstream>
+#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<PropertyStore<Int> *>(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<PropertyStore<Int> *>(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<Int>(t)));
+ } else {
+ static_cast<PropertyStore<Int> *>(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 << "<event";
+
+ if (getType().length() != 0) {
+ out << " type=\"" << getType() << "\"";
+ }
+
+ if (getDuration() != 0) {
+ out << " duration=\"" << getDuration() << "\"";
+ }
+
+ if (getSubOrdering() != 0) {
+ out << " subordering=\"" << getSubOrdering() << "\"";
+ }
+
+ if (expectedTime == 0) {
+ out << " absoluteTime=\"" << getAbsoluteTime() << "\"";
+ } else if (getAbsoluteTime() != expectedTime) {
+ out << " timeOffset=\"" << (getAbsoluteTime() - expectedTime) << "\"";
+ }
+
+ out << ">";
+
+ // Save all persistent properties as <property> elements
+
+ PropertyNames propertyNames(getPersistentPropertyNames());
+ for (PropertyNames::const_iterator i = propertyNames.begin();
+ i != propertyNames.end(); ++i) {
+
+ out << "<property name=\""
+ << XmlExportable::encode(i->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 <nproperty> 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 << "<nproperty name=\""
+ << XmlExportable::encode(s) << "\" ";
+ 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))
+ << "\"/>";
+ }
+
+ out << "</event>";
+
+#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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <bownie@bownie.com>
+
+ 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 <string>
+#include <vector>
+#ifndef NDEBUG
+#include <iostream>
+#include <ctime>
+#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 <PropertyType P>
+ typename PropertyDefn<P>::basic_type get(const PropertyName &name) const;
+ // throw (NoData, BadType);
+
+ // no throw, returns bool
+ template <PropertyType P>
+ bool get(const PropertyName &name, typename PropertyDefn<P>::basic_type &val) const;
+
+ template <PropertyType P>
+ bool isPersistent(const PropertyName &name) const;
+ // throw (NoData);
+
+ template <PropertyType P>
+ 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 <PropertyType P>
+ void set(const PropertyName &name, typename PropertyDefn<P>::basic_type value,
+ bool persistent = true);
+ // throw (BadType);
+
+ // set non-persistent, but only if there's no persistent value already
+ template <PropertyType P>
+ void setMaybe(const PropertyName &name, typename PropertyDefn<P>::basic_type value);
+ // throw (BadType);
+
+ template <PropertyType P>
+ 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<PropertyName> 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<Event *>(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 <PropertyType P>
+bool
+Event::get(const PropertyName &name, typename PropertyDefn<P>::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<PropertyStore<P> *>(sb))->getData();
+ return true;
+ }
+ else {
+#ifndef NDEBUG
+ std::cerr << "Event::get() Error: Attempt to get property \"" << name
+ << "\" as " << PropertyDefn<P>::typeName() <<", actual type is "
+ << sb->getTypeName() << std::endl;
+#endif
+ return false;
+ }
+
+ } else {
+ return false;
+ }
+}
+
+
+template <PropertyType P>
+typename PropertyDefn<P>::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<PropertyStore<P> *>(sb))->getData();
+ else {
+ throw BadType(name.getName(),
+ PropertyDefn<P>::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 <PropertyType P>
+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 <PropertyType P>
+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 <PropertyType P>
+void
+Event::set(const PropertyName &name, typename PropertyDefn<P>::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<PropertyStore<P> *>(sb))->setData(value);
+ } else {
+ throw BadType(name.getName(),
+ PropertyDefn<P>::typeName(), sb->getTypeName(),
+ __FILE__, __LINE__);
+ }
+
+ } else {
+ PropertyStoreBase *p = new PropertyStore<P>(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 <PropertyType P>
+void
+Event::setMaybe(const PropertyName &name, typename PropertyDefn<P>::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<PropertyStore<P> *>(sb))->setData(value);
+ } else {
+ throw BadType(name.getName(),
+ PropertyDefn<P>::typeName(), sb->getTypeName(),
+ __FILE__, __LINE__);
+ }
+ } else {
+ PropertyStoreBase *p = new PropertyStore<P>(value);
+ insert(PropertyPair(name, p), false);
+ }
+}
+
+
+template <PropertyType P>
+void
+Event::setFromString(const PropertyName &name, std::string value, bool persistent)
+ // throw (BadType)
+{
+ set<P>(name, PropertyDefn<P>::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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <bownie@bownie.com>
+
+ 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 <iostream>
+
+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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <bownie@bownie.com>
+
+ 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 <string>
+#include <exception>
+
+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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <bownie@bownie.com>
+
+ 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 <iterator>
+#include <cstdlib> /* for malloc, realloc, free */
+#include <cstring> /* for memmove */
+
+#include <cassert>
+
+
+/**
+ 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 T>
+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<std::random_access_iterator_tag, T, difference_type>
+#else
+#if defined(__STL_USE_NAMESPACES)
+ std::
+#endif
+ random_access_iterator<T, difference_type>
+#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<T>::difference_type i) {
+ m_i += i; return *this;
+ }
+ iterator_base &operator-=(FastVector<T>::difference_type i) {
+ m_i -= i; return *this;
+ }
+
+ iterator_base operator+(FastVector<T>::difference_type i) const {
+ iterator_base n(*this); n += i; return n;
+ }
+ iterator_base operator-(FastVector<T>::difference_type i) const {
+ iterator_base n(*this); n -= i; return n;
+ }
+
+ typename FastVector<T>::difference_type operator-(const iterator_base &i) const{
+ assert(m_v == i.m_v);
+ return m_i - i.m_i;
+ }
+
+ protected:
+ iterator_base(FastVector<T> *v, size_type i) : m_v(v), m_i(i) { }
+ FastVector<T> *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<T>;
+ iterator(FastVector<T> *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<T>;
+ reverse_iterator(FastVector<T> *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<T>;
+ const_iterator(const FastVector<T> *v, size_type i) :
+ iterator_base(const_cast<FastVector<T> *>(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<T>;
+ const_reverse_iterator(const FastVector<T> *v, size_type i) :
+ iterator_base(const_cast<FastVector<T> *>(v),i) { }
+ };
+
+public:
+ FastVector() :
+ m_items(0), m_count(0), m_gapStart(-1),
+ m_gapLength(0), m_size(0) { }
+ FastVector(const FastVector<T> &);
+ virtual ~FastVector();
+
+ template <class InputIterator>
+ 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<T> &operator=(const FastVector<T> &);
+
+ 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<T> &v);
+ bool operator==(const FastVector<T> &) const;
+ bool operator!=(const FastVector<T> &v) const { return !operator==(v); }
+ bool operator<(const FastVector<T> &) const;
+ bool operator>(const FastVector<T> &) const;
+ bool operator<=(const FastVector<T> &) const;
+ bool operator>=(const FastVector<T> &) 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<FastVector<T> *>(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 <class InputIterator>
+ 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 <class T>
+void *operator new(size_t, FastVector<T> *, void *space)
+{
+ return space;
+}
+
+template <class T>
+FastVector<T>::FastVector(const FastVector<T> &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 <class T>
+FastVector<T>::~FastVector()
+{
+ clear();
+ free(static_cast<void *>(m_items));
+}
+
+template <class T>
+FastVector<T>& FastVector<T>::operator=(const FastVector<T>& 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 <class T>
+void FastVector<T>::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 <class T>
+void FastVector<T>::resize(size_type needed)
+{
+ size_type newSize = bestNewCount(needed, sizeof(T));
+
+ if (m_items) {
+ m_items = static_cast<T *>(realloc(m_items, newSize * sizeof(T)));
+ } else {
+ m_items = static_cast<T *>(malloc(newSize * sizeof(T)));
+ }
+
+ m_size = newSize;
+}
+
+template <class T>
+void FastVector<T>::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 <class T>
+void FastVector<T>::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 <class T>
+template <class InputIterator>
+typename FastVector<T>::iterator FastVector<T>::insert
+(const FastVector<T>::iterator &p, InputIterator &i, InputIterator &j)
+{
+ size_type n = p.m_i;
+ while (i != j) {
+ --j;
+ insert(n, *j);
+ }
+ return begin() + n;
+}
+
+template <class T>
+typename FastVector<T>::iterator FastVector<T>::erase
+(const FastVector<T>::iterator &i, const FastVector<T>::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 <class T>
+void FastVector<T>::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 <class T>
+T* FastVector<T>::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 <class T>
+bool FastVector<T>::operator==(const FastVector<T> &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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <bownie@bownie.com>
+
+ 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 <stdio.h>
+
+#include "Instrument.h"
+#include "MidiDevice.h"
+#include "AudioPluginInstance.h"
+#include "AudioLevel.h"
+
+#if (__GNUC__ < 3)
+#include <strstream>
+#define stringstream strstream
+#else
+#include <sstream>
+#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 << " <instrument id=\"" << m_id;
+ instrument << "\" channel=\"" << (int)m_channel;
+ instrument << "\" type=\"";
+
+ if (m_type == Midi)
+ {
+ instrument << "midi\">" << std::endl;
+
+ if (m_sendBankSelect)
+ {
+ instrument << " <bank percussion=\""
+ << (isPercussion() ? "true" : "false") << "\" msb=\""
+ << (int)getMSB();
+ instrument << "\" lsb=\"" << (int)getLSB() << "\"/>" << std::endl;
+ }
+
+ if (m_sendProgramChange)
+ {
+ instrument << " <program id=\""
+ << (int)getProgramChange() << "\"/>"
+ << std::endl;
+ }
+
+ instrument << " <pan value=\""
+ << (int)m_pan << "\"/>" << std::endl;
+
+ instrument << " <volume value=\""
+ << (int)m_volume << "\"/>" << std::endl;
+
+ for (StaticControllerConstIterator it = m_staticControllers.begin();
+ it != m_staticControllers.end(); ++it)
+ {
+ instrument << " <controlchange type=\"" << int(it->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 << " <pan value=\""
+ << (int)m_pan << "\"/>" << std::endl;
+
+ instrument << " <level value=\""
+ << m_level << "\"/>" << std::endl;
+
+ instrument << " <recordLevel value=\""
+ << m_recordLevel << "\"/>" << std::endl;
+
+ bool aibuss;
+ int channel;
+ int ai = getAudioInput(aibuss, channel);
+
+ instrument << " <audioInput value=\""
+ << ai << "\" type=\""
+ << (aibuss ? "buss" : "record")
+ << "\" channel=\"" << channel
+ << "\"/>" << std::endl;
+
+ instrument << " <audioOutput value=\""
+ << m_audioOutput << "\"/>" << std::endl;
+
+ PluginInstanceIterator it = m_audioPlugins.begin();
+ for (; it != m_audioPlugins.end(); it++)
+ {
+ instrument << (*it)->toXmlString();
+ }
+ }
+
+ instrument << " </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<MidiDevice*>(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<MidiByte, MidiByte>(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("<no controller of that value>");
+}
+
+const MidiKeyMapping *
+Instrument::getKeyMapping() const
+{
+ MidiDevice *md = dynamic_cast<MidiDevice*>(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 << " <buss id=\"" << m_id << "\">" << std::endl;
+ buss << " <pan value=\"" << (int)m_pan << "\"/>" << std::endl;
+ buss << " <level value=\"" << m_level << "\"/>" << std::endl;
+
+ PluginInstanceIterator it = m_audioPlugins.begin();
+ for (; it != m_audioPlugins.end(); it++) {
+ buss << (*it)->toXmlString();
+ }
+
+ buss << " </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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <bownie@bownie.com>
+
+ 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 <string>
+#include <vector>
+
+#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<AudioPluginInstance*>::iterator PluginInstanceIterator;
+
+typedef std::vector<std::pair<MidiByte, MidiByte> > StaticControllers;
+typedef std::vector<std::pair<MidiByte, MidiByte> >::iterator StaticControllerIterator;
+typedef std::vector<std::pair<MidiByte, MidiByte> >::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<AudioPluginInstance*> 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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <bownie@bownie.com>
+
+ 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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <bownie@bownie.com>
+
+ 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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <bownie@bownie.com>
+
+ 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 <iostream>
+#include <cmath>
+#include <cstdio> // for sprintf
+#include <ctime>
+
+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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <bownie@bownie.com>
+
+ 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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <bownie@bownie.com>
+
+ 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 <strstream>
+#define stringstream strstream
+#else
+#include <sstream>
+#endif
+
+namespace Rosegarden
+{
+
+int Marker::m_sequence = 0;
+
+std::string
+Marker::toXmlString()
+{
+ std::stringstream marker;
+
+ marker << " <marker time=\"" << m_time
+ << "\" name=\"" << encode(m_name)
+ << "\" description=\"" << encode(m_description)
+ << "\"/>" << 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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <bownie@bownie.com>
+
+ 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 <string>
+
+#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("<unnamed>")),
+ m_description(std::string("<none>")) { 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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <bownie@bownie.com>
+
+ 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 <cstdio>
+#include <cstdlib>
+#include <iostream>
+#include <set>
+
+#if (__GNUC__ < 3)
+#include <strstream>
+#define stringstream strstream
+#else
+#include <sstream>
+#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<std::string, std::string>("<none>", "<none>"))
+{
+ 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<std::string, std::string>("<none>", "<none>"))
+{
+ 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, "<none>", "0", "127", "64", "10", "2", "0" },
+ { "Chorus", Rosegarden::Controller::EventType, "<none>", "0", "127", "0", "93", "3", "1" },
+ { "Volume", Rosegarden::Controller::EventType, "<none>", "0", "127", "0", "7", "1", "2" },
+ { "Reverb", Rosegarden::Controller::EventType, "<none>", "0", "127", "0", "91", "3", "3" },
+ { "Sustain", Rosegarden::Controller::EventType, "<none>", "0", "127", "0", "64", "4", "-1" },
+ { "Expression", Rosegarden::Controller::EventType, "<none>", "0", "127", "100", "11", "2", "-1" },
+ { "Modulation", Rosegarden::Controller::EventType, "<none>", "0", "127", "0", "1", "4", "-1" },
+ { "PitchBend", Rosegarden::PitchBend::EventType, "<none>", "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<MidiByte> 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<MidiByte>::iterator i = msbs.begin(); i != msbs.end(); ++i) {
+ v.push_back(*i);
+ }
+
+ return v;
+}
+
+MidiByteList
+MidiDevice::getDistinctLSBs(bool percussion, int msb) const
+{
+ std::set<MidiByte> 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<MidiByte>::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 << " <device id=\"" << m_id
+ << "\" name=\"" << m_name
+ << "\" direction=\"" << (m_direction == Play ?
+ "play" : "record")
+ << "\" variation=\"" << (m_variationType == VariationFromLSB ?
+ "LSB" :
+ m_variationType == VariationFromMSB ?
+ "MSB" : "")
+ << "\" connection=\"" << encode(m_connection)
+ << "\" type=\"midi\">" << std::endl << std::endl;
+
+ midiDevice << " <librarian name=\"" << encode(m_librarian.first)
+ << "\" email=\"" << encode(m_librarian.second)
+ << "\"/>" << std::endl;
+
+ if (m_metronome)
+ {
+ // Write out the metronome - watch the MidiBytes
+ // when using the stringstream
+ //
+ midiDevice << " <metronome "
+ << "instrument=\"" << m_metronome->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 << " <bank "
+ << "name=\"" << encode(it->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 << " <program "
+ << "id=\"" << (int)pt->getProgram() << "\" "
+ << "name=\"" << encode(pt->getName()) << "\" ";
+ if (!pt->getKeyMapping().empty()) {
+ midiDevice << "keymapping=\""
+ << encode(pt->getKeyMapping()) << "\" ";
+ }
+ midiDevice << "/>" << std::endl;
+ }
+ }
+
+ midiDevice << " </bank>" << std::endl << std::endl;
+ }
+
+ // Now controllers (before Instruments, which can depend on
+ // Controller colours)
+ //
+ midiDevice << " <controls>" << std::endl;
+ ControlList::iterator cIt;
+ for (cIt = m_controlList.begin(); cIt != m_controlList.end() ; ++cIt)
+ midiDevice << cIt->toXmlString();
+ midiDevice << " </controls>" << 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 << " <keymapping "
+ << "name=\"" << encode(kit->getName()) << "\">\n";
+
+ for (MidiKeyMapping::KeyNameMap::const_iterator nmi =
+ kit->getMap().begin(); nmi != kit->getMap().end(); ++nmi) {
+ midiDevice << " <key number=\"" << (int)nmi->first
+ << "\" name=\"" << encode(nmi->second) << "\"/>\n";
+ }
+
+ midiDevice << " </keymapping>\n";
+ }
+
+#if (__GNUC__ < 3)
+ midiDevice << " </device>" << std::endl << std::ends;
+#else
+ midiDevice << " </device>" << 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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <bownie@bownie.com>
+
+ 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 <string>
+#include <vector>
+
+#include "Device.h"
+#include "Instrument.h"
+#include "MidiProgram.h"
+#include "ControlParameter.h"
+#include "Controllable.h"
+
+namespace Rosegarden
+{
+
+typedef std::vector<std::string> StringList;
+typedef std::vector<MidiByte> 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<std::string, std::string> getLibrarian() const
+ { return m_librarian; }
+
+ // Set Librarian details
+ //
+ void setLibrarian(const std::string &name, const std::string &email)
+ { m_librarian = std::pair<std::string, std::string>(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<std::string, std::string> 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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <bownie@bownie.com>
+
+ 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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <bownie@bownie.com>
+
+ 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 <string>
+#include <vector>
+#include <map>
+
+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<MidiBank> 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<MidiProgram> ProgramList;
+
+class MidiKeyMapping
+{
+public:
+ typedef std::map<MidiByte, std::string> 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<MidiKeyMapping> 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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <bownie@bownie.com>
+
+ 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<Int>(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<Int>(MSB, (long)m_msb);
+ e->set<Int>(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<Int>(NUMBER, (long)m_number);
+ e->set<Int>(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<Int>(PITCH, (long)m_pitch);
+ e->set<Int>(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<Int>(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<Int>(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<String>(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<String>(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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <bownie@bownie.com>
+
+ 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 <list>
+
+#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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <bownie@bownie.com>
+
+ 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 <iostream>
+#include <cmath>
+#include <cstdio> // for sprintf
+#include <ctime>
+
+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<int> &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<Int>(m_provisionalAbsTime, t);
+ } else {
+ e->setMaybe<Int>(m_provisionalDuration, t);
+ }
+}
+
+timeT
+NotationQuantizer::Impl::getProvisional(Event *e, ValueType v) const
+{
+ timeT t;
+ if (v == AbsoluteTimeValue) {
+ t = e->getAbsoluteTime();
+ e->get<Int>(m_provisionalAbsTime, t);
+ } else {
+ t = e->getDuration();
+ e->get<Int>(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<Int>(m_provisionalNoteType, noteType);
+
+ int maxDepth = 8 - noteType;
+ if (maxDepth < 4) maxDepth = 4;
+ std::vector<int> 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<Int>(m_provisionalBase, bestBase);
+ (*i)->setMaybe<Int>(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<int> 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<timeT, timeT> bases;
+ for (int depth = 0; depth < maxDepth; ++depth) {
+ if (base >= ud) {
+ bases = std::pair<timeT, timeT>(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<Int>(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<int> &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<Int>(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<Event *> candidates;
+ int count = 0;
+
+ while (s->isBeforeEndMarker(j) &&
+ ((*j)->isa(Note::EventRestType) ||
+ ((*j)->get<Int>(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<Int>(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<int, bool> multiples;
+
+ for (std::vector<Event *>::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<String>(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<Int>(BEAMED_GROUP_ID, groupId);
+ (*ei)->set<Int>(BEAMED_GROUP_TUPLET_BASE, base/2); //!!! wrong if tuplet count != 3
+ (*ei)->set<Int>(BEAMED_GROUP_TUPLED_COUNT, 2); //!!! as above
+ (*ei)->set<Int>(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<String>(BEAMED_GROUP_TYPE, GROUP_TYPE_TUPLED);
+ rest->set<Int>(BEAMED_GROUP_ID, groupId);
+ rest->set<Int>(BEAMED_GROUP_TUPLET_BASE, base/2); //!!! wrong if tuplet count != 3
+ rest->set<Int>(BEAMED_GROUP_TUPLED_COUNT, 2); //!!! as above
+ rest->set<Int>(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<Int>(m_provisionalScore, score)) return false;
+
+ timeT t = m_q->getFromSource(*i, AbsoluteTimeValue);
+ timeT d = getProvisional(*i, DurationValue);
+ int noteType = (*i)->get<Int>(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<Int>(m_provisionalAbsTime);
+ timeT t1 = (*i)->get<Int>(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<int> 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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <bownie@bownie.com>
+
+ 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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <bownie@bownie.com>
+
+ 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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <bownie@bownie.com>
+
+ 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 <cstdio> // needed for sprintf()
+#include "NotationRules.h"
+#include "NotationTypes.h"
+#include "BaseProperties.h"
+#include <iostream>
+#include <cstdlib> // for atoi
+#include <limits.h> // for SHRT_MIN
+#include <cassert>
+
+#if (__GNUC__ < 3)
+#include <strstream>
+#else
+#include <sstream>
+#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<Int>(BaseProperties::MARK_COUNT, markCount);
+ return markCount;
+ }
+
+ std::vector<Mark> getMarks(const Event &e) {
+
+ std::vector<Mark> marks;
+
+ long markCount = 0;
+ e.get<Int>(BaseProperties::MARK_COUNT, markCount);
+ if (markCount == 0) return marks;
+
+ for (long j = 0; j < markCount; ++j) {
+
+ Mark mark(Marks::NoMark);
+ (void)e.get<String>(BaseProperties::getMarkPropertyName(j), mark);
+
+ marks.push_back(mark);
+ }
+
+ return marks;
+ }
+
+ Mark getFingeringMark(const Event &e) {
+
+ long markCount = 0;
+ e.get<Int>(BaseProperties::MARK_COUNT, markCount);
+ if (markCount == 0) return NoMark;
+
+ for (long j = 0; j < markCount; ++j) {
+
+ Mark mark(Marks::NoMark);
+ (void)e.get<String>(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<Int>(BaseProperties::MARK_COUNT, markCount);
+ e.set<Int>(BaseProperties::MARK_COUNT, markCount + 1);
+
+ PropertyName markProperty = BaseProperties::getMarkPropertyName(markCount);
+ e.set<String>(markProperty, mark);
+ }
+
+ bool removeMark(Event &e, const Mark &mark) {
+
+ long markCount = 0;
+ e.get<Int>(BaseProperties::MARK_COUNT, markCount);
+
+ for (long j = 0; j < markCount; ++j) {
+ PropertyName pn(BaseProperties::getMarkPropertyName(j));
+ std::string m;
+ if (e.get<String>(pn, m) && m == mark) {
+ e.unset(pn);
+ while (j < markCount - 1) {
+ PropertyName npn(BaseProperties::getMarkPropertyName(j+1));
+ if (e.get<String>(npn, m)) {
+ e.set<String>( pn, m);
+ }
+ pn = npn;
+ ++j;
+ }
+ e.set<Int>(BaseProperties::MARK_COUNT, markCount - 1);
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ bool hasMark(const Event &e, const Mark &mark) {
+ long markCount = 0;
+ e.get<Int>(BaseProperties::MARK_COUNT, markCount);
+
+ for (long j = 0; j < markCount; ++j) {
+ std::string m;
+ if (e.get<String>(BaseProperties::getMarkPropertyName(j), m) && m == mark) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ std::vector<Mark> 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<Mark> 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<String>(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<Int>(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<String>(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<String>(ClefPropertyName, m_clef);
+ e->set<Int>(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<String>(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<String>(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<int>(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<int> Key::getAccidentalHeights(const Clef &clef) const
+{
+ // staff positions of accidentals
+ checkAccidentalHeights();
+ vector<int> 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<int>;
+
+ 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<String>(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<String>(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<Int>(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<String>(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<Int>(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<String>(TextPropertyName, m_text);
+ e.get<String>(TextTypePropertyName, m_type);
+ e.get<Int>(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<String>(TextTypePropertyName) == type);
+}
+
+std::vector<std::string>
+Text::getUserStyles()
+{
+ std::vector<std::string> 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<std::string>
+Text::getLilyPondDirectives()
+{
+ std::vector<std::string> 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<String>(TextPropertyName, m_text);
+ e->set<String>(TextTypePropertyName, m_type);
+ if (m_type == Lyric) e->set<Int>(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<Int>(BaseProperties::PITCH, m_pitch);
+ e->set<String>(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 <dmmcintyr@users.sourceforge.net>
+ * 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<Int>(BaseProperties::PITCH);
+ e.get<String>(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<Int>(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<Int>(NumeratorPropertyName);
+ }
+
+ if (e.has(DenominatorPropertyName)) {
+ m_denominator = e.get<Int>(DenominatorPropertyName);
+ }
+
+ m_common = false;
+ e.get<Bool>(ShowAsCommonTimePropertyName, m_common);
+
+ m_hidden = false;
+ e.get<Bool>(IsHiddenPropertyName, m_hidden);
+
+ m_hiddenBars = false;
+ e.get<Bool>(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<Int>(NumeratorPropertyName, m_numerator);
+ e->set<Int>(DenominatorPropertyName, m_denominator);
+ e->set<Bool>(ShowAsCommonTimePropertyName, m_common);
+ e->set<Bool>(IsHiddenPropertyName, m_hidden);
+ e->set<Bool>(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<int> &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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <bownie@bownie.com>
+
+ 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 <list>
+#include <map>
+
+#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<int> 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<Accidental> 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<Mark> 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<Mark> 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<Clef> 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<int> 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<Key> 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<int> *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<std::string, KeyDetails> 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<std::string> getUserStyles();
+
+ /**
+ * Return a list of available special LilyPond directives
+ */
+ static std::vector<std::string> 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<int> &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<int, AccidentalRec> 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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <bownie@bownie.com>
+
+ 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 <iostream>
+#include "Profiler.h"
+
+#include <vector>
+#include <algorithm>
+
+//#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<const char *> 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<const char *>::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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <bownie@bownie.com>
+
+ 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 <ctime>
+#include <sys/time.h>
+#include <map>
+
+#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<clock_t, RealTime> TimePair;
+ typedef std::pair<int, TimePair> ProfilePair;
+ typedef std::map<const char *, ProfilePair> ProfileMap;
+ typedef std::map<const char *, TimePair> 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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <bownie@bownie.com>
+
+ 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 <cstdio>
+#include <cstdlib>
+#include <string>
+
+namespace Rosegarden
+{
+using std::string;
+
+string
+PropertyDefn<UInt>::typeName()
+{
+ return "UInt";
+}
+
+PropertyDefn<UInt>::basic_type
+PropertyDefn<UInt>::parse(string s)
+{
+ return atoi(s.c_str());
+}
+
+string
+PropertyDefn<UInt>::unparse(PropertyDefn<UInt>::basic_type i)
+{
+ static char buffer[20]; sprintf(buffer, "%ld", i);
+ return buffer;
+}
+
+
+string
+PropertyDefn<Int>::typeName()
+{
+ return "Int";
+}
+
+PropertyDefn<Int>::basic_type
+PropertyDefn<Int>::parse(string s)
+{
+ return atoi(s.c_str());
+}
+
+string
+PropertyDefn<Int>::unparse(PropertyDefn<Int>::basic_type i)
+{
+ static char buffer[20]; sprintf(buffer, "%ld", i);
+ return buffer;
+}
+
+string
+PropertyDefn<String>::typeName()
+{
+ return "String";
+}
+
+PropertyDefn<String>::basic_type
+PropertyDefn<String>::parse(string s)
+{
+ return s;
+}
+
+string
+PropertyDefn<String>::unparse(PropertyDefn<String>::basic_type i)
+{
+ return i;
+}
+
+string
+PropertyDefn<Bool>::typeName()
+{
+ return "Bool";
+}
+
+PropertyDefn<Bool>::basic_type
+PropertyDefn<Bool>::parse(string s)
+{
+ return s == "true";
+}
+
+string
+PropertyDefn<Bool>::unparse(PropertyDefn<Bool>::basic_type i)
+{
+ return (i ? "true" : "false");
+}
+
+string
+PropertyDefn<RealTimeT>::typeName()
+{
+ return "RealTimeT";
+}
+
+PropertyDefn<RealTimeT>::basic_type
+PropertyDefn<RealTimeT>::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<RealTimeT>::unparse(PropertyDefn<RealTimeT>::basic_type i)
+{
+ static char buffer[256]; sprintf(buffer, "%d/%d", i.sec, i.nsec);
+ return buffer;
+}
+
+PropertyStoreBase::~PropertyStoreBase()
+{
+}
+
+template <>
+size_t
+PropertyStore<UInt>::getStorageSize() const
+{
+ return sizeof(*this);
+}
+
+template <>
+size_t
+PropertyStore<Int>::getStorageSize() const
+{
+ return sizeof(*this);
+}
+
+template <>
+size_t
+PropertyStore<String>::getStorageSize() const
+{
+ return sizeof(*this) + m_data.size();
+}
+
+template <>
+size_t
+PropertyStore<Bool>::getStorageSize() const
+{
+ return sizeof(*this);
+}
+
+template <>
+size_t
+PropertyStore<RealTimeT>::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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <bownie@bownie.com>
+
+ 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 <string>
+
+#include "RealTime.h"
+
+namespace Rosegarden
+{
+
+enum PropertyType { Int, String, Bool, RealTimeT, UInt };
+
+template <PropertyType P>
+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 <PropertyType P>
+typename PropertyDefn<P>::basic_type
+PropertyDefn<P>::parse(std::string)
+{
+ throw(0);
+ return "";
+}
+
+template <PropertyType P>
+std::string
+PropertyDefn<P>::unparse(PropertyDefn<P>::basic_type)
+{
+ throw(0);
+ return "";
+}
+
+
+template <>
+class PropertyDefn<Int>
+{
+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<String>
+{
+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<Bool>
+{
+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<RealTimeT>
+{
+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<UInt>
+{
+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 <PropertyType P>
+class PropertyStore : public PropertyStoreBase
+{
+public:
+ PropertyStore(typename PropertyDefn<P>::basic_type d) :
+ m_data(d) { }
+ PropertyStore(const PropertyStore<P> &p) :
+ PropertyStoreBase(p), m_data(p.m_data) { }
+ PropertyStore &operator=(const PropertyStore<P> &p);
+
+ virtual PropertyType getType() const;
+ virtual std::string getTypeName() const;
+
+ virtual PropertyStoreBase* clone();
+
+ virtual std::string unparse() const;
+
+ typename PropertyDefn<P>::basic_type getData() { return m_data; }
+ void setData(typename PropertyDefn<P>::basic_type data) { m_data = data; }
+
+ virtual size_t getStorageSize() const;
+
+#ifndef NDEBUG
+ void dump(std::ostream&) const;
+#endif
+
+private:
+ typename PropertyDefn<P>::basic_type m_data;
+};
+
+template <PropertyType P>
+PropertyStore<P>&
+PropertyStore<P>::operator=(const PropertyStore<P> &p) {
+ if (this != &p) {
+ m_data = p.m_data;
+ }
+ return *this;
+}
+
+template <PropertyType P>
+PropertyType
+PropertyStore<P>::getType() const
+{
+ return P;
+}
+
+template <PropertyType P>
+std::string
+PropertyStore<P>::getTypeName() const
+{
+ return PropertyDefn<P>::typeName();
+}
+
+template <PropertyType P>
+PropertyStoreBase*
+PropertyStore<P>::clone()
+{
+ return new PropertyStore<P>(*this);
+}
+
+template <PropertyType P>
+std::string
+PropertyStore<P>::unparse() const
+{
+ return PropertyDefn<P>::unparse(m_data);
+}
+
+#ifndef NDEBUG
+template <PropertyType P>
+void
+PropertyStore<P>::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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <bownie@bownie.com>
+
+ 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 <cstdio>
+#include <iostream>
+#include <string>
+#include "PropertyMap.h"
+#include "XmlExportable.h"
+
+namespace Rosegarden
+{
+using std::string;
+
+#ifdef PROPERTY_MAP_IS_HASH_MAP
+PropertyMap::PropertyMap() :
+ __HASH_NS::hash_map<PropertyName,
+ PropertyStoreBase *,
+ PropertyNameHash,
+ PropertyNamesEqual>(50, PropertyNameHash())
+{
+ // nothing
+}
+#endif
+
+PropertyMap::PropertyMap(const PropertyMap &pm) :
+
+#ifdef PROPERTY_MAP_IS_HASH_MAP
+
+ __HASH_NS::hash_map<PropertyName,
+ PropertyStoreBase *,
+ PropertyNameHash,
+ PropertyNamesEqual>(50, PropertyNameHash())
+#else
+
+ std::map<PropertyName, PropertyStoreBase *>()
+
+#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 +=
+ "<property name=\"" + XmlExportable::encode(i->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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <bownie@bownie.com>
+
+ 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 <map>
+
+namespace Rosegarden {
+
+class PropertyMap : public std::map<PropertyName, PropertyStoreBase *>
+{
+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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <bownie@bownie.com>
+
+ 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 <iostream>
+#include <string>
+
+#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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <bownie@bownie.com>
+
+ 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 <string>
+#include <map>
+#include <iostream>
+
+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<const char *> _H;
+ size_t operator() (const PropertyName &s) const {
+ return _H(s.c_str());
+ }
+ };
+
+ std::hash<const char *> 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<std::string, int> intern_map;
+ typedef intern_map::value_type intern_pair;
+
+ typedef std::map<int, std::string> 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<size_t>(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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <bownie@bownie.com>
+
+ 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 <iostream>
+#include <cmath>
+#include <cstdio> // for sprintf
+#include <ctime>
+
+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<Int>(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<Int>(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<Int>(m_sourceProperties[v], t);
+ return t;
+ }
+
+ e->get<Int>(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<Int>(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<Int>(m_sourceProperties[AbsoluteTimeValue], st);
+ haveSd = (*i)->get<Int>(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<timeT, timeT>
+ (normalizeStart, normalizeEnd);
+ }
+
+ if (haveSt) e->setMaybe<Int>(m_sourceProperties[AbsoluteTimeValue],st);
+ if (haveSd) e->setMaybe<Int>(m_sourceProperties[DurationValue], sd);
+
+ if (m_target != RawEventData && m_target != NotationPrefix) {
+ e->setMaybe<Int>(m_targetProperties[AbsoluteTimeValue], absTime);
+ e->setMaybe<Int>(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<timeT, timeT>(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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <bownie@bownie.com>
+
+ 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 <string>
+
+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<timeT, timeT> 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<Event *> 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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <bownie@bownie.com>
+
+ 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 <iostream>
+
+#if (__GNUC__ < 3)
+#include <strstream>
+#define stringstream strstream
+#else
+#include <sstream>
+#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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <bownie@bownie.com>
+
+ 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 <iostream>
+#include <string>
+
+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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <bownie@bownie.com>
+
+ 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 RS>
+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<RS> m_refreshStatuses;
+};
+
+template<class RS>
+unsigned int RefreshStatusArray<RS>::getNewRefreshStatusId()
+{
+ m_refreshStatuses.push_back(RS());
+ unsigned int res = m_refreshStatuses.size() - 1;
+ return res;
+}
+
+void breakpoint();
+
+template<class RS>
+void RefreshStatusArray<RS>::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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <bownie@bownie.com>
+
+ 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 <cmath>
+#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<timeT, timeT> 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<timeT, timeT> 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<timeT, timeT> 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<timeT, timeT> 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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <bownie@bownie.com>
+
+ 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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <bownie@bownie.com>
+
+ 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 <map>
+
+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<ScriptInterface::EventId, Event *> m_events;
+};
+
+Event *
+ScriptRep::getEvent(ScriptInterface::EventId id)
+{
+ return m_events[id];
+}
+
+class ScriptInterface::ScriptContainer :
+ public std::map<ScriptId, ScriptRep *> { };
+
+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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <bownie@bownie.com>
+
+ 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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <bownie@bownie.com>
+
+ 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 <iostream>
+#include <algorithm>
+#include <iterator>
+#include <cstdio>
+#include <typeinfo>
+
+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<Event*, Event::EventCmp>(),
+ 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<Event*, Event::EventCmp>(),
+ 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<Event *> 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<Event*, Event::EventCmp>::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<Event*, Event::EventCmp>::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<Event*, Event::EventCmp>::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<iterator, iterator> 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<std::pair<timeT, timeT> > 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<timeT, timeT>
+ (lastNoteStarts,
+ thisNoteStarts - lastNoteStarts));
+ }
+ */
+
+ if (thisNoteStarts > lastNoteEnds) {
+ gaps.push_back(std::pair<timeT, timeT>
+ (lastNoteEnds,
+ thisNoteStarts - lastNoteEnds));
+ }
+
+ lastNoteStarts = thisNoteStarts;
+ lastNoteEnds = thisNoteStarts + (*i)->getNotationDuration();
+ }
+
+ if (endTime > lastNoteEnds) {
+ gaps.push_back(std::pair<timeT, timeT>
+ (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<iterator, iterator> 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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <bownie@bownie.com>
+
+ 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 <set>
+#include <list>
+#include <string>
+
+#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<Event*, Event::EventCmp>
+{
+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<EventRuler*> EventRulerList;
+ typedef std::vector<EventRuler*>::iterator EventRulerListIterator;
+ typedef std::vector<EventRuler*>::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<SegmentRefreshStatus> m_refreshStatusArray;
+
+ struct ClefKeyCmp {
+ bool operator()(const Event *e1, const Event *e2) const;
+ };
+ typedef std::multiset<Event*, ClefKeyCmp> ClefKeyList;
+ mutable ClefKeyList *m_clefKeyList;
+
+ // EventRulers currently selected as visible on this segment
+ //
+ EventRulerList m_eventRulerList;
+
+private: // stuff to support SegmentObservers
+
+ typedef std::list<SegmentObserver *> 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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <bownie@bownie.com>
+
+ 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<Int>(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<Int>(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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <bownie@bownie.com>
+
+ 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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <bownie@bownie.com>
+
+ 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 <iostream>
+#include <algorithm>
+#include <iterator>
+#include <list>
+
+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<Int>(BEAMED_GROUP_TUPLED_COUNT);
+ int ucount = (*i)->get<Int>(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<Bool>(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<Int>(NOTE_TYPE, n.getNoteType());
+ (*i)->setMaybe<Int>(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<Int>(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<Int>(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<Int>(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<Int>(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<Bool>(forwards ?
+ BaseProperties::TIED_FORWARD :
+ BaseProperties::TIED_BACKWARD, tied) || !tied) {
+ return end();
+ }
+
+ timeT myTime = note->getAbsoluteTime();
+ timeT myDuration = note->getDuration();
+ int myPitch = note->get<Int>(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<Bool>(forwards ?
+ BaseProperties::TIED_BACKWARD :
+ BaseProperties::TIED_FORWARD, tied) || !tied) {
+ continue;
+ }
+
+ if ((*i)->get<Int>(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<Int>(BEAMED_GROUP_ID, firstGroupId);
+
+ long nextGroupId = -1;
+ iterator ni(to);
+
+ if (segment().isBeforeEndMarker(ni) && segment().isBeforeEndMarker(++ni)) {
+ (*ni)->get<Int>(BEAMED_GROUP_ID, nextGroupId);
+ }
+
+ list<Event *> toInsert;
+ list<iterator> 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<Event *, Event *> 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<Bool>(TIED_BACKWARD, true);
+ eva->set<Bool>(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>::iterator i = toErase.begin();
+ i != toErase.end(); ++i) {
+ segment().erase(*i);
+ }
+
+ from = end();
+ iterator last = end();
+
+ // now insert the new events
+ for (list<Event *>::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<Event *> 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<Bool>(TIED_FORWARD, lastTiedForward);
+
+ e->set<Bool>(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<Event *, Event *> 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<Bool>(TIED_BACKWARD, true);
+ }
+
+ delete e;
+ }
+
+ for (std::vector<Event *>::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<Int>(PITCH, pitch);
+ e->set<String>(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<Int>(BEAMED_GROUP_TUPLED_COUNT) /
+ (*i)->get<Int>(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<Int>(BEAMED_GROUP_TUPLED_COUNT) /
+ (*i)->get<Int>(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<Bool>(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<Bool>(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<String>(BEAMED_GROUP_TYPE);
+ if (type != GROUP_TYPE_TUPLED && !(*i)->isa(Note::EventType)) {
+ if ((*i)->isa(Note::EventRestType)) return;
+ else {
+ ++i;
+ continue;
+ }
+ }
+
+ e->set<Int>(BEAMED_GROUP_ID, (*i)->get<Int>(BEAMED_GROUP_ID));
+ e->set<String>(BEAMED_GROUP_TYPE, type);
+
+ if ((*i)->has(BEAMED_GROUP_TUPLET_BASE)) {
+
+ e->set<Int>(BEAMED_GROUP_TUPLET_BASE,
+ (*i)->get<Int>(BEAMED_GROUP_TUPLET_BASE));
+ e->set<Int>(BEAMED_GROUP_TUPLED_COUNT,
+ (*i)->get<Int>(BEAMED_GROUP_TUPLED_COUNT));
+ e->set<Int>(BEAMED_GROUP_UNTUPLED_COUNT,
+ (*i)->get<Int>(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<Bool>(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<Bool>(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" <<endl;
+
+ int groupId = segment().getNextId();
+ bool beamedSomething = false;
+
+ for (iterator i = from; i != to; ++i) {
+// std::cerr << "looking at " << (*i)->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<String>(BEAMED_GROUP_TYPE) != GROUP_TYPE_BEAMED) {
+ continue;
+ }
+
+ if (!groupGraces) {
+ if ((*i)->has(IS_GRACE_NOTE) &&
+ (*i)->get<Bool>(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" <<std::endl;
+ if (!beamedSomething) continue;
+ iterator j = i;
+ bool somethingLeft = false;
+ while (++j != to) {
+ if ((*j)->getType() == Note::EventType &&
+ (*j)->getNotationAbsoluteTime() > (*i)->getNotationAbsoluteTime() &&
+ (*j)->getNotationDuration() < Note(Note::Crotchet).getDuration()) {
+ somethingLeft = true;
+ break;
+ }
+ }
+ if (!somethingLeft) continue;
+ }
+
+// std::cerr << "beaming it" <<std::endl;
+ (*i)->set<Int>(BEAMED_GROUP_ID, groupId);
+ (*i)->set<String>(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<Event *> toInsert;
+ list<iterator> 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<Int>(BEAMED_GROUP_ID, groupId);
+ e->set<String>(BEAMED_GROUP_TYPE, GROUP_TYPE_TUPLED);
+
+ e->set<Int>(BEAMED_GROUP_TUPLET_BASE, unit);
+ e->set<Int>(BEAMED_GROUP_TUPLED_COUNT, tupled);
+ e->set<Int>(BEAMED_GROUP_UNTUPLED_COUNT, untupled);
+
+ toInsert.push_back(e);
+ toErase.push_back(i);
+ }
+
+ for (list<iterator>::iterator i = toErase.begin();
+ i != toErase.end(); ++i) {
+ segment().erase(*i);
+ }
+
+ for (list<Event *>::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<timeT, timeT> 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<Int>(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<iterator> erasable;
+ std::vector<Event *> 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<Event *> &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<Event *> &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<Event *, Event *>
+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<Event *, Event *>(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<Bool>(TIED_FORWARD, true);
+ e2->set<Bool>(TIED_BACKWARD, true);
+
+ return std::pair<Event *, Event *>(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"<<std::endl;
+#endif
+
+ // find next event that's either at a different time or (if a
+ // note) has a different duration
+ Segment::iterator k = i;
+ while (segment().isBeforeEndMarker(k)) {
+ if ((*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<Event *, Event *> 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<Bool>(TIED_FORWARD, true);
+ e2->set<Bool>(TIED_BACKWARD, true);
+
+#ifdef DEBUG_DECOUNTERPOINT
+ std::cerr<<"Erasing:"<<std::endl;
+ (*toGo)->dump(std::cerr);
+#endif
+
+ segment().erase(toGo);
+
+#ifdef DEBUG_DECOUNTERPOINT
+ std::cerr<<"Inserting:"<<std::endl;
+ e1->dump(std::cerr);
+#endif
+
+ segment().insert(e1);
+
+#ifdef DEBUG_DECOUNTERPOINT
+ std::cerr<<"Inserting:"<<std::endl;
+ e2->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"<<std::endl;
+#endif
+ ++i;
+ }
+ }
+
+ segment().normalizeRests(startTime, endTime);
+}
+
+
+void
+SegmentNotationHelper::autoSlur(timeT startTime, timeT endTime, bool legatoOnly)
+{
+ iterator from = segment().findTime(startTime);
+ iterator to = segment().findTime(endTime);
+
+ timeT potentialStart = segment().getEndTime();
+ long groupId = -1;
+ timeT prevTime = startTime;
+ int count = 0;
+ bool thisLegato = false, prevLegato = false;
+
+ for (iterator i = from; i != to && segment().isBeforeEndMarker(i); ++i) {
+
+ timeT t = (*i)->getNotationAbsoluteTime();
+
+ long newGroupId = -1;
+ if ((*i)->get<Int>(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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <bownie@bownie.com>
+
+ 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<Event *, Event *> 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 &notationQuantizer();
+
+ /**
+ * 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<Event *>&);
+
+ void reorganizeRests(timeT, timeT, Reorganizer);
+
+ /// for use by normalizeRests
+ void normalizeContiguousRests(timeT, timeT, std::vector<Event *>&);
+
+ /// for use by collapseRestsAggressively
+ void mergeContiguousRests(timeT, timeT, std::vector<Event *>&);
+};
+
+}
+
+#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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <bownie@bownie.com>
+
+ 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 <iostream>
+
+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<Bool>(TIED_BACKWARD, tiedBack);
+ e->get<Bool>(TIED_FORWARD, tiedForward);
+
+ timeT d = e->getNotationDuration();
+ timeT t = e->getNotationAbsoluteTime();
+
+ if (!e->has(PITCH)) return c;
+ int pitch = e->get<Int>(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<Int>(PITCH) != pitch) continue;
+
+ bool prevTiedForward = false;
+ if (!e->get<Bool>(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<Int>(PITCH) != pitch) continue;
+
+ if (!e->get<Bool>(TIED_BACKWARD, tiedBack) ||
+ !tiedBack) break;
+
+ d += e->getNotationDuration();
+ c.push_back(j);
+ valid = true;
+
+ if (!e->get<Bool>(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<Bool>(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<Bool>(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<Bool>(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<Bool>(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<Bool>(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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <bownie@bownie.com>
+
+ 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<iterator> 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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <bownie@bownie.com>
+
+ 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<eventcontainer::iterator, eventcontainer::iterator>
+ 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<eventcontainer::const_iterator, eventcontainer::const_iterator>
+ 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<timeT, TimeSignature> 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<timeT, tempoT> change = composition.getTempoChange(i);
+
+ if (change.first < endTime) {
+ if (change.first < beginTime) {
+ if (includeOpeningTempo) {
+ change.first = beginTime;
+ } else {
+ continue;
+ }
+ }
+ std::pair<bool, tempoT> 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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <bownie@bownie.com>
+
+ 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 <set>
+#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<Event*, Event::EventCmp> 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<std::pair<Segment::iterator,
+ Segment::iterator> > 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<std::pair<timeT, timeT> > 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<Segment *>
+{
+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<timeT, TimeSignature> 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<tempoT, tempoT> tempochange;
+ typedef std::multimap<timeT, tempochange> 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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <bownie@bownie.com>
+
+ 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<Event, Segment>::getAsEvent(const Segment::iterator &i)
+{
+ return *i;
+}
+
+template <>
+Event *
+AbstractSet<Event, CompositionTimeSliceAdapter>::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<Int>(name);
+}
+
+extern bool
+get__Bool(Event *e, const PropertyName &name)
+{
+ return e->get<Bool>(name);
+}
+
+extern std::string
+get__String(Event *e, const PropertyName &name)
+{
+ return e->get<String>(name);
+}
+
+extern bool
+get__Int(Event *e, const PropertyName &name, long &ref)
+{
+ return e->get<Int>(name, ref);
+}
+
+extern bool
+get__Bool(Event *e, const PropertyName &name, bool &ref)
+{
+ return e->get<Bool>(name, ref);
+}
+
+extern bool
+get__String(Event *e, const PropertyName &name, std::string &ref)
+{
+ return e->get<String>(name, ref);
+}
+
+extern bool
+isPersistent__Bool(Event *e, const PropertyName &name)
+{
+ return e->isPersistent<Bool>(name);
+}
+
+extern void
+setMaybe__Int(Event *e, const PropertyName &name, long value)
+{
+ e->setMaybe<Int>(name, value);
+}
+
+extern void
+setMaybe__String(Event *e, const PropertyName &name, const std::string &value)
+{
+ e->setMaybe<String>(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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <bownie@bownie.com>
+
+ 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 <vector>
+#include <algorithm>
+
+#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 Element, class Container>
+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 Element, class Container, bool singleStaff>
+class GenericChord : public AbstractSet<Element, Container>,
+ public std::vector<typename Container::iterator>
+{
+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<Mark> getMarksForChord() const;
+ virtual std::vector<int> 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 <class Element, class Container>
+AbstractSet<Element, Container>::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 <class Element, class Container>
+void
+AbstractSet<Element, Container>::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 <class Element, class Container>
+bool
+AbstractSet<Element, Container>::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 <class Element, class Container, bool singleStaff>
+GenericChord<Element, Container, singleStaff>::GenericChord(Container &c,
+ Iterator i,
+ const Quantizer *q,
+ PropertyName stemUpProperty) :
+ AbstractSet<Element, Container>(c, i, q),
+ m_stemUpProperty(stemUpProperty),
+ m_time(q->getQuantizedAbsoluteTime(getAsEvent(i))),
+ m_subordering(getAsEvent(i)->getSubOrdering()),
+ m_firstReject(c.end())
+{
+ AbstractSet<Element, Container>::initialise();
+
+ if (std::vector<typename Container::iterator>::size() > 1) {
+ std::stable_sort(std::vector<typename Container::iterator>::begin(),
+ std::vector<typename Container::iterator>::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<Int>(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 <class Element, class Container, bool singleStaff>
+GenericChord<Element, Container, singleStaff>::~GenericChord()
+{
+}
+
+template <class Element, class Container, bool singleStaff>
+bool
+GenericChord<Element, Container, singleStaff>::test(const Iterator &i)
+{
+ Event *e = getAsEvent(i);
+ if (AbstractSet<Element, Container>::
+ 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 <class Element, class Container, bool singleStaff>
+bool
+GenericChord<Element, Container, singleStaff>::sample(const Iterator &i,
+ bool goingForwards)
+{
+ Event *e1 = getAsEvent(i);
+ if (!e1->isa(Note::EventType)) {
+ if (goingForwards && m_firstReject == AbstractSet<Element, Container>::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<Element, Container>::m_baseIterator != AbstractSet<Element, Container>::getContainer().end()) {
+
+ Event *e0 = getAsEvent(AbstractSet<Element, Container>::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<Element, Container>::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<Element, Container>::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<Element, Container>::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<Element, Container>::sample(i, goingForwards);
+ push_back(i);
+ return true;
+}
+
+template <class Element, class Container, bool singleStaff>
+void
+GenericChord<Element, Container, singleStaff>::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 <class Element, class Container, bool singleStaff>
+int
+GenericChord<Element, Container, singleStaff>::getMarkCountForChord() const
+{
+ // need to weed out duplicates
+
+ std::set<Mark> cmarks;
+
+ for (unsigned int i = 0; i < std::vector<typename Container::iterator>::size(); ++i) {
+
+ Event *e = getAsEvent((*this)[i]);
+ std::vector<Mark> marks(Marks::getMarks(*e));
+
+ for (std::vector<Mark>::iterator j = marks.begin(); j != marks.end(); ++j) {
+ cmarks.insert(*j);
+ }
+ }
+
+ return cmarks.size();
+}
+
+
+template <class Element, class Container, bool singleStaff>
+std::vector<Mark>
+GenericChord<Element, Container, singleStaff>::getMarksForChord() const
+{
+ std::vector<Mark> cmarks;
+
+ for (unsigned int i = 0; i < std::vector<typename Container::iterator>::size(); ++i) {
+
+ Event *e = getAsEvent((*this)[i]);
+ std::vector<Mark> marks(Marks::getMarks(*e));
+
+
+ for (std::vector<Mark>::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 <class Element, class Container, bool singleStaff>
+std::vector<int>
+GenericChord<Element, Container, singleStaff>::getPitches() const
+{
+ std::vector<int> pitches;
+
+ for (typename std::vector<typename Container::iterator>::const_iterator
+ i = std::vector<typename Container::iterator>::begin(); i != std::vector<typename Container::iterator>::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 <class Element, class Container, bool singleStaff>
+bool
+GenericChord<Element, Container, singleStaff>::contains(const Iterator &itr) const
+{
+ for (typename std::vector<typename Container::iterator>::const_iterator
+ i = std::vector<typename Container::iterator>::begin();
+ i != std::vector<typename Container::iterator>::end(); ++i) {
+ if (*i == itr) return true;
+ }
+ return false;
+}
+
+
+template <class Element, class Container, bool singleStaff>
+typename GenericChord<Element, Container, singleStaff>::Iterator
+GenericChord<Element, Container, singleStaff>::getPreviousNote()
+{
+ Iterator i(AbstractSet<Element, Container>::getInitialElement());
+ while (1) {
+ if (i == AbstractSet<Element, Container>::getContainer().begin()) return AbstractSet<Element, Container>::getContainer().end();
+ --i;
+ if (getAsEvent(i)->isa(Note::EventType)) {
+ return i;
+ }
+ }
+}
+
+
+template <class Element, class Container, bool singleStaff>
+typename GenericChord<Element, Container, singleStaff>::Iterator
+GenericChord<Element, Container, singleStaff>::getNextNote()
+{
+ Iterator i(AbstractSet<Element, Container>::getFinalElement());
+ while ( i != AbstractSet<Element, Container>::getContainer().end() &&
+ ++i != AbstractSet<Element, Container>::getContainer().end()) {
+ if (getAsEvent(i)->isa(Note::EventType)) {
+ return i;
+ }
+ }
+ return AbstractSet<Element, Container>::getContainer().end();
+}
+
+
+template <class Element, class Container, bool singleStaff>
+typename GenericChord<Element, Container, singleStaff>::Iterator
+GenericChord<Element, Container, singleStaff>::getFirstElementNotInChord()
+{
+ return m_firstReject;
+}
+
+
+template <class Element, class Container, bool singleStaff>
+bool
+GenericChord<Element, Container, singleStaff>::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<Event, Segment, true> Chord;
+typedef GenericChord<Event, CompositionTimeSliceAdapter, false> 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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <bownie@bownie.com>
+
+ 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<timeT, timeT> 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<timeT, timeT> 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<int, int>::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<int, int>::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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <bownie@bownie.com>
+
+ 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 <map>
+
+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<int, int> 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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <bownie@bownie.com>
+
+ 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 <cstdio>
+#include <cstdlib>
+
+
+#if (__GNUC__ < 3)
+#include <strstream>
+#define stringstream strstream
+#else
+#include <sstream>
+#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, "<none>", "0", "127", "64", "10", "2", "0" },
+ { "Chorus", Rosegarden::Controller::EventType, "<none>", "0", "127", "0", "93", "3", "1" },
+ { "Volume", Rosegarden::Controller::EventType, "<none>", "0", "127", "0", "7", "1", "2" },
+ { "Reverb", Rosegarden::Controller::EventType, "<none>", "0", "127", "0", "91", "3", "3" },
+ { "Sustain", Rosegarden::Controller::EventType, "<none>", "0", "127", "0", "64", "4", "-1" },
+ { "Expression", Rosegarden::Controller::EventType, "<none>", "0", "127", "100", "11", "2", "-1" },
+ { "Modulation", Rosegarden::Controller::EventType, "<none>", "0", "127", "0", "1", "4", "-1" },
+ { "PitchBend", Rosegarden::PitchBend::EventType, "<none>", "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 << " <device id=\"" << m_id
+ << "\" name=\"" << m_name
+ << "\" type=\"softsynth\">" << std::endl;
+
+ for (iit = m_instruments.begin(); iit != m_instruments.end(); ++iit)
+ ssiDevice << (*iit)->toXmlString();
+
+ ssiDevice << " </device>"
+#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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <bownie@bownie.com>
+
+ 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 <string>
+
+#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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <bownie@bownie.com>
+
+ 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<ViewElementList::iterator,
+ ViewElementList::iterator>
+ 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 *>(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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <bownie@bownie.com>
+
+ 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 <iostream>
+#include <cassert>
+
+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<StaffObserver*> 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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ 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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <bownie@bownie.com>
+
+ 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 <iostream>
+
+#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 <strstream>
+#define stringstream strstream
+#else
+#include <sstream>
+#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<Device*>::iterator it;
+ MidiDevice *midiDevice;
+
+ // Append lists
+ //
+ for (it = m_devices.begin(); it != m_devices.end(); it++)
+ {
+ midiDevice = dynamic_cast<MidiDevice*>(*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<Device*>::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<Device*>::iterator it;
+ InstrumentList list;
+ InstrumentList::iterator iit;
+ int count = 0;
+
+ for (it = m_devices.begin(); it != m_devices.end(); it++)
+ {
+ MidiDevice *midiDevice = dynamic_cast<MidiDevice*>(*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<Device*>::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<DeviceId>());
+}
+
+std::string
+Studio::toXmlString(const std::vector<DeviceId> &devices)
+{
+ std::stringstream studio;
+
+ studio << "<studio thrufilter=\"" << m_midiThruFilter
+ << "\" recordfilter=\"" << m_midiRecordFilter
+ << "\" audioinputpairs=\"" << m_recordIns.size()
+ << "\" mixerdisplayoptions=\"" << m_mixerDisplayOptions
+ << "\" metronomedevice=\"" << m_metronomeDevice
+ << "\">" << 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<DeviceId>::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 << "</studio>" << endl << std::ends;
+#else
+ studio << "</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<Device*>::iterator it;
+ MidiDevice *midiDevice;
+
+ for (it = m_devices.begin(); it != m_devices.end(); it++)
+ {
+ midiDevice = dynamic_cast<MidiDevice*>(*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<Device*>::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<MidiDevice*>(*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<Device*>::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<MidiDevice*>(*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<AudioDevice*>(*it);
+
+ if (audioDevice)
+ {
+ instList = (*it)->getPresentationInstruments();
+
+ for (iit = instList.begin(); iit != instList.end(); iit++)
+ (*iit)->emptyPlugins();
+ }
+ }
+ }
+}
+
+void
+Studio::clearMidiBanksAndPrograms()
+{
+ MidiDevice *midiDevice;
+ std::vector<Device*>::iterator it;
+
+ for (it = m_devices.begin(); it != m_devices.end(); it++)
+ {
+ midiDevice = dynamic_cast<MidiDevice*>(*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<Device*>::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<Device*>::iterator it;
+ Rosegarden::InstrumentList::iterator iit;
+ Rosegarden::InstrumentList instList;
+
+ for (it = m_devices.begin(); it != m_devices.end(); it++)
+ {
+ midiDevice = dynamic_cast<MidiDevice*>(*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<Device*>::iterator it;
+
+ for (it = m_devices.begin(); it != m_devices.end(); it++)
+ {
+ audioDevice = dynamic_cast<AudioDevice*>(*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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <bownie@bownie.com>
+
+ 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 <string>
+#include <vector>
+
+#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<Instrument *> InstrumentList;
+typedef std::vector<Device*> DeviceList;
+typedef std::vector<Buss *> BussList;
+typedef std::vector<RecordIn *> RecordInList;
+typedef std::vector<Device*>::iterator DeviceListIterator;
+typedef std::vector<Device*>::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<DeviceId> &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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <bownie@bownie.com>
+
+ 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 <iostream>
+#include <cstdio>
+
+#if (__GNUC__ < 3)
+#include <strstream>
+#define stringstream strstream
+#else
+#include <sstream>
+#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 << "<track id=\"" << m_id;
+ track << "\" label=\"" << encode(m_label);
+ track << "\" position=\"" << m_position;
+
+ track << "\" muted=";
+
+ if (m_muted)
+ track << "\"true\"";
+ else
+ track << "\"false\"";
+
+ track << " instrument=\"" << m_instrument << "\"";
+
+ track << " defaultLabel=\"" << m_presetLabel << "\"";
+ track << " defaultClef=\"" << m_clef << "\"";
+ track << " defaultTranspose=\"" << m_transpose << "\"";
+ track << " defaultColour=\"" << m_color << "\"";
+ track << " defaultHighestPlayable=\"" << m_highestPlayable << "\"";
+ track << " defaultLowestPlayable=\"" << m_lowestPlayable << "\"";
+
+ track << " staffSize=\"" << m_staffSize << "\"";
+ track << " staffBracket=\"" << m_staffBracket << "\"";
+
+#if (__GNUC__ < 3)
+ 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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <bownie@bownie.com>
+
+ 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 <string>
+
+#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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <bownie@bownie.com>
+
+ 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<Int>(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<Int>(BaseProperties::PITCH);
+ }
+ }
+
+ if (m_baseVelocity < 0) {
+ if ((*i)->has(BaseProperties::VELOCITY)) {
+ m_baseVelocity = (*i)->get<Int>(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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <bownie@bownie.com>
+
+ 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 <set>
+#include <string>
+
+namespace Rosegarden
+{
+
+typedef unsigned int TriggerSegmentId;
+
+class Segment;
+
+class TriggerSegmentRec
+{
+public:
+ typedef std::set<int> 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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <bownie@bownie.com>
+
+ 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 <iostream>
+#include <cassert>
+
+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<iterator, iterator> 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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <bownie@bownie.com>
+
+ 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 <set>
+#include <list>
+
+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<ViewElement *, ViewElementComparator >
+{
+ typedef std::multiset<ViewElement *, ViewElementComparator > 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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <bownie@bownie.com>
+
+ 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 <iostream>
+#include <cstdlib>
+#include <cstring>
+
+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, "&amp;", 5); buflen += 5; break;
+ case '<' : strncpy(buffer + buflen, "&lt;", 4); buflen += 4; break;
+ case '>' : strncpy(buffer + buflen, "&gt;", 4); buflen += 4; break;
+ case '"' : strncpy(buffer + buflen, "&quot;", 6); buflen += 6; break;
+ case '\'' : strncpy(buffer + buflen, "&apos;", 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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <bownie@bownie.com>
+
+ 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 <string>
+
+// [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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <bownie@bownie.com>
+
+ This file is Copyright 2003
+ Mark Hymers <markh@linuxfromscratch.org>
+
+ 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 <iostream>
+
+
+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:
+<colour red="155" green="50" blue="101"/>
+For a colourmap:
+ <colourmap name="segmentmap">
+ <colourpair id="0" name="" red="210" green="0" blue="100"/>
+ <colourpair id="1" name="TEST1" red="100" green="101" blue="102"/>
+ <colourpair id="2" name="TEST2" red="101" green="102" blue="103"/>
+ <colourpair id="3" name="YES" red="233" green="233" blue="233"/>
+ <colourpair id="4" name="NEW" red="211" green="212" blue="213"/>
+ </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 <alsa/asoundlib.h>
+#include <alsa/seq.h>
+#include <sys/time.h>
+#include <sched.h>
+
+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, &param)) {
+ 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 <alsa/asoundlib.h>
+#include <alsa/seq.h>
+#include <sys/time.h>
+
+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 <target-client-id>\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 <alsa/asoundlib.h>
+#include <alsa/seq.h>
+#include <jack/jack.h>
+#include <sys/time.h>
+
+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 <alsa/asoundlib.h>
+#include <alsa/seq.h>
+#include <sys/time.h>
+
+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 <cstdio>
+
+#include <sys/times.h>
+#include <iostream>
+
+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<FooType T> void func();
+};
+
+template<class T>
+void Foo::func()
+{
+ // dummy code
+ T j = 0;
+ for(T i = 0; i < 100; ++i) j += i;
+}
+
+//template void Foo::func<int>();
+
+template <class R>
+class FooR
+{
+public:
+ void rfunc();
+};
+
+template<class R>
+void FooR<R>::rfunc()
+{
+ // this won't compile
+ Foo* foo;
+ foo->func<A>();
+}
+
+void templateTest()
+{
+ Foo foo;
+ foo.func<A>();
+
+// FooR<float> foor;
+// foor.rfunc();
+}
+
+
+template <class Element, class Container>
+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 <class Element, class Container>
+bool
+GenericSet<Element, Container>::sample(const Iterator &i)
+{
+ Event *e;
+ long p = e->get<Int>("blah");
+}
+
+#endif
+
+int main(int argc, char **argv)
+{
+ typedef std::vector<int> intvect;
+
+// intvect foo;
+
+// GenericSet<int, intvect> genset;
+// genset.sample(foo.begin());
+
+ clock_t st, et;
+ struct tms spare;
+
+#ifdef TEST_WIDE_STRING
+ basic_string<wchar_t> 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<Int>(DURATION_PROPERTY, 20);
+ cout << "duration is " << e.get<Int>(DURATION_PROPERTY) << endl;
+
+ e.set<Bool>(SOME_BOOL_PROPERTY, true);
+ e.set<String>(SOME_STRING_PROPERTY, "foobar");
+
+ cout << "sizeof event after some properties set : "
+ << sizeof e << endl;
+
+ try {
+ cout << "duration is " << e.get<String>(DURATION_PROPERTY) << endl;
+ } catch (Event::BadType bt) {
+ cout << "Correctly caught BadType when trying to get<String> of duration" << endl;
+ }
+
+ string s;
+
+ if (!e.get<String>(DURATION_PROPERTY, s)) {
+ cout << "Correctly got error when trying to get<String> of duration" << endl;
+ } else {
+ cerr << "ERROR AT " << __LINE__ << endl;
+ }
+
+ try {
+ cout << "dummy prop is " << e.get<String>(NONEXISTENT_PROPERTY) << endl;
+ } catch (Event::NoData bt) {
+ cout << "Correctly caught NoData when trying to get non existent property" << endl;
+ }
+
+ if (!e.get<String>(NONEXISTENT_PROPERTY, s)) {
+ cout << "Correctly got error when trying to get<String> of non existent property" << endl;
+ } else {
+ cerr << "ERROR AT " << __LINE__ << endl;
+ }
+
+
+ e.setFromString<Int>(DURATION_PROPERTY, "30");
+ cout << "duration is " << e.get<Int>(DURATION_PROPERTY) << endl;
+
+ e.setFromString<String>(ANNOTATION_PROPERTY, "This is my house");
+ cout << "annotation is " << e.get<String>(ANNOTATION_PROPERTY) << endl;
+
+ long durationVal;
+ if (e.get<Int>(DURATION_PROPERTY, durationVal))
+ cout << "duration is " << durationVal << endl;
+ else
+ cerr << "ERROR AT " << __LINE__ << endl;
+
+ if (e.get<String>(ANNOTATION_PROPERTY, s))
+ cout << "annotation is " << s << endl;
+ else
+ cerr << "ERROR AT " << __LINE__ << endl;
+
+ cout << "\nTesting persistence & setMaybe..." << endl;
+
+ e.setMaybe<Int>(SOME_INT_PROPERTY, 1);
+ if (e.get<Int>(SOME_INT_PROPERTY) == 1) {
+ cout << "a. Correct: 1" << endl;
+ } else {
+ cout << "a. ERROR: " << e.get<Int>(SOME_INT_PROPERTY) << endl;
+ }
+
+ e.set<Int>(SOME_INT_PROPERTY, 2, false);
+ e.setMaybe<Int>(SOME_INT_PROPERTY, 3);
+ if (e.get<Int>(SOME_INT_PROPERTY) == 3) {
+ cout << "b. Correct: 3" << endl;
+ } else {
+ cout << "b. ERROR: " << e.get<Int>(SOME_INT_PROPERTY) << endl;
+ }
+
+ e.set<Int>(SOME_INT_PROPERTY, 4);
+ e.setMaybe<Int>(SOME_INT_PROPERTY, 5);
+ if (e.get<Int>(SOME_INT_PROPERTY) == 4) {
+ cout << "c. Correct: 4" << endl;
+ } else {
+ cout << "c. ERROR: " << e.get<Int>(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<Int>(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<Int>(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<Int>(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<String>(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<String>(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<String>(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<String>(names[i % NAME_COUNT]);
+ (void)e11.set<String>(names[i % NAME_COUNT], "blah");
+ }
+ et = times(&spare);
+ cout << "Event: 1000 copy ctors plus set<String> 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<Int>(names[0], 40);
+ (void)e21.get<Int>(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<Int>("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 <cstdio>
+#include <sys/times.h>
+#include <iostream>
+
+#include <pthread.h>
+#include <unistd.h>
+
+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<Rosegarden::Composition*>(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<Rosegarden::Composition*>(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 <iostream>
+#include <string>
+
+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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "AddDotCommand.h"
+
+#include "base/Event.h"
+#include "base/NotationTypes.h"
+#include "base/Selection.h"
+#include "document/BasicSelectionCommand.h"
+#include <qstring.h>
+
+
+namespace Rosegarden
+{
+
+void
+AddDotCommand::modifySegment()
+{
+ std::vector<Event *> toErase;
+ std::vector<Event *> 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<Event *>::iterator i = toErase.begin(); i != toErase.end(); ++i) {
+ m_selection->getSegment().eraseSingle(*i);
+ }
+
+ for (std::vector<Event *>::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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_ADDDOTCOMMAND_H_
+#define _RG_ADDDOTCOMMAND_H_
+
+#include "document/BasicSelectionCommand.h"
+#include <qstring.h>
+#include <klocale.h>
+
+
+
+
+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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "AddMarkerCommand.h"
+
+#include "base/Composition.h"
+#include "base/Marker.h"
+#include <qstring.h>
+
+
+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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_ADDMARKERCOMMAND_H_
+#define _RG_ADDMARKERCOMMAND_H_
+
+#include <string>
+#include <kcommand.h>
+#include <qstring.h>
+#include "base/Event.h"
+#include <klocale.h>
+
+
+
+
+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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "ChangeVelocityCommand.h"
+
+#include "base/NotationTypes.h"
+#include "base/Selection.h"
+#include "document/BasicSelectionCommand.h"
+#include <qstring.h>
+#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
+ <Int>(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<Int>(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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_CHANGEVELOCITYCOMMAND_H_
+#define _RG_CHANGEVELOCITYCOMMAND_H_
+
+#include "document/BasicSelectionCommand.h"
+#include <qstring.h>
+#include <klocale.h>
+
+
+
+
+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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "ClearTriggersCommand.h"
+
+#include "base/Selection.h"
+#include "document/BasicSelectionCommand.h"
+#include <qstring.h>
+#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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_CLEARTRIGGERSCOMMAND_H_
+#define _RG_CLEARTRIGGERSCOMMAND_H_
+
+#include "document/BasicSelectionCommand.h"
+#include <qstring.h>
+#include <klocale.h>
+
+
+
+
+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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "CollapseNotesCommand.h"
+
+#include "base/Event.h"
+#include "base/SegmentNotationHelper.h"
+#include "base/Selection.h"
+#include "document/BasicSelectionCommand.h"
+#include <qstring.h>
+
+
+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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_ADJUSTMENUCOLLAPSENOTESCOMMAND_H_
+#define _RG_ADJUSTMENUCOLLAPSENOTESCOMMAND_H_
+
+#include "document/BasicSelectionCommand.h"
+#include <qstring.h>
+#include <klocale.h>
+
+
+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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "CopyCommand.h"
+
+#include "misc/Strings.h"
+#include "base/Clipboard.h"
+#include "base/Composition.h"
+#include "base/Selection.h"
+#include <qstring.h>
+
+
+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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_COPYCOMMAND_H_
+#define _RG_COPYCOMMAND_H_
+
+#include <kcommand.h>
+#include <qstring.h>
+#include "base/Event.h"
+#include <klocale.h>
+
+
+
+
+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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "CutAndCloseCommand.h"
+
+#include "base/Clipboard.h"
+#include "base/NotationTypes.h"
+#include "base/Segment.h"
+#include "base/Selection.h"
+#include "CutCommand.h"
+#include <qstring.h>
+#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<Event *> 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<Event *> 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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_CUTANDCLOSECOMMAND_H_
+#define _RG_CUTANDCLOSECOMMAND_H_
+
+#include <kcommand.h>
+#include <qstring.h>
+#include "base/Event.h"
+#include <klocale.h>
+
+
+
+
+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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "CutCommand.h"
+
+#include "base/Clipboard.h"
+#include "base/Selection.h"
+#include "commands/segment/SegmentEraseCommand.h"
+#include "CopyCommand.h"
+#include "EraseCommand.h"
+#include <qstring.h>
+
+
+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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_CUTCOMMAND_H_
+#define _RG_CUTCOMMAND_H_
+
+#include <qstring.h>
+#include <klocale.h>
+#include <kcommand.h>
+
+
+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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "EraseCommand.h"
+
+#include "misc/Debug.h"
+#include "base/NotationTypes.h"
+#include "base/Selection.h"
+#include "document/BasicSelectionCommand.h"
+#include <qstring.h>
+
+
+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<Event *> 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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_ERASECOMMAND_H_
+#define _RG_ERASECOMMAND_H_
+
+#include "document/BasicSelectionCommand.h"
+#include <qstring.h>
+#include "base/Event.h"
+#include <klocale.h>
+
+
+
+
+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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "EventEditCommand.h"
+
+#include "base/Event.h"
+#include "base/Segment.h"
+#include "document/BasicCommand.h"
+#include <qstring.h>
+
+
+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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_EVENTEDITCOMMAND_H_
+#define _RG_EVENTEDITCOMMAND_H_
+
+#include "base/Event.h"
+#include "document/BasicCommand.h"
+#include <qstring.h>
+#include <klocale.h>
+
+
+
+
+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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "EventInsertionCommand.h"
+
+#include <klocale.h>
+#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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "EventQuantizeCommand.h"
+
+#include <klocale.h>
+#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 <kconfig.h>
+#include <qstring.h>
+#include "base/BaseProperties.h"
+#include "gui/application/RosegardenApplication.h"
+#include <kapplication.h>
+
+
+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<NotationQuantizer *>(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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_EVENTQUANTIZECOMMAND_H_
+#define _RG_EVENTQUANTIZECOMMAND_H_
+
+#include "document/BasicCommand.h"
+#include <qobject.h>
+#include <qstring.h>
+#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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "EventUnquantizeCommand.h"
+
+#include <klocale.h>
+#include "base/Quantizer.h"
+#include "base/Segment.h"
+#include "base/Selection.h"
+#include "document/BasicCommand.h"
+#include <qstring.h>
+
+
+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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_EVENTUNQUANTIZECOMMAND_H_
+#define _RG_EVENTUNQUANTIZECOMMAND_H_
+
+#include "document/BasicCommand.h"
+#include <qstring.h>
+#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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "InsertTriggerNoteCommand.h"
+
+#include <klocale.h>
+#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
+ <Int>(PITCH, m_pitch);
+ e->set
+ <Int>(VELOCITY, m_velocity);
+
+ if (m_noteStyle != NoteStyleFactory::DefaultStyle) {
+ e->set
+ <String>(NotationProperties::NOTE_STYLE, m_noteStyle);
+ }
+
+ e->set
+ <Int>(TRIGGER_SEGMENT_ID, m_id);
+ e->set
+ <Bool>(TRIGGER_SEGMENT_RETUNE, m_retune);
+ e->set
+ <String>(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
+ <Bool>(TIED_BACKWARD) &&
+ (*j)->has(PITCH) && ((*j)->get<Int>(PITCH) == m_pitch)) {
+ (*i)->set
+ <Bool>(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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#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 <string>
+#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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "InvertCommand.h"
+
+#include "base/NotationTypes.h"
+#include "base/Selection.h"
+#include "document/BasicSelectionCommand.h"
+#include <qstring.h>
+#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
+ <Int>(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
+ <Int>(PITCH);
+ pitch = lowestPitch + (highestPitch - pitch);
+ pitch += m_semitones;
+ (*i)->set
+ <Int>(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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_INVERTCOMMAND_H_
+#define _RG_INVERTCOMMAND_H_
+
+#include "document/BasicSelectionCommand.h"
+#include <qstring.h>
+#include <klocale.h>
+
+
+
+
+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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "ModifyMarkerCommand.h"
+
+#include "base/Composition.h"
+#include <qstring.h>
+
+
+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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_MODIFYMARKERCOMMAND_H_
+#define _RG_MODIFYMARKERCOMMAND_H_
+
+#include <string>
+#include <kcommand.h>
+#include <qstring.h>
+#include "base/Event.h"
+#include <klocale.h>
+
+
+
+
+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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "MoveAcrossSegmentsCommand.h"
+
+#include <klocale.h>
+#include "base/Clipboard.h"
+#include "base/Segment.h"
+#include "base/Selection.h"
+#include "CutCommand.h"
+#include "PasteEventsCommand.h"
+#include <qstring.h>
+
+
+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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_MOVEACROSSSEGMENTSCOMMAND_H_
+#define _RG_MOVEACROSSSEGMENTSCOMMAND_H_
+
+#include <qstring.h>
+#include "base/Event.h"
+#include <kcommand.h>
+
+
+
+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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "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 <qstring.h>
+
+
+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<Event *> toErase;
+ std::vector<Event *> 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<String>(ACCIDENTAL, explicitAccidental);
+ if (toInsert[j]->get<Int>(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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_MOVECOMMAND_H_
+#define _RG_MOVECOMMAND_H_
+
+#include "document/BasicCommand.h"
+#include <qstring.h>
+#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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "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 <qstring.h>
+#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<long, long> groupIdMap;
+ for (Segment::iterator i = source->begin(); i != source->end(); ++i) {
+ long groupId = -1;
+ if ((*i)->get
+ <Int>(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<Event *> 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
+ <Int>(BEAMED_GROUP_ID, groupIdMap[e->get
+ <Int>(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<Int>(BEAMED_GROUP_ID,
+ groupIdMap[e->get<Int>(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
+ <String>(BEAMED_GROUP_TYPE) == GROUP_TYPE_BEAMED) {
+ e->unset(BEAMED_GROUP_ID);
+ e->unset(BEAMED_GROUP_TYPE);
+ }
+
+ if (e->has(BEAMED_GROUP_ID)) {
+ e->set
+ <Int>(BEAMED_GROUP_ID, groupIdMap[e->get
+ <Int>(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
+ <Int>(BEAMED_GROUP_ID, groupIdMap[e->get
+ <Int>(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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_PASTEEVENTSCOMMAND_H_
+#define _RG_PASTEEVENTSCOMMAND_H_
+
+#include "document/BasicCommand.h"
+#include "base/Selection.h"
+#include <map>
+#include <qstring.h>
+#include "base/Event.h"
+#include <klocale.h>
+
+
+
+
+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<PasteType, QString> 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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "PasteSegmentsCommand.h"
+
+#include "base/Clipboard.h"
+#include "base/Composition.h"
+#include "base/Segment.h"
+#include "base/Track.h"
+#include <qstring.h>
+
+
+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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_PASTESEGMENTSCOMMAND_H_
+#define _RG_PASTESEGMENTSCOMMAND_H_
+
+#include "base/Track.h"
+#include <kcommand.h>
+#include <qstring.h>
+#include <vector>
+#include "base/Event.h"
+#include <klocale.h>
+
+
+
+
+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<Segment *> 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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "RemoveMarkerCommand.h"
+
+#include "base/Composition.h"
+#include "base/Marker.h"
+#include <qstring.h>
+
+
+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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_REMOVEMARKERCOMMAND_H_
+#define _RG_REMOVEMARKERCOMMAND_H_
+
+#include <string>
+#include <kcommand.h>
+#include <qstring.h>
+#include "base/Event.h"
+#include <klocale.h>
+
+
+
+
+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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "RescaleCommand.h"
+
+#include "base/Event.h"
+#include "base/Segment.h"
+#include "base/Selection.h"
+#include "document/BasicCommand.h"
+#include <qstring.h>
+
+
+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<Event *> toErase;
+ std::vector<Event *> 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<Event *>::iterator i = toErase.begin(); i != toErase.end(); ++i) {
+ m_selection->removeEvent(*i); // remove from selection
+ segment.eraseSingle(*i);
+ }
+
+ for (std::vector<Event *>::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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_RESCALECOMMAND_H_
+#define _RG_RESCALECOMMAND_H_
+
+#include "document/BasicCommand.h"
+#include <qstring.h>
+#include "base/Event.h"
+#include <klocale.h>
+
+
+
+
+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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "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 <qstring.h>
+
+
+namespace Rosegarden
+{
+
+void
+RetrogradeCommand::modifySegment()
+{
+ std::vector<Event *> toErase;
+ std::vector<Event *> 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<String>(ACCIDENTAL, explicitAccidental);
+ if (toInsert[j]->get<Int>(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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_RETROGRADECOMMAND_H_
+#define _RG_RETROGRADECOMMAND_H_
+
+#include "document/BasicSelectionCommand.h"
+#include <qstring.h>
+#include <klocale.h>
+
+
+
+
+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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "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 <qstring.h>
+#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
+ <Int>(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
+ <Int>(PITCH);
+ pitch = lowestPitch + (highestPitch - pitch);
+ pitch += m_semitones;
+ (*i)->set
+ <Int>(PITCH, pitch);
+ (*i)->unset(ACCIDENTAL);
+ } catch (...) { }
+ }
+ }
+ std::vector<Event *> toErase;
+ std::vector<Event *> 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<String>(ACCIDENTAL, explicitAccidental);
+ if (toInsert[j]->get<Int>(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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_RETROGRADEINVERTCOMMAND_H_
+#define _RG_RETROGRADEINVERTCOMMAND_H_
+
+#include "document/BasicSelectionCommand.h"
+#include <qstring.h>
+#include <klocale.h>
+
+
+
+
+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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "SelectionPropertyCommand.h"
+
+#include "base/Event.h"
+#include "base/PropertyName.h"
+#include "base/Selection.h"
+#include "document/BasicSelectionCommand.h"
+#include <qstring.h>
+
+
+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
+ <Int>(m_property, m_value1);
+ else if (m_pattern == AlternatingPattern) {
+ if (count % 2 == 0)
+ (*i)->set
+ <Int>(m_property, m_value1);
+ else
+ (*i)->set
+ <Int>(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
+ <Int>(m_property,
+ m_value1 -
+ int(step *
+ ((*i)->getAbsoluteTime() - startTime)));
+ // ringing
+ } else if (m_pattern == RingingPattern) {
+ if (count % 2 == 0)
+ (*i)->set
+ <Int>
+ (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
+ <Int>(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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_SELECTIONPROPERTYCOMMAND_H_
+#define _RG_SELECTIONPROPERTYCOMMAND_H_
+
+#include "base/PropertyName.h"
+#include "document/BasicSelectionCommand.h"
+#include <qstring.h>
+#include <klocale.h>
+
+
+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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "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 <qregexp.h>
+#include <qstring.h>
+#include <qstringlist.h>
+
+
+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<Event *>::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<String>(Text::TextTypePropertyName, textType) &&
+ textType == Text::Lyric) {
+ long verse = 0;
+ (*i)->get<Int>(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<timeT, timeT> 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
+ <Bool>(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<Int>(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<String>(Text::TextTypePropertyName, textType) &&
+ textType == Text::Lyric) {
+ long verse = 0;
+ (*i)->get<Int>(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<Event *>::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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_SETLYRICSCOMMAND_H_
+#define _RG_SETLYRICSCOMMAND_H_
+
+#include <kcommand.h>
+#include <qstring.h>
+#include <vector>
+#include <klocale.h>
+
+
+
+
+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<Event *> 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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "SetNoteTypeCommand.h"
+
+#include "base/Event.h"
+#include "base/NotationTypes.h"
+#include "base/Selection.h"
+#include "document/BasicSelectionCommand.h"
+#include <qstring.h>
+
+
+namespace Rosegarden
+{
+
+void
+SetNoteTypeCommand::modifySegment()
+{
+ std::vector<Event *> toErase;
+ std::vector<Event *> 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<Event *>::iterator i = toErase.begin(); i != toErase.end(); ++i) {
+ m_selection->getSegment().eraseSingle(*i);
+ }
+
+ for (std::vector<Event *>::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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_SETNOTETYPECOMMAND_H_
+#define _RG_SETNOTETYPECOMMAND_H_
+
+#include "base/NotationTypes.h"
+#include "document/BasicSelectionCommand.h"
+#include <qstring.h>
+#include <klocale.h>
+
+
+
+
+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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "SetTriggerCommand.h"
+
+#include "base/NotationTypes.h"
+#include "base/Selection.h"
+#include "base/TriggerSegment.h"
+#include "document/BasicSelectionCommand.h"
+#include "base/BaseProperties.h"
+#include <qstring.h>
+
+
+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
+ <Int>(TRIGGER_SEGMENT_ID, m_triggerSegmentId);
+ (*i)->set
+ <Bool>(TRIGGER_SEGMENT_RETUNE, m_retune);
+ (*i)->set
+ <String>(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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_SETTRIGGERCOMMAND_H_
+#define _RG_SETTRIGGERCOMMAND_H_
+
+#include "base/TriggerSegment.h"
+#include "document/BasicSelectionCommand.h"
+#include <string>
+#include <qstring.h>
+#include <klocale.h>
+
+
+
+
+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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "TransposeCommand.h"
+
+#include <iostream>
+#include "base/NotationTypes.h"
+#include "base/Selection.h"
+#include "document/BasicSelectionCommand.h"
+#include <qstring.h>
+#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<String>(BaseProperties::ACCIDENTAL, newAccidental);
+
+ (*i)->set<Int>(PITCH, newPitch.getPerformancePitch());
+ (*i)->set<String>(ACCIDENTAL, newAccidental);
+ }
+ else
+ {
+ try {
+ long pitch = (*i)->get<Int>(PITCH);
+ pitch += m_semitones;
+ (*i)->set<Int>(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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_TRANSPOSECOMMAND_H_
+#define _RG_TRANSPOSECOMMAND_H_
+
+#include "document/BasicSelectionCommand.h"
+#include <qstring.h>
+#include <klocale.h>
+
+
+
+
+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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "MatrixEraseCommand.h"
+
+#include <klocale.h>
+#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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "MatrixInsertionCommand.h"
+
+#include <klocale.h>
+#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
+ <Int>(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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "MatrixModifyCommand.h"
+
+#include "base/Event.h"
+#include <klocale.h>
+#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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "MatrixPercussionInsertionCommand.h"
+
+#include <klocale.h>
+#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
+ <Int>(VELOCITY, 100);
+ }
+
+ Segment &s = getSegment();
+
+ Segment::iterator i = s.findTime(m_time);
+
+ int pitch = 0;
+ if (m_event->has(PITCH)) {
+ pitch = m_event->get
+ <Int>(PITCH);
+ }
+
+ while (i != s.begin()) {
+
+ --i;
+
+ if ((*i)->getAbsoluteTime() < m_time &&
+ (*i)->isa(Note::EventType)) {
+
+ if ((*i)->has(PITCH) &&
+ (*i)->get
+ <Int>(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<Int>(PITCH);
+ }
+
+ Segment::iterator i = segment.findTime(time);
+ while (i != segment.begin()) {
+ --i;
+
+ if ((*i)->has(PITCH) &&
+ (*i)->get
+ <Int>(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<Int>(PITCH);
+ }
+
+ for (Segment::iterator i = segment.findTime(time);
+ segment.isBeforeEndMarker(i); ++i) {
+
+ if ((*i)->has(PITCH) &&
+ (*i)->get
+ <Int>(PITCH) == pitch) {
+
+ if ((*i)->getAbsoluteTime() > time &&
+ (*i)->isa(Note::EventType)) {
+ endTime = (*i)->getAbsoluteTime();
+ }
+ }
+ }
+
+ Composition *comp = segment.getComposition();
+ std::pair<timeT, timeT> 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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "AddFingeringMarkCommand.h"
+
+#include <klocale.h>
+#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 <qstring.h>
+
+
+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
+ <Event *> 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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_NOTESMENUADDFINGERINGMARKCOMMAND_H_
+#define _RG_NOTESMENUADDFINGERINGMARKCOMMAND_H_
+
+#include "document/BasicSelectionCommand.h"
+#include <string>
+#include <qstring.h>
+
+
+
+
+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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "AddIndicationCommand.h"
+
+#include <klocale.h>
+#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 <qstring.h>
+
+
+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<Int>(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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_NOTESMENUADDINDICATIONCOMMAND_H_
+#define _RG_NOTESMENUADDINDICATIONCOMMAND_H_
+
+#include "document/BasicCommand.h"
+#include <string>
+#include <qstring.h>
+#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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "AddMarkCommand.h"
+
+#include <klocale.h>
+#include "misc/Strings.h"
+#include "base/Selection.h"
+#include "document/BasicSelectionCommand.h"
+#include "base/BaseProperties.h"
+#include <qstring.h>
+
+
+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
+ <Int>(MARK_COUNT, n);
+ (*i)->set
+ <Int>(MARK_COUNT, n + 1);
+ (*i)->set
+ <String>(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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_NOTESMENUADDMARKCOMMAND_H_
+#define _RG_NOTESMENUADDMARKCOMMAND_H_
+
+#include "document/BasicSelectionCommand.h"
+#include <qstring.h>
+
+
+
+
+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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "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
+ <Int>(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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_NOTESMENUADDSLASHESCOMMAND_H_
+#define _RG_NOTESMENUADDSLASHESCOMMAND_H_
+
+#include "document/BasicSelectionCommand.h"
+#include <klocale.h>
+
+
+
+
+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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "AddTextMarkCommand.h"
+
+#include "base/Selection.h"
+#include "document/BasicSelectionCommand.h"
+#include "base/BaseProperties.h"
+#include <qstring.h>
+
+
+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
+ <Int>(MARK_COUNT, n);
+ (*i)->set
+ <Int>(MARK_COUNT, n + 1);
+ (*i)->set
+ <String>(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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_NOTESMENUADDTEXTMARKCOMMAND_H_
+#define _RG_NOTESMENUADDTEXTMARKCOMMAND_H_
+
+#include "document/BasicSelectionCommand.h"
+#include <string>
+#include <qstring.h>
+#include <klocale.h>
+
+
+
+
+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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "AutoBeamCommand.h"
+
+#include "base/Segment.h"
+#include "base/SegmentNotationHelper.h"
+#include "base/Selection.h"
+#include "document/BasicSelectionCommand.h"
+#include "base/BaseProperties.h"
+#include <qstring.h>
+
+
+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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_NOTESMENUAUTOBEAMCOMMAND_H_
+#define _RG_NOTESMENUAUTOBEAMCOMMAND_H_
+
+#include "document/BasicSelectionCommand.h"
+#include <qstring.h>
+#include <klocale.h>
+
+
+
+
+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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "BeamCommand.h"
+
+#include "base/NotationTypes.h"
+#include "base/Selection.h"
+#include "document/BasicSelectionCommand.h"
+#include "base/BaseProperties.h"
+#include <qstring.h>
+
+
+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
+ <Int>(BEAMED_GROUP_ID, id);
+ (*i)->set
+ <String>(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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_NOTESMENUBEAMCOMMAND_H_
+#define _RG_NOTESMENUBEAMCOMMAND_H_
+
+#include "document/BasicSelectionCommand.h"
+#include <qstring.h>
+#include <klocale.h>
+
+
+
+
+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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "BreakCommand.h"
+
+#include "base/Selection.h"
+#include "document/BasicSelectionCommand.h"
+#include "gui/editors/notation/NotationProperties.h"
+#include "base/BaseProperties.h"
+#include <qstring.h>
+
+
+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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_NOTESMENUBREAKCOMMAND_H_
+#define _RG_NOTESMENUBREAKCOMMAND_H_
+
+#include "document/BasicSelectionCommand.h"
+#include <qstring.h>
+#include <klocale.h>
+
+
+
+
+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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "ChangeSlurPositionCommand.h"
+
+#include "base/NotationTypes.h"
+#include "base/Selection.h"
+#include "document/BasicSelectionCommand.h"
+#include "gui/editors/notation/NotationProperties.h"
+#include <qstring.h>
+
+
+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<String>
+ (Indication::IndicationTypePropertyName, indicationType)
+ && (indicationType == Indication::Slur ||
+ indicationType == Indication::PhrasingSlur)) {
+ (*i)->set<Bool>(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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_ADJUSTMENUCHANGESLURPOSITIONCOMMAND_H_
+#define _RG_ADJUSTMENUCHANGESLURPOSITIONCOMMAND_H_
+
+#include "document/BasicSelectionCommand.h"
+#include <qstring.h>
+#include <klocale.h>
+
+
+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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "ChangeStemsCommand.h"
+
+#include "base/NotationTypes.h"
+#include "base/Selection.h"
+#include "document/BasicSelectionCommand.h"
+#include "gui/editors/notation/NotationProperties.h"
+#include <qstring.h>
+
+
+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
+ <Bool>(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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_ADJUSTMENUCHANGESTEMSCOMMAND_H_
+#define _RG_ADJUSTMENUCHANGESTEMSCOMMAND_H_
+
+#include "document/BasicSelectionCommand.h"
+#include <qstring.h>
+#include <klocale.h>
+
+
+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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "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 <qstring.h>
+
+
+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
+ <String>
+ (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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_ADJUSTMENUCHANGESTYLECOMMAND_H_
+#define _RG_ADJUSTMENUCHANGESTYLECOMMAND_H_
+
+#include "document/BasicSelectionCommand.h"
+#include "gui/editors/notation/NoteStyle.h"
+#include <qstring.h>
+#include <klocale.h>
+
+
+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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "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 <qstring.h>
+
+
+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<Bool>(BaseProperties::TIED_FORWARD)) {
+ (*i)->set<Bool>(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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_ADJUSTMENUCHANGETIEPOSITIONCOMMAND_H_
+#define _RG_ADJUSTMENUCHANGETIEPOSITIONCOMMAND_H_
+
+#include "document/BasicSelectionCommand.h"
+#include <qstring.h>
+#include <klocale.h>
+
+
+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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "ClefInsertionCommand.h"
+
+#include <klocale.h>
+#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 <qstring.h>
+
+
+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
+ <Int>(PITCH, pitch)) {
+ pitch += semitones;
+ (*i)->set
+ <Int>(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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_CLEFINSERTIONCOMMAND_H_
+#define _RG_CLEFINSERTIONCOMMAND_H_
+
+#include "base/NotationTypes.h"
+#include "document/BasicCommand.h"
+#include <qstring.h>
+#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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "CollapseRestsCommand.h"
+
+#include "base/Segment.h"
+#include "base/SegmentNotationHelper.h"
+#include "base/Selection.h"
+#include "document/BasicCommand.h"
+#include <qstring.h>
+
+
+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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_ADJUSTMENUCOLLAPSERESTSCOMMAND_H_
+#define _RG_ADJUSTMENUCOLLAPSERESTSCOMMAND_H_
+
+#include "document/BasicCommand.h"
+#include <qstring.h>
+#include "base/Event.h"
+#include <klocale.h>
+
+
+
+
+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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "DeCounterpointCommand.h"
+
+#include "base/Segment.h"
+#include "base/SegmentNotationHelper.h"
+#include "base/Selection.h"
+#include "document/BasicSelectionCommand.h"
+#include <qstring.h>
+
+
+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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_ADJUSTMENUDECOUNTERPOINTCOMMAND_H_
+#define _RG_ADJUSTMENUDECOUNTERPOINTCOMMAND_H_
+
+#include "document/BasicSelectionCommand.h"
+#include <qstring.h>
+#include <klocale.h>
+
+
+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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_ERASEEVENTCOMMAND_H_
+#define _RG_ERASEEVENTCOMMAND_H_
+
+#include "document/BasicCommand.h"
+#include <string>
+#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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "FixNotationQuantizeCommand.h"
+
+#include "base/Event.h"
+#include "base/Quantizer.h"
+#include "base/Segment.h"
+#include "base/Selection.h"
+#include "document/BasicSelectionCommand.h"
+#include <qstring.h>
+
+
+namespace Rosegarden
+{
+
+void
+FixNotationQuantizeCommand::modifySegment()
+{
+ std::vector<Event *> toErase;
+ std::vector<Event *> 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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_ADJUSTMENUFIXNOTATIONQUANTIZECOMMAND_H_
+#define _RG_ADJUSTMENUFIXNOTATIONQUANTIZECOMMAND_H_
+
+#include "document/BasicSelectionCommand.h"
+#include <qstring.h>
+#include <klocale.h>
+
+
+
+
+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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "GraceCommand.h"
+
+#include "base/NotationTypes.h"
+#include "base/Segment.h"
+#include "base/Selection.h"
+#include "document/BasicCommand.h"
+#include "base/BaseProperties.h"
+#include <qstring.h>
+
+
+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<Bool>(IS_GRACE_NOTE, true);
+ (*i)->set<Int>(BEAMED_GROUP_ID, id);
+ (*i)->set<String>(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
+ <Bool>(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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_ADJUSTMENUGRACECOMMAND_H_
+#define _RG_ADJUSTMENUGRACECOMMAND_H_
+
+#include "document/BasicCommand.h"
+#include <qstring.h>
+#include "base/Event.h"
+#include <klocale.h>
+
+
+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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "GuitarChordInsertionCommand.h"
+
+#include <klocale.h>
+#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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "IncrementDisplacementsCommand.h"
+
+#include "base/Selection.h"
+#include "document/BasicSelectionCommand.h"
+#include "base/BaseProperties.h"
+#include <qstring.h>
+
+
+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
+ <Int>(DISPLACED_X, prevX);
+ (*i)->get
+ <Int>(DISPLACED_Y, prevY);
+ (*i)->setMaybe<Int>(DISPLACED_X, prevX + long(m_dx));
+ (*i)->setMaybe<Int>(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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_INCREMENTDISPLACEMENTSCOMMAND_H_
+#define _RG_INCREMENTDISPLACEMENTSCOMMAND_H_
+
+#include "document/BasicSelectionCommand.h"
+#include <qstring.h>
+#include <klocale.h>
+
+
+
+
+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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "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 <qstring.h>
+
+
+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
+ <String>(Text::TextPropertyName, text)) {
+ velocity = getVelocityForDynamic(text);
+ }
+ }
+
+ if (t >= startTime &&
+ (*i)->isa(Note::EventType) && m_selection->contains(*i)) {
+ (*i)->set
+ <Int>(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
+ <Int>(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
+ <Int>(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
+ <String>(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
+ <Int>(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
+ <Int>(VELOCITY, velocity);
+ velocity += velocity * velocityChange / 100;
+ if (velocity < 10)
+ velocity = 10;
+ if (velocity > 127)
+ velocity = 127;
+ e->set
+ <Int>(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
+ <Event *> toErase;
+ std::set
+ <Event *> 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<Mark> marks(chord.getMarksForChord());
+
+ for (std::vector<Mark>::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
+ <Int>(VELOCITY, velocity);
+ velocity += velocity * velocityChange / 100;
+ if (velocity < 10)
+ velocity = 10;
+ if (velocity > 127)
+ velocity = 127;
+ e->set
+ <Int>(VELOCITY, velocity);
+
+ timeT duration = e->getNotationDuration();
+
+ // don't mess with the duration of a tied note
+ bool tiedForward = false;
+ if (e->get
+ <Bool>(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
+ <Event *>::iterator j = toErase.begin(); j != toErase.end(); ++j) {
+ Segment::iterator jtr(segment.findSingle(*j));
+ if (jtr != segment.end())
+ segment.erase(jtr);
+ }
+
+ for (std::set
+ <Event *>::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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_ADJUSTMENUINTERPRETCOMMAND_H_
+#define _RG_ADJUSTMENUINTERPRETCOMMAND_H_
+
+#include "document/BasicSelectionCommand.h"
+#include <map>
+#include <string>
+#include <qstring.h>
+#include "base/Event.h"
+#include <klocale.h>
+
+
+
+
+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<timeT,
+ Indication *> 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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "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 <qstring.h>
+
+
+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
+ <Int>(PITCH);
+
+ if (m_convert) {
+ (*i)->set
+ <Int>(PITCH, m_key.convertFrom(pitch, oldKey));
+ } else {
+ (*i)->set
+ <Int>(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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_KEYINSERTIONCOMMAND_H_
+#define _RG_KEYINSERTIONCOMMAND_H_
+
+#include "base/NotationTypes.h"
+#include "document/BasicCommand.h"
+#include <qstring.h>
+#include "base/Event.h"
+#include <klocale.h>
+#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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "MakeAccidentalsCautionaryCommand.h"
+
+#include <klocale.h>
+#include "base/NotationTypes.h"
+#include "base/Selection.h"
+#include "document/BasicSelectionCommand.h"
+#include "gui/editors/notation/NotationProperties.h"
+#include <qstring.h>
+
+
+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
+ <Bool>(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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_MAKEACCIDENTALSCAUTIONARYCOMMAND_H_
+#define _RG_MAKEACCIDENTALSCAUTIONARYCOMMAND_H_
+
+#include "document/BasicSelectionCommand.h"
+#include <qstring.h>
+
+
+
+
+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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "MakeChordCommand.h"
+
+#include "base/Event.h"
+#include "base/NotationTypes.h"
+#include "base/Segment.h"
+#include "base/Selection.h"
+#include "document/BasicSelectionCommand.h"
+#include <qstring.h>
+
+
+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<Event *> toErase;
+ std::vector<Event *> 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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_ADJUSTMENUMAKECHORDCOMMAND_H_
+#define _RG_ADJUSTMENUMAKECHORDCOMMAND_H_
+
+#include "document/BasicSelectionCommand.h"
+#include <qstring.h>
+#include <klocale.h>
+
+
+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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "MakeNotesViableCommand.h"
+
+#include "base/Segment.h"
+#include "base/SegmentNotationHelper.h"
+#include "base/Selection.h"
+#include "document/BasicSelectionCommand.h"
+#include <qstring.h>
+
+
+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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_ADJUSTMENUMAKENOTESVIABLECOMMAND_H_
+#define _RG_ADJUSTMENUMAKENOTESVIABLECOMMAND_H_
+
+#include "document/BasicSelectionCommand.h"
+#include <qstring.h>
+#include <klocale.h>
+
+
+
+
+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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "MakeRegionViableCommand.h"
+
+#include "base/Segment.h"
+#include "base/SegmentNotationHelper.h"
+#include "document/BasicCommand.h"
+#include <qstring.h>
+
+
+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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_ADJUSTMENUMAKEREGIONVIABLECOMMAND_H_
+#define _RG_ADJUSTMENUMAKEREGIONVIABLECOMMAND_H_
+
+#include "document/BasicCommand.h"
+#include <qstring.h>
+#include "base/Event.h"
+#include <klocale.h>
+
+
+
+
+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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "MultiKeyInsertionCommand.h"
+
+#include "base/Composition.h"
+#include "base/NotationTypes.h"
+#include "base/Segment.h"
+#include "KeyInsertionCommand.h"
+#include <qstring.h>
+#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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_MULTIKEYINSERTIONCOMMAND_H_
+#define _RG_MULTIKEYINSERTIONCOMMAND_H_
+
+#include "base/NotationTypes.h"
+#include <qstring.h>
+#include "base/Event.h"
+#include <klocale.h>
+#include "misc/Strings.h"
+#include <kcommand.h>
+#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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "NormalizeRestsCommand.h"
+
+#include "base/Segment.h"
+#include "base/Selection.h"
+#include "document/BasicCommand.h"
+#include <qstring.h>
+
+
+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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_ADJUSTMENUNORMALIZERESTSCOMMAND_H_
+#define _RG_ADJUSTMENUNORMALIZERESTSCOMMAND_H_
+
+#include "document/BasicCommand.h"
+#include <qstring.h>
+#include "base/Event.h"
+#include <klocale.h>
+
+
+
+
+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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "NoteInsertionCommand.h"
+
+#include <cmath>
+#include <klocale.h>
+#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<Int>(PITCH, m_pitch);
+ e->set<Int>(VELOCITY, 100);
+
+ if (m_accidental != Accidentals::NoAccidental) {
+ e->set<String>(ACCIDENTAL, m_accidental);
+ }
+
+ if (m_noteStyle != NoteStyleFactory::DefaultStyle) {
+ e->set<String>(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<Event *> 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<Event *>::iterator k = toErase.begin();
+ k != toErase.end(); ++k) segment.eraseSingle(*k);
+ for (std::vector<Event *>::iterator k = toInsert.begin();
+ k != toInsert.end(); ++k) segment.insert(*k);
+ }
+ }
+
+ e->set<Bool>(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<Bool>(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<Int>(BEAMED_GROUP_TUPLED_COUNT, count-1);
+ (*i)->set<Int>(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<Int>(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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "RemoveFingeringMarksCommand.h"
+
+#include "base/Selection.h"
+#include "document/BasicSelectionCommand.h"
+#include <qstring.h>
+
+
+namespace Rosegarden
+{
+
+void
+RemoveFingeringMarksCommand::modifySegment()
+{
+ EventSelection::eventcontainer::iterator i;
+
+ for (i = m_selection->getSegmentEvents().begin();
+ i != m_selection->getSegmentEvents().end(); ++i) {
+
+ std::vector<Mark> marks = Marks::getMarks(**i);
+ for (std::vector<Mark>::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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_NOTESMENUREMOVEFINGERINGMARKSCOMMAND_H_
+#define _RG_NOTESMENUREMOVEFINGERINGMARKSCOMMAND_H_
+
+#include "document/BasicSelectionCommand.h"
+#include <qstring.h>
+#include <klocale.h>
+
+
+
+
+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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "RemoveMarksCommand.h"
+
+#include "base/Selection.h"
+#include "document/BasicSelectionCommand.h"
+#include <qstring.h>
+#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
+ <Int>(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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_NOTESMENUREMOVEMARKSCOMMAND_H_
+#define _RG_NOTESMENUREMOVEMARKSCOMMAND_H_
+
+#include "document/BasicSelectionCommand.h"
+#include <qstring.h>
+#include <klocale.h>
+
+
+
+
+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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "RemoveNotationQuantizeCommand.h"
+
+#include "base/Event.h"
+#include "base/Selection.h"
+#include "document/BasicSelectionCommand.h"
+#include <qstring.h>
+
+
+namespace Rosegarden
+{
+
+void
+RemoveNotationQuantizeCommand::modifySegment()
+{
+ EventSelection::eventcontainer::iterator i;
+
+ std::vector<Event *> toInsert;
+ std::vector<Event *> 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<Event *>::iterator i = toErase.begin(); i != toErase.end();
+ ++i) {
+ m_selection->getSegment().eraseSingle(*i);
+ }
+
+ for (std::vector<Event *>::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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_ADJUSTMENUREMOVENOTATIONQUANTIZECOMMAND_H_
+#define _RG_ADJUSTMENUREMOVENOTATIONQUANTIZECOMMAND_H_
+
+#include "document/BasicSelectionCommand.h"
+#include <qstring.h>
+#include <klocale.h>
+
+
+
+
+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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "ResetDisplacementsCommand.h"
+
+#include "base/Selection.h"
+#include "document/BasicSelectionCommand.h"
+#include "base/BaseProperties.h"
+#include <qstring.h>
+
+
+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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_RESETDISPLACEMENTSCOMMAND_H_
+#define _RG_RESETDISPLACEMENTSCOMMAND_H_
+
+#include "document/BasicSelectionCommand.h"
+#include <qstring.h>
+#include <klocale.h>
+
+
+
+
+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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "RespellCommand.h"
+
+#include <klocale.h>
+#include "base/NotationTypes.h"
+#include "base/Selection.h"
+#include "document/BasicSelectionCommand.h"
+#include "base/BaseProperties.h"
+#include <qstring.h>
+
+
+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
+ <String>(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
+ <String>(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
+ <Int>(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
+ <String>(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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_RESPELLCOMMAND_H_
+#define _RG_RESPELLCOMMAND_H_
+
+#include "document/BasicSelectionCommand.h"
+#include <qstring.h>
+
+
+
+
+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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "RestoreSlursCommand.h"
+
+#include "base/NotationTypes.h"
+#include "base/Selection.h"
+#include "document/BasicSelectionCommand.h"
+#include "gui/editors/notation/NotationProperties.h"
+#include <qstring.h>
+
+
+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
+ <String>(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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_ADJUSTMENURESTORESLURSCOMMAND_H_
+#define _RG_ADJUSTMENURESTORESLURSCOMMAND_H_
+
+#include "document/BasicSelectionCommand.h"
+#include <qstring.h>
+#include <klocale.h>
+
+
+
+
+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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "RestoreStemsCommand.h"
+
+#include "base/NotationTypes.h"
+#include "base/Selection.h"
+#include "document/BasicSelectionCommand.h"
+#include "gui/editors/notation/NotationProperties.h"
+#include <qstring.h>
+
+
+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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_ADJUSTMENURESTORESTEMSCOMMAND_H_
+#define _RG_ADJUSTMENURESTORESTEMSCOMMAND_H_
+
+#include "document/BasicSelectionCommand.h"
+#include <qstring.h>
+#include <klocale.h>
+
+
+
+
+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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "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 <qstring.h>
+
+
+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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_ADJUSTMENURESTORETIESCOMMAND_H_
+#define _RG_ADJUSTMENURESTORETIESCOMMAND_H_
+
+#include "document/BasicSelectionCommand.h"
+#include <qstring.h>
+#include <klocale.h>
+
+
+
+
+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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "SetVisibilityCommand.h"
+
+#include "base/Selection.h"
+#include "document/BasicSelectionCommand.h"
+#include "base/BaseProperties.h"
+#include <qstring.h>
+
+
+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
+ <Bool>(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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_SETVISIBILITYCOMMAND_H_
+#define _RG_SETVISIBILITYCOMMAND_H_
+
+#include "document/BasicSelectionCommand.h"
+#include <qstring.h>
+#include <klocale.h>
+
+
+
+
+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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "SustainInsertionCommand.h"
+
+#include "base/Event.h"
+#include "base/MidiTypes.h"
+#include "base/Segment.h"
+#include "document/BasicCommand.h"
+#include <qstring.h>
+
+
+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
+ <Int>(Controller::NUMBER, m_controllerNumber);
+ e->set
+ <Int>(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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_SUSTAININSERTIONCOMMAND_H_
+#define _RG_SUSTAININSERTIONCOMMAND_H_
+
+#include "document/BasicCommand.h"
+#include <qstring.h>
+#include "base/Event.h"
+#include <klocale.h>
+
+
+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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "TextChangeCommand.h"
+
+#include <klocale.h>
+#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
+ <String>(Text::TextTypePropertyName, m_text.getTextType());
+ m_event->set
+ <String>(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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "TextInsertionCommand.h"
+
+#include <klocale.h>
+#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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "TieNotesCommand.h"
+
+#include "base/Segment.h"
+#include "base/SegmentNotationHelper.h"
+#include "base/Selection.h"
+#include "document/BasicSelectionCommand.h"
+#include "base/BaseProperties.h"
+#include <qstring.h>
+
+
+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<Bool>(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<Bool>(TIED_FORWARD, true);
+ (*si)->unset(TIE_IS_ABOVE);
+ (*sj)->set<Bool>(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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_NOTESMENUTIENOTESCOMMAND_H_
+#define _RG_NOTESMENUTIENOTESCOMMAND_H_
+
+#include "document/BasicSelectionCommand.h"
+#include <qstring.h>
+#include <klocale.h>
+
+
+
+
+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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "TupletCommand.h"
+
+#include "base/Event.h"
+#include "base/Segment.h"
+#include "base/SegmentNotationHelper.h"
+#include "document/BasicCommand.h"
+#include "base/BaseProperties.h"
+#include <qstring.h>
+
+
+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
+ <Int>(BEAMED_GROUP_ID, groupId);
+ e->set
+ <String>(BEAMED_GROUP_TYPE, GROUP_TYPE_TUPLED);
+
+ e->set
+ <Int>(BEAMED_GROUP_TUPLET_BASE, m_unit);
+ e->set
+ <Int>(BEAMED_GROUP_TUPLED_COUNT, m_tupled);
+ e->set
+ <Int>(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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_ADJUSTMENUTUPLETCOMMAND_H_
+#define _RG_ADJUSTMENUTUPLETCOMMAND_H_
+
+#include "document/BasicCommand.h"
+#include <qstring.h>
+#include "base/Event.h"
+#include <klocale.h>
+
+
+
+
+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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "UnGraceCommand.h"
+
+#include "base/Selection.h"
+#include "document/BasicSelectionCommand.h"
+#include <qstring.h>
+
+
+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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_ADJUSTMENUUNGRACECOMMAND_H_
+#define _RG_ADJUSTMENUUNGRACECOMMAND_H_
+
+#include "document/BasicSelectionCommand.h"
+#include <qstring.h>
+#include <klocale.h>
+
+
+
+
+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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "UnTupletCommand.h"
+
+#include "base/Selection.h"
+#include "document/BasicSelectionCommand.h"
+#include "base/BaseProperties.h"
+#include <qstring.h>
+
+
+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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_ADJUSTMENUUNTUPLETCOMMAND_H_
+#define _RG_ADJUSTMENUUNTUPLETCOMMAND_H_
+
+#include "document/BasicSelectionCommand.h"
+#include <qstring.h>
+#include <klocale.h>
+
+
+
+
+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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "UntieNotesCommand.h"
+
+#include "base/Selection.h"
+#include "document/BasicSelectionCommand.h"
+#include <qstring.h>
+#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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_ADJUSTMENUUNTIENOTESCOMMAND_H_
+#define _RG_ADJUSTMENUUNTIENOTESCOMMAND_H_
+
+#include "document/BasicSelectionCommand.h"
+#include <qstring.h>
+#include <klocale.h>
+
+
+
+
+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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "AddTempoChangeCommand.h"
+
+#include "base/Composition.h"
+#include <qstring.h>
+
+
+namespace Rosegarden
+{
+
+AddTempoChangeCommand::~AddTempoChangeCommand()
+{
+ // nothing here either
+}
+
+void
+AddTempoChangeCommand::execute()
+{
+ int oldIndex = m_composition->getTempoChangeNumberAt(m_time);
+
+ if (oldIndex >= 0) {
+ std::pair<timeT, tempoT> 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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_ADDTEMPOCHANGECOMMAND_H_
+#define _RG_ADDTEMPOCHANGECOMMAND_H_
+
+#include <kcommand.h>
+#include <qstring.h>
+#include "base/Event.h"
+#include "base/Composition.h" // for tempoT
+#include <klocale.h>
+
+
+
+
+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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_ADDTIMESIGNATUREANDNORMALIZECOMMAND_H_
+#define _RG_ADDTIMESIGNATUREANDNORMALIZECOMMAND_H_
+
+#include "base/NotationTypes.h"
+#include "base/Event.h"
+#include <kcommand.h>
+
+
+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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "AddTimeSignatureCommand.h"
+
+#include "base/Composition.h"
+#include "base/NotationTypes.h"
+#include <qstring.h>
+
+
+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<timeT, TimeSignature> 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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_ADDTIMESIGNATURECOMMAND_H_
+#define _RG_ADDTIMESIGNATURECOMMAND_H_
+
+#include "base/NotationTypes.h"
+#include <kcommand.h>
+#include <qstring.h>
+#include "base/Event.h"
+#include <klocale.h>
+
+
+
+
+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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "AddTracksCommand.h"
+
+#include "misc/Debug.h"
+#include "base/Composition.h"
+#include "base/MidiProgram.h"
+#include "base/Track.h"
+#include <qstring.h>
+
+
+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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_ADDTRACKSCOMMAND_H_
+#define _RG_ADDTRACKSCOMMAND_H_
+
+#include "base/MidiProgram.h"
+#include "base/Composition.h"
+#include <kcommand.h>
+#include <qstring.h>
+#include <vector>
+#include <map>
+#include <klocale.h>
+
+
+
+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<TrackId, int> TrackPositionMap;
+
+ std::vector<Track *> 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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "AddTriggerSegmentCommand.h"
+
+#include <klocale.h>
+#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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_ADDTRIGGERSEGMENTCOMMAND_H_
+#define _RG_ADDTRIGGERSEGMENTCOMMAND_H_
+
+#include "base/TriggerSegment.h"
+#include <kcommand.h>
+#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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "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 <qstring.h>
+
+
+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<AutoSplitPoint> 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<SplitPointPair> 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<SplitPointPair>::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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_AUDIOSEGMENTAUTOSPLITCOMMAND_H_
+#define _RG_AUDIOSEGMENTAUTOSPLITCOMMAND_H_
+
+#include <kcommand.h>
+#include <qstring.h>
+#include <vector>
+#include <klocale.h>
+
+
+
+
+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<Segment *> 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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "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 <qstring.h>
+
+
+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<timeT> 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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_AUDIOSEGMENTDISTRIBUTECOMMAND_H_
+#define _RG_AUDIOSEGMENTDISTRIBUTECOMMAND_H_
+
+#include "base/Selection.h"
+#include <kcommand.h>
+#include <qstring.h>
+#include <vector>
+#include <klocale.h>
+
+
+
+
+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<Segment*> 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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "AudioSegmentInsertCommand.h"
+
+#include <klocale.h>
+#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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_AUDIOSEGMENTINSERTCOMMAND_H_
+#define _RG_AUDIOSEGMENTINSERTCOMMAND_H_
+
+#include "base/RealTime.h"
+#include "base/Track.h"
+#include "sound/AudioFile.h"
+#include <kcommand.h>
+#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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "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 <qstring.h>
+
+
+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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_AUDIOSEGMENTRESCALECOMMAND_H_
+#define _RG_AUDIOSEGMENTRESCALECOMMAND_H_
+
+#include <kcommand.h>
+#include <qstring.h>
+#include "base/Event.h"
+#include <klocale.h>
+
+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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "AudioSegmentResizeFromStartCommand.h"
+
+#include <klocale.h>
+#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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_AUDIOSEGMENTRESIZEFROMSTARTCOMMAND_H_
+#define _RG_AUDIOSEGMENTRESIZEFROMSTARTCOMMAND_H_
+
+#include <kcommand.h>
+#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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "AudioSegmentSplitCommand.h"
+
+#include <klocale.h>
+#include "misc/Debug.h"
+#include "misc/Strings.h"
+#include "base/RealTime.h"
+#include "base/Composition.h"
+#include "base/Segment.h"
+#include <qstring.h>
+
+
+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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_AUDIOSEGMENTSPLITCOMMAND_H_
+#define _RG_AUDIOSEGMENTSPLITCOMMAND_H_
+
+#include <string>
+#include <kcommand.h>
+#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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "ChangeCompositionLengthCommand.h"
+
+#include "base/Composition.h"
+#include <qstring.h>
+
+
+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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_CHANGECOMPOSITIONLENGTHCOMMAND_H_
+#define _RG_CHANGECOMPOSITIONLENGTHCOMMAND_H_
+
+#include <kcommand.h>
+#include <qstring.h>
+#include "base/Event.h"
+#include <klocale.h>
+
+
+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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "CreateTempoMapFromSegmentCommand.h"
+
+#include <klocale.h>
+#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<timeT> beatTimeTs;
+ std::vector<RealTime> 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<timeT, tempoT> 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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_CREATETEMPOMAPFROMSEGMENTCOMMAND_H_
+#define _RG_CREATETEMPOMAPFROMSEGMENTCOMMAND_H_
+
+#include <map>
+#include <kcommand.h>
+#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<timeT, tempoT> 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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "CutRangeCommand.h"
+
+#include <klocale.h>
+#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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_CUTRANGECOMMAND_H_
+#define _RG_CUTRANGECOMMAND_H_
+
+#include "base/Event.h"
+#include <kcommand.h>
+
+
+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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "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<Segment *> 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<Segment *>::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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_DELETERANGECOMMAND_H_
+#define _RG_DELETERANGECOMMAND_H_
+
+#include <kcommand.h>
+#include "base/Event.h"
+#include <klocale.h>
+
+#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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "DeleteTracksCommand.h"
+
+#include "misc/Debug.h"
+#include "base/Composition.h"
+#include "base/Segment.h"
+#include <qstring.h>
+
+
+namespace Rosegarden
+{
+
+DeleteTracksCommand::DeleteTracksCommand(Composition *composition,
+ std::vector<TrackId> 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<Track*>::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<Track*>::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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_DELETETRACKSCOMMAND_H_
+#define _RG_DELETETRACKSCOMMAND_H_
+
+#include <kcommand.h>
+#include <qstring.h>
+#include <vector>
+#include <klocale.h>
+#include "base/Track.h"
+
+
+namespace Rosegarden
+{
+
+class Track;
+class Segment;
+class Composition;
+
+
+class DeleteTracksCommand : public KNamedCommand
+{
+public:
+ DeleteTracksCommand(Composition *composition,
+ std::vector<TrackId> tracks);
+ virtual ~DeleteTracksCommand();
+
+ static QString getGlobalName() { return i18n("Delete Tracks..."); }
+
+ virtual void execute();
+ virtual void unexecute();
+
+protected:
+ Composition *m_composition;
+ std::vector<TrackId> m_tracks;
+
+ std::vector<Track*> m_oldTracks;
+ std::vector<Segment*> 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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "DeleteTriggerSegmentCommand.h"
+
+#include <klocale.h>
+#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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_DELETETRIGGERSEGMENTCOMMAND_H_
+#define _RG_DELETETRIGGERSEGMENTCOMMAND_H_
+
+#include "base/TriggerSegment.h"
+#include <kcommand.h>
+
+
+
+
+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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "EraseSegmentsStartingInRangeCommand.h"
+
+#include <klocale.h>
+#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<Segment *>::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<Segment *>::iterator i = m_detaching.begin();
+ i != m_detaching.end(); ++i) {
+ m_composition->detachSegment(*i);
+ }
+
+ m_detached = true;
+}
+
+void
+EraseSegmentsStartingInRangeCommand::unexecute()
+{
+ for (std::vector<Segment *>::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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_ERASESEGMENTSSTARTINGINRANGECOMMAND_H_
+#define _RG_ERASESEGMENTSSTARTINGINRANGECOMMAND_H_
+
+#include <kcommand.h>
+#include <vector>
+#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<Segment *> 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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "InsertRangeCommand.h"
+
+#include <klocale.h>
+#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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_INSERTRANGECOMMAND_H_
+#define _RG_INSERTRANGECOMMAND_H_
+
+#include "base/Event.h"
+#include <kcommand.h>
+
+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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "ModifyDefaultTempoCommand.h"
+
+#include "base/Composition.h"
+#include <qstring.h>
+
+
+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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_MODIFYDEFAULTTEMPOCOMMAND_H_
+#define _RG_MODIFYDEFAULTTEMPOCOMMAND_H_
+
+#include <kcommand.h>
+#include <qstring.h>
+#include <klocale.h>
+#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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "MoveTracksCommand.h"
+
+#include "base/Composition.h"
+#include "base/Track.h"
+#include <qstring.h>
+
+
+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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_MOVETRACKSCOMMAND_H_
+#define _RG_MOVETRACKSCOMMAND_H_
+
+#include "base/Track.h"
+#include <kcommand.h>
+#include <qstring.h>
+#include <klocale.h>
+
+
+
+
+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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "OpenOrCloseRangeCommand.h"
+
+#include <klocale.h>
+#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<Segment *>::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<Segment *>::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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_OPENORCLOSERANGECOMMAND_H_
+#define _RG_OPENORCLOSERANGECOMMAND_H_
+
+#include "base/Selection.h"
+#include <kcommand.h>
+#include <vector>
+#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<Segment *> 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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "PasteConductorDataCommand.h"
+
+#include <klocale.h>
+#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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_PASTECONDUCTORDATACOMMAND_H_
+#define _RG_PASTECONDUCTORDATACOMMAND_H_
+
+#include <kcommand.h>
+#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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "PasteRangeCommand.h"
+
+#include <klocale.h>
+#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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_PASTERANGECOMMAND_H_
+#define _RG_PASTERANGECOMMAND_H_
+
+#include "base/Event.h"
+#include <kcommand.h>
+
+
+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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "PasteToTriggerSegmentCommand.h"
+
+#include "base/Event.h"
+#include <klocale.h>
+#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 <qstring.h>
+
+
+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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_PASTETOTRIGGERSEGMENTCOMMAND_H_
+#define _RG_PASTETOTRIGGERSEGMENTCOMMAND_H_
+
+#include "base/TriggerSegment.h"
+#include <kcommand.h>
+#include <qstring.h>
+
+
+
+
+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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "RemoveTempoChangeCommand.h"
+
+#include "base/Composition.h"
+#include <qstring.h>
+
+
+namespace Rosegarden
+{
+
+void
+RemoveTempoChangeCommand::execute()
+{
+ if (m_tempoChangeIndex >= 0) {
+ std::pair<timeT, tempoT> 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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_REMOVETEMPOCHANGECOMMAND_H_
+#define _RG_REMOVETEMPOCHANGECOMMAND_H_
+
+#include <kcommand.h>
+#include <qstring.h>
+#include "base/Event.h"
+#include "base/Composition.h" // for tempoT
+#include <klocale.h>
+
+
+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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "RemoveTimeSignatureCommand.h"
+
+#include "base/Composition.h"
+#include "base/NotationTypes.h"
+#include <qstring.h>
+
+
+namespace Rosegarden
+{
+
+void
+RemoveTimeSignatureCommand::execute()
+{
+ if (m_timeSigIndex >= 0) {
+ std::pair<timeT, TimeSignature> 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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_REMOVETIMESIGNATURECOMMAND_H_
+#define _RG_REMOVETIMESIGNATURECOMMAND_H_
+
+#include "base/NotationTypes.h"
+#include <kcommand.h>
+#include <qstring.h>
+#include "base/Event.h"
+#include <klocale.h>
+
+
+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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "RenameTrackCommand.h"
+
+#include "misc/Debug.h"
+#include "base/Composition.h"
+#include "base/Track.h"
+#include <qstring.h>
+
+
+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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_RENAMETRACKCOMMAND_H_
+#define _RG_RENAMETRACKCOMMAND_H_
+
+#include "base/Track.h"
+#include <string>
+#include <kcommand.h>
+#include <qstring.h>
+#include <klocale.h>
+
+
+
+
+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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "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 <qstring.h>
+
+
+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<AutoSplitPoint> 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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_SEGMENTAUTOSPLITCOMMAND_H_
+#define _RG_SEGMENTAUTOSPLITCOMMAND_H_
+
+#include <kcommand.h>
+#include <qstring.h>
+#include <vector>
+#include <klocale.h>
+
+
+
+
+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<Segment *> 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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "SegmentChangePlayableRangeCommand.h"
+
+#include "base/Segment.h"
+#include "gui/editors/notation/NotationStrings.h"
+#include <qstring.h>
+
+
+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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_SEGMENTCHANGEPLAYABLERANGECOMMAND_H_
+#define _RG_SEGMENTCHANGEPLAYABLERANGECOMMAND_H_
+
+#include <kcommand.h>
+#include <qstring.h>
+#include <vector>
+#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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "SegmentChangeQuantizationCommand.h"
+
+#include "base/Segment.h"
+#include "base/BasicQuantizer.h"
+#include "gui/editors/notation/NotationStrings.h"
+#include <qstring.h>
+
+
+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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_SEGMENTCHANGEQUANTIZATIONCOMMAND_H_
+#define _RG_SEGMENTCHANGEQUANTIZATIONCOMMAND_H_
+
+#include <kcommand.h>
+#include <qstring.h>
+#include <vector>
+#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<SegmentRec> 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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "SegmentChangeTransposeCommand.h"
+
+#include "base/Segment.h"
+#include "gui/editors/notation/NotationStrings.h"
+#include <qstring.h>
+
+
+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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_SEGMENTCHANGETRANSPOSECOMMAND_H_
+#define _RG_SEGMENTCHANGETRANSPOSECOMMAND_H_
+
+#include <kcommand.h>
+#include <qstring.h>
+#include <vector>
+#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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "SegmentColourCommand.h"
+
+#include "base/Segment.h"
+#include "base/Selection.h"
+#include <qstring.h>
+
+
+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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_SEGMENTCOLOURCOMMAND_H_
+#define _RG_SEGMENTCOLOURCOMMAND_H_
+
+#include "base/Segment.h"
+#include <kcommand.h>
+#include <qstring.h>
+#include <vector>
+#include <klocale.h>
+
+
+
+
+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<Segment*> m_segments;
+ std::vector<unsigned int> 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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "SegmentColourMapCommand.h"
+
+#include "base/ColourMap.h"
+#include "base/Segment.h"
+#include "document/RosegardenGUIDoc.h"
+#include <qstring.h>
+
+
+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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_SEGMENTCOLOURMAPCOMMAND_H_
+#define _RG_SEGMENTCOLOURMAPCOMMAND_H_
+
+#include "base/ColourMap.h"
+#include "base/Segment.h"
+#include <kcommand.h>
+#include <qstring.h>
+#include <klocale.h>
+
+
+
+
+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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "SegmentCommand.h"
+
+#include "base/Segment.h"
+#include <qstring.h>
+
+
+namespace Rosegarden
+{
+
+SegmentCommand::SegmentCommand(QString name, const std::vector<Segment*>& 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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_SEGMENTCOMMAND_H_
+#define _RG_SEGMENTCOMMAND_H_
+
+#include <kcommand.h>
+#include <qstring.h>
+#include <vector>
+
+
+
+
+namespace Rosegarden
+{
+
+class Segment;
+
+
+/**
+ * Base class for commands from the SegmentParameterBox
+ */
+class SegmentCommand : public KNamedCommand
+{
+public:
+ SegmentCommand(QString name, const std::vector<Segment*>&);
+
+ typedef std::vector<Segment*> 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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "SegmentCommandRepeat.h"
+
+#include <klocale.h>
+#include "base/Segment.h"
+#include "SegmentCommand.h"
+#include <qstring.h>
+
+
+namespace Rosegarden
+{
+
+SegmentCommandRepeat::SegmentCommandRepeat(const std::vector<Segment*>& 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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_SEGMENTCOMMANDREPEAT_H_
+#define _RG_SEGMENTCOMMANDREPEAT_H_
+
+#include "SegmentCommand.h"
+#include <vector>
+
+
+
+
+namespace Rosegarden
+{
+
+class Segment;
+
+
+/**
+ * Repeat segment command from the SegmentParameterBox
+ */
+class SegmentCommandRepeat : public SegmentCommand
+{
+public:
+ SegmentCommandRepeat(const std::vector<Segment*>&,
+ 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<Segment*>&,
+// int transposeValue);
+
+// virtual void execute();
+// virtual void unexecute();
+
+// protected:
+// int m_transposeValue;
+// std::vector<int> 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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "SegmentEraseCommand.h"
+
+#include <klocale.h>
+#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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_SEGMENTERASECOMMAND_H_
+#define _RG_SEGMENTERASECOMMAND_H_
+
+#include <string>
+#include <kcommand.h>
+
+
+
+
+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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "SegmentInsertCommand.h"
+
+#include <klocale.h>
+#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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_SEGMENTINSERTCOMMAND_H_
+#define _RG_SEGMENTINSERTCOMMAND_H_
+
+#include "base/Track.h"
+#include <kcommand.h>
+#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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "SegmentJoinCommand.h"
+
+#include "base/Composition.h"
+#include "base/Event.h"
+#include "base/NotationTypes.h"
+#include "base/Segment.h"
+#include "base/Selection.h"
+#include <qstring.h>
+
+
+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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_SEGMENTJOINCOMMAND_H_
+#define _RG_SEGMENTJOINCOMMAND_H_
+
+#include <kcommand.h>
+#include <qstring.h>
+#include <vector>
+#include <klocale.h>
+
+
+
+
+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<Segment *> 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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "SegmentLabelCommand.h"
+
+#include "misc/Strings.h"
+#include "base/Segment.h"
+#include "base/Selection.h"
+#include <qstring.h>
+
+
+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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_SEGMENTLABELCOMMAND_H_
+#define _RG_SEGMENTLABELCOMMAND_H_
+
+#include <kcommand.h>
+#include <qstring.h>
+#include <vector>
+#include <klocale.h>
+
+
+
+
+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<Segment*> m_segments;
+ std::vector<QString> 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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "SegmentQuickCopyCommand.h"
+
+#include "misc/Strings.h"
+#include "base/Composition.h"
+#include "base/Segment.h"
+#include <qstring.h>
+
+
+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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_SEGMENTQUICKCOPYCOMMAND_H_
+#define _RG_SEGMENTQUICKCOPYCOMMAND_H_
+
+#include <kcommand.h>
+#include <qstring.h>
+#include <klocale.h>
+
+
+
+
+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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "SegmentReconfigureCommand.h"
+
+#include "base/Segment.h"
+#include "base/Track.h"
+#include <qstring.h>
+
+
+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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_SEGMENTRECONFIGURECOMMAND_H_
+#define _RG_SEGMENTRECONFIGURECOMMAND_H_
+
+#include "base/Track.h"
+#include <kcommand.h>
+#include <qstring.h>
+#include <vector>
+#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<SegmentRec> 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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "SegmentRecordCommand.h"
+
+#include <klocale.h>
+#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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_SEGMENTRECORDCOMMAND_H_
+#define _RG_SEGMENTRECORDCOMMAND_H_
+
+#include <kcommand.h>
+
+
+
+
+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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "SegmentRepeatToCopyCommand.h"
+
+#include "base/Event.h"
+#include <klocale.h>
+#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<Segment*>::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<Segment*>::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<Segment*>::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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_SEGMENTREPEATTOCOPYCOMMAND_H_
+#define _RG_SEGMENTREPEATTOCOPYCOMMAND_H_
+
+#include <kcommand.h>
+#include <vector>
+
+
+
+
+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<Segment*> 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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "SegmentRescaleCommand.h"
+
+#include "misc/Strings.h"
+#include "base/Event.h"
+#include "base/Composition.h"
+#include "base/NotationTypes.h"
+#include "base/Segment.h"
+#include <qstring.h>
+
+
+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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_SEGMENTRESCALECOMMAND_H_
+#define _RG_SEGMENTRESCALECOMMAND_H_
+
+#include <kcommand.h>
+#include <qstring.h>
+#include "base/Event.h"
+#include <klocale.h>
+
+
+
+
+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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "SegmentResizeFromStartCommand.h"
+
+#include <klocale.h>
+#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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "SegmentSingleRepeatToCopyCommand.h"
+
+#include <klocale.h>
+#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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_SEGMENTSINGLEREPEATTOCOPYCOMMAND_H_
+#define _RG_SEGMENTSINGLEREPEATTOCOPYCOMMAND_H_
+
+#include <kcommand.h>
+#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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "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 <qstring.h>
+
+
+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
+ <Int>(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<int>::iterator PitchItr;
+ std::set<int> 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<int> c0p(c0.getPitches());
+ pitches.insert<std::vector<int>::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<int> c1p(c1.getPitches());
+ pitches.insert<std::vector<int>::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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_SEGMENTSPLITBYPITCHCOMMAND_H_
+#define _RG_SEGMENTSPLITBYPITCHCOMMAND_H_
+
+#include "base/Segment.h"
+#include <kcommand.h>
+#include <qstring.h>
+#include <klocale.h>
+#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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "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 <qstring.h>
+
+
+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
+ <Int>(BaseProperties::RECORDED_CHANNEL) );
+ }
+
+ if ((*i)->has(BaseProperties::RECORDED_PORT)) {
+ selectedD = true;
+ if (m_device > -1)
+ selectedD = ( m_device ==
+ (*i)->get
+ <Int>(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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_SEGMENTSPLITBYRECORDINGSRCCOMMAND_H_
+#define _RG_SEGMENTSPLITBYRECORDINGSRCCOMMAND_H_
+
+#include <kcommand.h>
+#include <qstring.h>
+#include <klocale.h>
+#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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "SegmentSplitCommand.h"
+
+#include <klocale.h>
+#include "misc/Strings.h"
+#include "base/Event.h"
+#include "base/Composition.h"
+#include "base/NotationTypes.h"
+#include "base/Segment.h"
+#include <qstring.h>
+
+
+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<Event *> 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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_SEGMENTSPLITCOMMAND_H_
+#define _RG_SEGMENTSPLITCOMMAND_H_
+
+#include <string>
+#include <kcommand.h>
+#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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_SEGMENTSYNCCLEFCOMMAND_H_
+#define _RG_SEGMENTSYNCCLEFCOMMAND_H_
+
+#include <kcommand.h>
+#include "base/Event.h"
+#include "base/NotationTypes.h"
+#include "document/MultiViewCommandHistory.h"
+#include <klocale.h>
+
+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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "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<Segment *> 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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_SEGMENTSYNCCOMMAND_H_
+#define _RG_SEGMENTSYNCCOMMAND_H_
+
+#include <kcommand.h>
+#include "base/Composition.h"
+#include "base/Event.h"
+#include "base/NotationTypes.h"
+#include "document/MultiViewCommandHistory.h"
+#include <klocale.h>
+
+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<Segment *> 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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "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<KeyInsertionCommand*> 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<KeyInsertionCommand*>::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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_SEGMENTTRANSPOSECOMMAND_H_
+#define _RG_SEGMENTTRANSPOSECOMMAND_H_
+
+#include <kcommand.h>
+#include "base/Event.h"
+#include "document/MultiViewCommandHistory.h"
+#include <klocale.h>
+
+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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "SetTriggerSegmentBasePitchCommand.h"
+
+#include <klocale.h>
+#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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_SETTRIGGERSEGMENTBASEPITCHCOMMAND_H_
+#define _RG_SETTRIGGERSEGMENTBASEPITCHCOMMAND_H_
+
+#include "base/TriggerSegment.h"
+#include <kcommand.h>
+
+
+
+
+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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "SetTriggerSegmentBaseVelocityCommand.h"
+
+#include <klocale.h>
+#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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_SETTRIGGERSEGMENTBASEVELOCITYCOMMAND_H_
+#define _RG_SETTRIGGERSEGMENTBASEVELOCITYCOMMAND_H_
+
+#include "base/TriggerSegment.h"
+#include <kcommand.h>
+
+
+
+
+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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "SetTriggerSegmentDefaultRetuneCommand.h"
+
+#include <klocale.h>
+#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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_SETTRIGGERSEGMENTDEFAULTRETUNECOMMAND_H_
+#define _RG_SETTRIGGERSEGMENTDEFAULTRETUNECOMMAND_H_
+
+#include "base/TriggerSegment.h"
+#include <kcommand.h>
+
+
+
+
+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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "SetTriggerSegmentDefaultTimeAdjustCommand.h"
+
+#include <klocale.h>
+#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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_SETTRIGGERSEGMENTDEFAULTTIMEADJUSTCOMMAND_H_
+#define _RG_SETTRIGGERSEGMENTDEFAULTTIMEADJUSTCOMMAND_H_
+
+#include "base/TriggerSegment.h"
+#include <string>
+#include <kcommand.h>
+
+
+
+
+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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "AddControlParameterCommand.h"
+
+#include "base/ControlParameter.h"
+#include "base/Device.h"
+#include "base/MidiDevice.h"
+#include "base/Studio.h"
+#include <qstring.h>
+#include <iostream>
+
+
+namespace Rosegarden
+{
+
+AddControlParameterCommand::~AddControlParameterCommand()
+{}
+
+void
+AddControlParameterCommand::execute()
+{
+ MidiDevice *md = dynamic_cast<MidiDevice *>
+ (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<MidiDevice *>
+ (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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_ADDCONTROLPARAMETERCOMMAND_H_
+#define _RG_ADDCONTROLPARAMETERCOMMAND_H_
+
+#include "base/ControlParameter.h"
+#include "base/Device.h"
+#include <kcommand.h>
+#include <qstring.h>
+#include <klocale.h>
+
+
+
+
+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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "CreateOrDeleteDeviceCommand.h"
+
+#include "misc/Debug.h"
+#include "misc/Strings.h"
+#include "base/Device.h"
+#include "base/MidiDevice.h"
+#include "base/Studio.h"
+#include <qcstring.h>
+#include <qdatastream.h>
+#include <qstring.h>
+#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<MidiDevice *>(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<MidiDevice *>
+ (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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_CREATEORDELETEDEVICECOMMAND_H_
+#define _RG_CREATEORDELETEDEVICECOMMAND_H_
+
+#include "base/Device.h"
+#include "base/MidiDevice.h"
+#include <string>
+#include <kcommand.h>
+#include <qstring.h>
+#include <klocale.h>
+
+
+
+
+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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "ModifyControlParameterCommand.h"
+
+#include "base/ControlParameter.h"
+#include "base/Device.h"
+#include "base/MidiDevice.h"
+#include "base/Studio.h"
+#include <qstring.h>
+#include <iostream>
+
+
+namespace Rosegarden
+{
+
+ModifyControlParameterCommand::~ModifyControlParameterCommand()
+{}
+
+void
+ModifyControlParameterCommand::execute()
+{
+ MidiDevice *md = dynamic_cast<MidiDevice *>
+ (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<MidiDevice *>
+ (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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_MODIFYCONTROLPARAMETERCOMMAND_H_
+#define _RG_MODIFYCONTROLPARAMETERCOMMAND_H_
+
+#include "base/ControlParameter.h"
+#include "base/Device.h"
+#include <kcommand.h>
+#include <qstring.h>
+#include <klocale.h>
+
+
+
+
+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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "ModifyDeviceCommand.h"
+
+#include "base/Device.h"
+#include "base/MidiDevice.h"
+#include "base/Studio.h"
+#include <qstring.h>
+#include <iostream>
+
+
+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<MidiDevice *>(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<MidiDevice *>(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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_MODIFYDEVICECOMMAND_H_
+#define _RG_MODIFYDEVICECOMMAND_H_
+
+#include "base/Device.h"
+#include "base/MidiDevice.h"
+#include <string>
+#include <kcommand.h>
+#include <qstring.h>
+#include <klocale.h>
+
+
+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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "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 <qstring.h>
+
+
+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<std::pair<TrackId, InstrumentId> >
+ ::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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_MODIFYDEVICEMAPPINGCOMMAND_H_
+#define _RG_MODIFYDEVICEMAPPINGCOMMAND_H_
+
+#include "base/Device.h"
+#include "base/Track.h"
+#include <kcommand.h>
+#include <qstring.h>
+#include <vector>
+#include <klocale.h>
+
+
+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<std::pair<TrackId, InstrumentId> > 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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "ModifyInstrumentMappingCommand.h"
+
+#include "base/Composition.h"
+#include "base/MidiProgram.h"
+#include "base/Studio.h"
+#include "base/Track.h"
+#include "document/RosegardenGUIDoc.h"
+#include <qstring.h>
+
+
+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<TrackId>::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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_MODIFYINSTRUMENTMAPPINGCOMMAND_H_
+#define _RG_MODIFYINSTRUMENTMAPPINGCOMMAND_H_
+
+#include "base/MidiProgram.h"
+#include "base/Track.h"
+#include <kcommand.h>
+#include <qstring.h>
+#include <vector>
+#include <klocale.h>
+
+
+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<TrackId> 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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "ReconnectDeviceCommand.h"
+
+#include "misc/Strings.h"
+#include "misc/Debug.h"
+#include "base/Device.h"
+#include "base/Studio.h"
+#include <qcstring.h>
+#include <qdatastream.h>
+#include <qstring.h>
+#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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_RECONNECTDEVICECOMMAND_H_
+#define _RG_RECONNECTDEVICECOMMAND_H_
+
+#include "base/Device.h"
+#include <string>
+#include <kcommand.h>
+#include <qstring.h>
+#include <klocale.h>
+
+
+
+
+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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "RemoveControlParameterCommand.h"
+
+#include "base/ControlParameter.h"
+#include "base/Device.h"
+#include "base/MidiDevice.h"
+#include "base/Studio.h"
+#include <qstring.h>
+#include <iostream>
+
+
+namespace Rosegarden
+{
+
+RemoveControlParameterCommand::~RemoveControlParameterCommand()
+{}
+
+void
+RemoveControlParameterCommand::execute()
+{
+ MidiDevice *md = dynamic_cast<MidiDevice *>
+ (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<MidiDevice *>
+ (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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_REMOVECONTROLPARAMETERCOMMAND_H_
+#define _RG_REMOVECONTROLPARAMETERCOMMAND_H_
+
+#include "base/ControlParameter.h"
+#include "base/Device.h"
+#include <kcommand.h>
+#include <qstring.h>
+#include <klocale.h>
+
+
+
+
+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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "RenameDeviceCommand.h"
+
+#include "base/Device.h"
+#include "base/Studio.h"
+#include <qstring.h>
+
+
+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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_RENAMEDEVICECOMMAND_H_
+#define _RG_RENAMEDEVICECOMMAND_H_
+
+#include "base/Device.h"
+#include <string>
+#include <kcommand.h>
+#include <qstring.h>
+#include <klocale.h>
+
+
+
+
+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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "BasicCommand.h"
+
+#include "base/Segment.h"
+#include <qstring.h>
+
+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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_BASICCOMMAND_H_
+#define _RG_BASICCOMMAND_H_
+
+#include "base/Segment.h"
+#include <kcommand.h>
+#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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "BasicSelectionCommand.h"
+
+#include "base/Segment.h"
+#include "base/Selection.h"
+#include "BasicCommand.h"
+#include <qstring.h>
+
+
+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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <bownie@bownie.com>
+
+ 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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <bownie@bownie.com>
+
+ 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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "MultiViewCommandHistory.h"
+
+#include <klocale.h>
+#include "misc/Debug.h"
+#include <kactioncollection.h>
+#include <kaction.h>
+#include <kcommand.h>
+#include <kstdaction.h>
+#include <qobject.h>
+#include <qpopupmenu.h>
+#include <qregexp.h>
+#include <qstring.h>
+#include <kpopupmenu.h>
+
+
+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<KToolBarPopupAction*>(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<KToolBarPopupAction*>(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<KNamedCommand *>(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<KNamedCommand *>(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<KToolBarPopupAction *>(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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_MULTIVIEWCOMMANDHISTORY_H_
+#define _RG_MULTIVIEWCOMMANDHISTORY_H_
+
+#include <set>
+#include <stack>
+#include <qobject.h>
+
+
+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<KActionCollection *> ViewSet;
+ ViewSet m_views;
+
+ typedef std::stack<KCommand *> 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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "RoseXmlHandler.h"
+
+#include "sound/Midi.h"
+#include <klocale.h>
+#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 <kfiledialog.h>
+#include <kmessagebox.h>
+#include <qcstring.h>
+#include <qdatastream.h>
+#include <qdialog.h>
+#include <qfileinfo.h>
+#include <qstring.h>
+#include <qstringlist.h>
+#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<String>(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<Int>(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<Rosegarden::RealTimeT>(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<Rosegarden::Bool>(qstrtostr(m_propertyName), b);
+
+ return true;
+ }
+
+ if (!m_propertyType ||
+ m_propertyType == "String") {
+
+ m_configuration->set<Rosegarden::String>(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
+ <Int>(BEAMED_GROUP_ID);
+
+ if (m_groupIdMap.find(storedId) == m_groupIdMap.end()) {
+ m_groupIdMap[storedId] = m_currentSegment->getNextId();
+ }
+
+ m_currentEvent->set
+ <Int>(BEAMED_GROUP_ID, m_groupIdMap[storedId]);
+
+ } else if (m_inGroup) {
+ m_currentEvent->set
+ <Int>(BEAMED_GROUP_ID, m_groupId);
+ m_currentEvent->set
+ <String>(BEAMED_GROUP_TYPE, m_groupType);
+ if (m_groupType == GROUP_TYPE_TUPLED) {
+ m_currentEvent->set
+ <Int>
+ (BEAMED_GROUP_TUPLET_BASE, m_groupTupletBase);
+ m_currentEvent->set
+ <Int>
+ (BEAMED_GROUP_TUPLED_COUNT, m_groupTupledCount);
+ m_currentEvent->set
+ <Int>
+ (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<MidiDevice *>
+ (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<MidiDevice*>(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<MidiDevice*>(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<MidiDevice*>(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<MidiDevice*>(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 <device> 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<MidiDevice*>(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<MidiDevice*>(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<MidiDevice *>
+ (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<MidiDevice *>(*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<MidiDevice *>(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<MidiDevice *>(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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_ROSEXMLHANDLER_H_
+#define _RG_ROSEXMLHANDLER_H_
+
+#include "base/Device.h"
+#include "base/MidiProgram.h"
+#include "gui/general/ProgressReporter.h"
+#include <map>
+#include <set>
+#include <string>
+#include <qstring.h>
+#include "base/Event.h"
+#include <qxml.h>
+
+
+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<QString> &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<long, long> m_groupIdMap;
+
+ bool m_foundTempo;
+
+ QString m_errorString;
+ std::set<QString> 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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "RosegardenGUIDoc.h"
+#include <kapplication.h>
+
+#include <qxml.h>
+#include "sound/Midi.h"
+#include "gui/editors/segment/TrackEditor.h"
+#include "gui/editors/segment/TrackButtons.h"
+#include <klocale.h>
+#include <kstddirs.h>
+#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 <kcommand.h>
+#include <kconfig.h>
+#include <kfilterdev.h>
+#include <kglobal.h>
+#include <kmessagebox.h>
+#include <kprocess.h>
+#include <kprogress.h>
+#include <ktempfile.h>
+#include <qcstring.h>
+#include <qdatastream.h>
+#include <qdialog.h>
+#include <qdir.h>
+#include <qfile.h>
+#include <qfileinfo.h>
+#include <qobject.h>
+#include <qstring.h>
+#include <qstringlist.h>
+#include <qtextstream.h>
+#include <qwidget.h>
+#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<QString> recordedOrphans;
+ std::vector<QString> derivedOrphans;
+
+ if (documentWillNotBeSaved) {
+
+ // All audio files recorded or derived in this session are
+ // about to become orphans
+
+ for (std::vector<AudioFile *>::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<QString>::iterator i = m_orphanedRecordedAudioFiles.begin();
+ i != m_orphanedRecordedAudioFiles.end(); ++i) {
+
+ bool stillHave = false;
+
+ for (std::vector<AudioFile *>::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<QString>::iterator i = m_orphanedDerivedAudioFiles.begin();
+ i != m_orphanedDerivedAudioFiles.end(); ++i) {
+
+ bool stillHave = false;
+
+ for (std::vector<AudioFile *>::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("<qt>About to delete 1 audio file permanently from the hard disk.<br>There will be no way to recover this file.<br>Are you sure?</qt>\n", "<qt>About to delete %n audio files permanently from the hard disk.<br>There will be no way to recover these files.<br>Are you sure?</qt>", 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*>(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<char> 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<timeT, TimeSignature> ts =
+ doc->getComposition().getTimeSignatureChange(i);
+ getComposition().addTimeSignature(ts.first + time0, ts.second);
+ }
+ for (int i = 0; i < doc->getComposition().getTempoChangeCount(); ++i) {
+ std::pair<timeT, tempoT> 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<PluginContainer *> 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<PluginContainer *>::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*>(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 << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
+ << "<!DOCTYPE rosegarden-data>\n"
+ << "<rosegarden-data version=\"" << VERSION
+ << "\" format-version-major=\"" << FILE_FORMAT_VERSION_MAJOR
+ << "\" format-version-minor=\"" << FILE_FORMAT_VERSION_MINOR
+ << "\" format-version-point=\"" << FILE_FORMAT_VERSION_POINT
+ << "\">\n";
+
+ 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 << "<appearance>" << endl;
+ outStream << strtoqstr(getComposition().getSegmentColourMap().toXmlString("segmentmap"));
+ outStream << strtoqstr(getComposition().getGeneralColourMap().toXmlString("generalmap"));
+ outStream << "</appearance>" << endl << endl << endl;
+
+ // close the top-level XML tag
+ //
+ outStream << "</rosegarden-data>\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<DeviceId> devices)
+{
+ Profiler profiler("RosegardenGUIDoc::exportStudio");
+ RG_DEBUG << "RosegardenGUIDoc::exportStudio("
+ << filename << ")\n";
+
+ KFilterDev* fileCompressedDevice = static_cast<KFilterDev*>(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 << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
+ << "<!DOCTYPE rosegarden-data>\n"
+ << "<rosegarden-data version=\"" << VERSION << "\">\n";
+
+ // Send out the studio - a self contained command
+ //
+ outStream << strtoqstr(m_studio.toXmlString(devices)) << endl << endl;
+
+ // close the top-level XML tag
+ //
+ outStream << "</rosegarden-data>\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("<segment track=\"%1\" start=\"%2\" ")
+ .arg(segment->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 << " <begin index=\""
+ << time
+ << "\"/>\n";
+
+ time.sprintf("%d.%06d", segment->getAudioEndTime().sec,
+ segment->getAudioEndTime().usec());
+
+ outStream << " <end index=\""
+ << time
+ << "\"/>\n";
+
+ if (segment->isAutoFading()) {
+ time.sprintf("%d.%06d", segment->getFadeInTime().sec,
+ segment->getFadeInTime().usec());
+
+ outStream << " <fadein time=\""
+ << time
+ << "\"/>\n";
+
+ time.sprintf("%d.%06d", segment->getFadeOutTime().sec,
+ segment->getFadeOutTime().usec());
+
+ outStream << " <fadeout time=\""
+ << time
+ << "\"/>\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 << "<chord>" << 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 << "</chord>\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 << "</chord>\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 << "<gui>\n"; // gui elements
+ Segment::EventRulerListConstIterator it;
+ for (it = list.begin(); it != list.end(); ++it) {
+ outStream << " <controller type=\"" << strtoqstr((*it)->m_type);
+
+ if ((*it)->m_type == Controller::EventType) {
+ outStream << "\" value =\"" << (*it)->m_controllerValue;
+ }
+
+ outStream << "\"/>\n";
+ }
+ outStream << "</gui>\n";
+ }
+
+ }
+
+
+ outStream << "</segment>\n"; //-------------------------
+
+}
+
+bool RosegardenGUIDoc::isSequencerRunning()
+{
+ RosegardenGUIApp* parentApp = dynamic_cast<RosegardenGUIApp*>(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("<h3>Audio and plugins not available</h3><p>This composition uses audio files or plugins, but Rosegarden is currently running without audio because the JACK audio server was not available on startup.</p><p>Please exit Rosegarden, start the JACK audio server and re-start Rosegarden if you wish to load this complete composition.</p><p><b>WARNING:</b> If you re-save this composition, all audio and plugin data and settings in it will be lost.</p>"));
+#else
+ KMessageBox::information
+ (0, i18n("<h3>Audio and plugins not available</h3><p>This composition uses audio files or plugins, but you are running a version of Rosegarden that was compiled without audio support.</p><p><b>WARNING:</b> If you re-save this composition from this version of Rosegarden, all audio and plugin data and settings in it will be lost.</p>"));
+#endif
+ }
+ CurrentProgressDialog::thaw();
+
+ } else {
+
+ bool shownWarning = false;
+
+ int sr = 0;
+ if (getSequenceManager()) {
+ sr = getSequenceManager()->getSampleRate();
+ }
+
+ int er = m_audioFileManager.getExpectedSampleRate();
+
+ std::set<int> rates = m_audioFileManager.getActualSampleRates();
+ bool other = false;
+ bool mixed = (rates.size() > 1);
+ for (std::set<int>::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("<h3>Incorrect audio sample rate</h3><p>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).</p><p>Rosegarden will play this composition at the correct speed, but any audio files in it will probably sound awful.</p><p>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.</p>").arg(er).arg(sr).arg(er));
+
+ CurrentProgressDialog::thaw();
+ shownWarning = true;
+
+ } else if (sr != 0 && mixed) {
+
+ KStartupLogo::hideIfStillThere();
+ CurrentProgressDialog::freeze();
+
+ KMessageBox::information(0, i18n("<h3>Inconsistent audio sample rates</h3><p>This composition contains audio files at more than one sample rate.</p><p>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.</p><p>Please see the audio file manager dialog for more details, and consider resampling any files that are at the wrong rate.</p>").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("<h3>Plugins not found</h3><p>The following audio plugins could not be loaded:</p><ul>"));
+
+ for (std::set<QString>::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("<li>%1 (from %2)</li>").arg(label).arg(pluginFileName);
+ }
+ msg += "</ul>";
+
+ 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
+ <Int>(PITCH, pitch);
+ rEvent->set
+ <Int>(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
+ <Int>(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
+ <Int>(RECORDED_CHANNEL, channel);
+ break;
+
+ case MappedEvent::MidiController:
+ rEvent = Controller
+ ((*i)->getData1(), (*i)->getData2()).getAsEvent(absTime);
+ rEvent->set
+ <Int>(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
+ <Int>(RECORDED_CHANNEL, channel);
+ break;
+
+ case MappedEvent::MidiChannelPressure:
+ rEvent = ChannelPressure
+ ((*i)->getData1()).getAsEvent(absTime);
+ rEvent->set
+ <Int>(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
+ <Int>(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
+ <Int>(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<RecordingSegmentMap::iterator> 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<MidiDevice *>(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<MidiDevice *>(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<MidiControlPair> 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<MidiControlPair>::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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#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 <map>
+#include "sound/AudioFileManager.h"
+// #include <qlist.h> (fixes problem for Adam Dingle)
+#include <qobject.h>
+#include <qstring.h>
+#include <qstringlist.h>
+#include <vector>
+#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<DeviceId> devices =
+ std::vector<DeviceId>());
+
+ /**
+ * 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<RosegardenGUIApp*>(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<RosegardenGUIView>& 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<NoteOnRec> 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<RosegardenGUIView> m_viewList;
+
+ /**
+ * the list of the edit views currently editing a part of this document
+ */
+ QList<EditViewBase> 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<InstrumentId, Segment *> 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<int, NoteOnRecSet> PitchMap;
+
+ /**
+ * a map[Channel] of PitchMap
+ */
+ typedef std::map<int, PitchMap> ChanMap;
+
+ /**
+ * a map[Port] of ChanMap
+ */
+ typedef std::map<int, ChanMap> 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<QString> m_orphanedRecordedAudioFiles;
+ std::vector<QString> 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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "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 <qstring.h>
+
+
+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
+ <Bool>(qstrtostr(attrName), valLowerCase == "true");
+
+ } else {
+
+ // Not a bool, check if integer val
+ numVal = val.toInt(&isNumeric);
+ if (isNumeric) {
+ set
+ <Int>(qstrtostr(attrName), numVal);
+ } else {
+ // not an int either, default to string
+ set
+ <String>(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
+ <Bool>(qstrtostr(name), attrVal.lower() == "true",
+ persistent);
+ have = true;
+ } else if (attrName == "int") {
+ set
+ <Int>(qstrtostr(name), attrVal.toInt(), persistent);
+ have = true;
+ } else if (attrName == "string") {
+ set
+ <String>(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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_XMLSTORABLEEVENT_H_
+#define _RG_XMLSTORABLEEVENT_H_
+
+#include "base/Event.h"
+#include <qxml.h>
+
+
+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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#include "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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_XMLSUBHANDLER_H_
+#define _RG_XMLSUBHANDLER_H_
+
+#include <qstring.h>
+#include <qxml.h>
+
+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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "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 <qobject.h>
+#include <fstream>
+#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
+ <Int>(BaseProperties::PITCH, pitch);
+
+ long velocity = 127;
+ (*j)->get
+ <Int>(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<timeT, tempoT> 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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_CSOUNDEXPORTER_H_
+#define _RG_CSOUNDEXPORTER_H_
+
+#include "gui/general/ProgressReporter.h"
+#include <string>
+
+
+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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "HydrogenLoader.h"
+
+#include <qxml.h>
+#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 <qfile.h>
+#include <qobject.h>
+#include <qstring.h>
+
+
+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;
+ 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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_HYDROGENLOADER_H_
+#define _RG_HYDROGENLOADER_H_
+
+#include "base/PropertyName.h"
+#include "gui/general/ProgressReporter.h"
+#include <string>
+#include <vector>
+
+
+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<std::pair<std::string, Segment*> > SegmentMap;
+typedef std::vector<std::pair<std::string, Segment*> >::iterator SegmentMapIterator;
+typedef std::vector<std::pair<std::string, Segment*> >::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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "HydrogenXMLHandler.h"
+
+#include "base/Event.h"
+#include "base/BaseProperties.h"
+#include <klocale.h>
+#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 <qstring.h>
+
+
+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
+ <Int>(
+ BaseProperties::PITCH, 36 + m_instrument);
+ noteEvent->set
+ <Int>(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,
+ "<blank spacer>", 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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_HYDROGENXMLHANDLER_H_
+#define _RG_HYDROGENXMLHANDLER_H_
+
+#include "HydrogenLoader.h"
+#include "base/MidiProgram.h"
+#include "base/Track.h"
+#include <string>
+#include <qstring.h>
+#include <vector>
+#include <qxml.h>
+
+
+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<double> 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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ This file is Copyright 2002
+ Hans Kieserman <hkieserman@mail.com>
+ with heavy lifting from csoundio as it was on 13/5/2002.
+
+ Numerous additions and bug fixes by
+ Michael McIntyre <dmmcintyr@users.sourceforge.net>
+
+ 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 <klocale.h>
+#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 <kconfig.h>
+#include <kmessagebox.h>
+#include <qfileinfo.h>
+#include <qobject.h>
+#include <qregexp.h>
+#include <qstring.h>
+#include <qtextcodec.h>
+#include <kapplication.h>
+#include <sstream>
+#include <algorithm>
+
+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
+ <Bool>(NotationProperties::SLUR_ABOVE))
+ str << "^( ";
+ else
+ str << "_( ";
+ } else if (i.getIndicationType() == Indication::PhrasingSlur) {
+ if ((*m)->get
+ <Bool>(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<std::string> 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<String>(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<timeT, long> 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 <untitled> 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
+ <String>(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<Int>(Text::LyricVersePropertyName, verse);
+
+ if (verse == currentVerse) {
+ std::string ssyllable;
+ (*j)->get<String>(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<int, int> &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<Int>(NOTE_TYPE);
+ int dots = (*i)->get<Int>(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<Bool>(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<int,int> barDurationRatio(timeSignature.getNumerator(),timeSignature.getDenominator());
+ std::pair<int,int> durationRatioSum(0,1);
+ static std::pair<int,int> 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<int, int> 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
+ <Int>(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
+ <String>(BEAMED_GROUP_TYPE, groupType);
+
+ if (groupType == GROUP_TYPE_TUPLED) {
+ long numerator = 0;
+ long denominator = 0;
+ (*i)->get
+ <Int>(BEAMED_GROUP_TUPLED_COUNT, numerator);
+ (*i)->get
+ <Int>(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<int, int>(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<int, int>(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<int, int>(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<Bool>(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
+ <Int>(DISPLACED_X)) / 1000;
+ str << "\\once \\override NoteColumn #'force-hshift = #"
+ << xDisplacement << " ";
+ }
+
+ bool hiddenNote = false;
+ if (e->has(INVISIBLE)) {
+ if (e->get
+ <Bool>(INVISIBLE)) {
+ hiddenNote = true;
+ }
+ }
+
+ if ( hiddenNote ) {
+ str << "\\hideNotes ";
+ }
+
+ if (e->has(NotationProperties::STEM_UP)) {
+ if (e->get
+ <Bool>(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
+ <Bool>(NotationProperties::USE_CAUTIONARY_ACCIDENTAL, noteHasCautionaryAccidental);
+ if (noteHasCautionaryAccidental)
+ str << "?";
+
+ // get TIED_FORWARD and TIE_IS_ABOVE for later
+ (*i)->get<Bool>(TIED_FORWARD, tiedForward);
+ (*i)->get<Bool>(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<int,int> 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<Mark> marks(chord.getMarksForChord());
+ // problem here: stem direction unavailable (it's a view-local property)
+ bool stemUp = true;
+ e->get
+ <Bool>(NotationProperties::STEM_UP, stemUp);
+ for (std::vector<Mark>::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
+ <Bool>(INVISIBLE)) {
+ hiddenRest = true;
+ }
+ }
+
+ bool offsetRest = false;
+ int restOffset = 0;
+ if ((*i)->has(DISPLACED_Y)) {
+ restOffset = (*i)->get<Int>(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<int,int> 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 <Bool>(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<int, int>(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<int,int>
+LilyPondExporter::writeSkip(const TimeSignature &timeSig,
+ timeT offset,
+ timeT duration,
+ bool useRests,
+ std::ofstream &str)
+{
+ DurationList dlist;
+ timeSig.getDurationListForInterval(dlist, duration, offset);
+ std::pair<int,int> durationRatioSum(0,1);
+ std::pair<int,int> 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
+ <String>(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
+ <Int>(PITCH, pitch);
+
+ Accidental accidental = Accidentals::NoAccidental;
+ note->get
+ <String>(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
+ <String>(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<int,int>
+LilyPondExporter::writeDuration(timeT duration,
+ std::ofstream &str)
+{
+ Note note(Note::getNearestNote(duration, MAX_DOTS));
+ std::pair<int,int> durationRatio(0,1);
+
+ switch (note.getNoteType()) {
+
+ case Note::SixtyFourthNote:
+ str << "64"; durationRatio = std::pair<int,int>(1,64);
+ break;
+
+ case Note::ThirtySecondNote:
+ str << "32"; durationRatio = std::pair<int,int>(1,32);
+ break;
+
+ case Note::SixteenthNote:
+ str << "16"; durationRatio = std::pair<int,int>(1,16);
+ break;
+
+ case Note::EighthNote:
+ str << "8"; durationRatio = std::pair<int,int>(1,8);
+ break;
+
+ case Note::QuarterNote:
+ str << "4"; durationRatio = std::pair<int,int>(1,4);
+ break;
+
+ case Note::HalfNote:
+ str << "2"; durationRatio = std::pair<int,int>(1,2);
+ break;
+
+ case Note::WholeNote:
+ str << "1"; durationRatio = std::pair<int,int>(1,1);
+ break;
+
+ case Note::DoubleWholeNote:
+ str << "\\breve"; durationRatio = std::pair<int,int>(2,1);
+ break;
+ }
+
+ for (int numDots = 0; numDots < note.getDots(); numDots++) {
+ str << ".";
+ }
+ durationRatio = fractionProduct(durationRatio,
+ std::pair<int,int>((1<<(note.getDots()+1))-1,1<<note.getDots()));
+ return durationRatio;
+}
+
+void
+LilyPondExporter::writeSlashes(const Event *note, std::ofstream &str)
+{
+ // write slashes after text
+ // / = 8 // = 16 /// = 32, etc.
+ long slashes = 0;
+ note->get
+ <Int>(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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ This file is Copyright 2002
+ Hans Kieserman <hkieserman@mail.com>
+ with heavy lifting from csoundio as it was on 13/5/2002.
+
+ Numerous additions and bug fixes by
+ Michael McIntyre <dmmcintyr@users.sourceforge.net>
+
+ 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 <fstream>
+#include <set>
+#include <string>
+#include <utility>
+
+
+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<Event*, Event::EventCmp> eventstartlist;
+ typedef std::multiset<Event*, Event::EventEndCmp> 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<int, int> &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<int,int> 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<int,int> 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<int,int> fractionSum(std::pair<int,int> x,std::pair<int,int> y) {
+ std::pair<int,int> z(
+ x.first * y.second + x.second * y.first,
+ x.second * y.second);
+ return fractionSimplify(z);
+ }
+ std::pair<int,int> fractionProduct(std::pair<int,int> x,std::pair<int,int> y) {
+ std::pair<int,int> z(
+ x.first * y.first,
+ x.second * y.second);
+ return fractionSimplify(z);
+ }
+ std::pair<int,int> fractionProduct(std::pair<int,int> x,int y) {
+ std::pair<int,int> z(
+ x.first * y,
+ x.second);
+ return fractionSimplify(z);
+ }
+ bool fractionSmaller(std::pair<int,int> x,std::pair<int,int> y) {
+ return (x.first * y.second < x.second * y.first);
+ }
+ std::pair<int,int> fractionSimplify(std::pair<int,int> x) {
+ return std::pair<int,int>(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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "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 <qobject.h>
+
+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<timeT, TimeSignature> 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<Int>(NOTE_TYPE);
+ int dots = e->get
+ <Int>(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
+ <Int>(BEAMED_GROUP_ID);
+ string type = e->get
+ <String>(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
+ <Int>(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
+ <Int>(PITCH, pitch)) {
+ str << "c"; // have to write something, or it won't parse
+ return ;
+ }
+
+ Accidental accidental = Accidentals::NoAccidental;
+ (void)event->get
+ <String>(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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_MUPEXPORTER_H_
+#define _RG_MUPEXPORTER_H_
+
+#include "base/Track.h"
+#include "gui/general/ProgressReporter.h"
+#include <map>
+#include <string>
+#include <utility>
+#include "base/Event.h"
+#include "base/NotationTypes.h"
+#include <fstream>
+
+
+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<Clef, Rosegarden::Key> ClefKeyPair;
+ typedef std::map<TrackId, ClefKeyPair> 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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ This file is Copyright 2002
+ Hans Kieserman <hkieserman@mail.com>
+ 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 <qobject.h>
+
+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<note>" << std::endl;
+
+ Pitch pitch(64);
+ Accidental acc;
+ Accidental displayAcc;
+ bool cautionary;
+ Accidental processedDisplayAcc;
+
+ if (e->isa(Note::EventRestType)) {
+ str << "\t\t\t\t<rest/>" << 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<chord/>" << std::endl;
+ } else {
+ accTable.update();
+ }
+
+ str << "\t\t\t\t<pitch>" << std::endl;
+
+ long p = 0;
+ e->get<Int>(PITCH, p);
+ pitch = p;
+
+ str << "\t\t\t\t\t<step>" << pitch.getNoteName(key) << "</step>" << 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<alter>-2</alter>" << std::endl;
+ } else if (acc == Accidentals::Flat) {
+ str << "\t\t\t\t\t<alter>-1</alter>" << std::endl;
+ } else if (acc == Accidentals::Sharp) {
+ str << "\t\t\t\t\t<alter>1</alter>" << std::endl;
+ } else if (acc == Accidentals::DoubleSharp) {
+ str << "\t\t\t\t\t<alter>2</alter>" << std::endl;
+ }
+
+ int octave = pitch.getOctave( -1);
+ str << "\t\t\t\t\t<octave>" << octave << "</octave>" << std::endl;
+
+ str << "\t\t\t\t</pitch>" << 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<duration>" << e->getNotationDuration() << "</duration>" << std::endl;
+
+ if (!e->isa(Note::EventRestType)) {
+
+ if (e->has(TIED_BACKWARD) &&
+ e->get
+ <Bool>(TIED_BACKWARD)) {
+ str << "\t\t\t\t<tie type=\"stop\"/>" << std::endl;
+ }
+ if (e->has(TIED_FORWARD) &&
+ e->get
+ <Bool>(TIED_FORWARD)) {
+ str << "\t\t\t\t<tie type=\"start\"/>" << std::endl;
+ }
+
+ // Incomplete: will RG ever use this?
+ str << "\t\t\t\t<voice>" << "1" << "</voice>" << 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<type>" << noteNames[noteType] << "</type>" << std::endl;
+ for (int i = 0; i < note.getDots(); ++i) {
+ str << "\t\t\t\t<dot/>" << std::endl;
+ }
+
+ if (!e->isa(Note::EventRestType)) {
+
+ if (processedDisplayAcc == Accidentals::DoubleFlat) {
+ str << "\t\t\t\t<accidental>flat-flat</accidental>" << std::endl;
+ } else if (processedDisplayAcc == Accidentals::Flat) {
+ str << "\t\t\t\t<accidental>flat</accidental>" << std::endl;
+ } else if (processedDisplayAcc == Accidentals::Natural) {
+ str << "\t\t\t\t<accidental>natural</accidental>" << std::endl;
+ } else if (processedDisplayAcc == Accidentals::Sharp) {
+ str << "\t\t\t\t<accidental>sharp</accidental>" << std::endl;
+ } else if (processedDisplayAcc == Accidentals::DoubleSharp) {
+ str << "\t\t\t\t<accidental>double-sharp</accidental>" << std::endl;
+ }
+
+ bool haveNotations = false;
+ if (e->has(TIED_BACKWARD) &&
+ e->get
+ <Bool>(TIED_BACKWARD)) {
+ if (!haveNotations) {
+ str << "\t\t\t\t<notations>" << std::endl;
+ haveNotations = true;
+ }
+ str << "\t\t\t\t\t<tied type=\"stop\"/>" << std::endl;
+ }
+ if (e->has(TIED_FORWARD) &&
+ e->get
+ <Bool>(TIED_FORWARD)) {
+ if (!haveNotations) {
+ str << "\t\t\t\t<notations>" << std::endl;
+ haveNotations = true;
+ }
+ str << "\t\t\t\t\t<tied type=\"start\"/>" << std::endl;
+ }
+ if (haveNotations) {
+ str << "\t\t\t\t</notations>" << std::endl;
+ }
+ }
+
+ // could also do <stem>down</stem> if you wanted
+ str << "\t\t\t</note>" << std::endl;
+}
+
+void
+MusicXmlExporter::writeKey(Rosegarden::Key whichKey, std::ofstream &str)
+{
+ str << "\t\t\t\t<key>" << std::endl;
+ str << "\t\t\t\t<fifths>"
+ << (whichKey.isSharp() ? "" : "-")
+ << (whichKey.getAccidentalCount()) << "</fifths>" << std::endl;
+ str << "\t\t\t\t<mode>";
+ if (whichKey.isMinor()) {
+ str << "minor";
+ } else {
+ str << "major";
+ }
+ str << "</mode>" << std::endl;
+ str << "\t\t\t\t</key>" << std::endl;
+}
+
+void
+MusicXmlExporter::writeTime(TimeSignature timeSignature, std::ofstream &str)
+{
+ str << "\t\t\t\t<time>" << std::endl;
+ str << "\t\t\t\t<beats>" << timeSignature.getNumerator() << "</beats>" << std::endl;
+ str << "\t\t\t\t<beat-type>" << timeSignature.getDenominator() << "</beat-type>" << std::endl;
+ str << "\t\t\t\t</time>" << std::endl;
+}
+
+void
+MusicXmlExporter::writeClef(Clef whichClef, std::ofstream &str)
+{
+ str << "\t\t\t\t<clef>" << std::endl;
+ if (whichClef == Clef::Treble) {
+ str << "\t\t\t\t<sign>G</sign>" << std::endl;
+ str << "\t\t\t\t<line>2</line>" << std::endl;
+ } else if (whichClef == Clef::Alto) {
+ str << "\t\t\t\t<sign>C</sign>" << std::endl;
+ str << "\t\t\t\t<line>3</line>" << std::endl;
+ } else if (whichClef == Clef::Tenor) {
+ str << "\t\t\t\t<sign>C</sign>" << std::endl;
+ str << "\t\t\t\t<line>4</line>" << std::endl;
+ } else if (whichClef == Clef::Bass) {
+ str << "\t\t\t\t<sign>F</sign>" << std::endl;
+ str << "\t\t\t\t<line>4</line>" << std::endl;
+ }
+ str << "\t\t\t\t</clef>" << 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 << "<?xml version=\"1.0\"?>" << std::endl;
+ str << "<!DOCTYPE score-partwise PUBLIC \"-//Recordare//DTD MusicXML 1.1 Partwise//EN\" \"http://www.musicxml.org/dtds/partwise.dtd\">" << std::endl;
+ // MusicXml header information
+ str << "<score-partwise>" << std::endl;
+ str << "\t<work> <work-title>" << XmlExportable::encode(m_fileName)
+ << "</work-title></work> " << std::endl;
+ // Movement, etc. info goes here
+ str << "\t<identification> " << std::endl;
+ if (composition->getCopyrightNote() != "") {
+ str << "\t\t<rights>"
+ << XmlExportable::encode(composition->getCopyrightNote())
+ << "</rights>" << std::endl;
+ }
+ str << "\t\t<encoding>" << std::endl;
+ // Incomplete: Insert date!
+ // str << "\t\t\t<encoding-date>" << << "</encoding-date>" << std::endl;
+ str << "\t\t\t<software>Rosegarden v" VERSION "</software>" << std::endl;
+ str << "\t\t</encoding>" << std::endl;
+ str << "\t</identification> " << std::endl;
+
+ // MIDI information
+ str << "\t<part-list>" << 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<score-part id=\"" << numToId((*i).first) << "\">" << std::endl;
+ str << "\t\t\t<part-name>" << XmlExportable::encode((*i).second->getLabel()) << "</part-name>" << 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\t<score-instrument id=\"" << trackInstrument->getName() << "\">" << std::endl;
+ str << "\t\t\t\t<instrument-name>" << trackInstrument->getType() << "</instrument-name>" << std::endl;
+ str << "\t\t\t</score-instrument>" << std::endl;
+ str << "\t\t\t<midi-instrument id=\"" << trackInstrument->getName() << "\">" << std::endl;
+ str << "\t\t\t\t<midi-channel>" << ((unsigned int)trackInstrument->getMidiChannel() + 1) << "</midi-channel>" << std::endl;
+ if (trackInstrument->sendsProgramChange()) {
+ str << "\t\t\t\t<midi-program>" << ((unsigned int)trackInstrument->getProgramChange() + 1) << "</midi-program>" << std::endl;
+ }
+ str << "\t\t\t</midi-instrument>" << std::endl;
+*/
+ }
+ str << "\t\t</score-part>" << std::endl;
+
+ emit setProgress(int(double(trackNo++) / double(tracks.size()) * 20.0));
+ rgapp->refreshGUI(50);
+
+ } // end track iterator
+ str << "\t</part-list>" << 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<part id=\"" << numToId((*j).first) << "\">" << 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</attributes>" << std::endl;
+ startedAttributes = false;
+ }
+
+ while (measureNumber > oldMeasureNumber) {
+
+ bool first = (oldMeasureNumber < 0);
+
+ if (!first) {
+ if (startedAttributes) {
+ str << "\t\t\t</attributes>" << std::endl;
+ }
+ str << "\t\t</measure>\n" << std::endl;
+ }
+
+ ++oldMeasureNumber;
+
+ str << "\t\t<measure number=\"" << (oldMeasureNumber + 1) << "\">" << std::endl;
+
+ if (first) {
+ str << "\t\t\t<attributes>" << std::endl;
+ // Divisions is divisions of crotchet (quarter-note) on which all
+ // note-lengths are based
+ str << "\t\t\t\t<divisions>" << Note(Note::Crotchet).getDuration() << "</divisions>" << 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<attributes>" << std::endl;
+ startedAttributes = true;
+ }
+ }
+
+ // process event
+ if (event->isa(Rosegarden::Key::EventType)) {
+
+ if (!startedAttributes) {
+ str << "\t\t\t<attributes>" << 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<attributes>" << 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</attributes>" << 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</attributes>" << std::endl;
+ startedAttributes = false;
+ }
+
+ str << "\t\t</measure>" << std::endl;
+ str << "\t</part>" << std::endl;
+ }
+
+ emit setProgress(20 +
+ int(double(trackNo++) / double(tracks.size()) * 80.0));
+ rgapp->refreshGUI(50);
+ }
+
+ str << "</score-partwise>" << 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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ This file is Copyright 2002
+ Hans Kieserman <hkieserman@mail.com>
+ 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 <fstream>
+#include <set>
+#include <string>
+
+
+class QObject;
+
+
+namespace Rosegarden
+{
+
+class RosegardenGUIDoc;
+class Key;
+class Clef;
+class AccidentalTable;
+
+
+/**
+ * MusicXml scorefile export
+ */
+
+class MusicXmlExporter : public ProgressReporter
+{
+public:
+ typedef std::multiset<Event*, Event::EventCmp> eventstartlist;
+ typedef std::multiset<Event*, Event::EventEndCmp> 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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "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 <qfile.h>
+#include <qobject.h>
+#include <qstring.h>
+#include <qstringlist.h>
+#include <qtextstream.h>
+#include <string>
+#include <vector>
+
+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<string> 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
+ <Int>(PITCH, pitch);
+
+ if (m_tieStatus == 1) {
+ noteEvent->set
+ <Bool>(TIED_FORWARD, true);
+ } else if (m_tieStatus == 2) {
+ noteEvent->set
+ <Bool>(TIED_BACKWARD, true);
+ }
+
+ if (marks.size() > 0) {
+ noteEvent->set
+ <Int>(MARK_COUNT, marks.size());
+ for (unsigned int j = 0; j < marks.size(); ++j) {
+ noteEvent->set
+ <String>(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
+ <Int>(BEAMED_GROUP_ID, m_groupId);
+ e->set
+ <String>(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
+ <Bool>(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<Int>("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
+ <Int>
+ //!!! (Indication::IndicationDurationPropertyName,
+ ("indicationduration",
+ m_currentSegmentTime - indicationEvent->getAbsoluteTime());
+}
+
+void RG21Loader::closeGroup()
+{
+ if (m_groupType == GROUP_TYPE_TUPLED) {
+
+ Segment::iterator i = m_currentSegment->end();
+ vector<Event *> toInsert;
+ vector<Segment::iterator> toErase;
+
+ if (i != m_currentSegment->begin()) {
+
+ --i;
+ long groupId;
+ timeT prev = m_groupStartTime + m_groupTupledLength;
+
+ while ((*i)->get
+ <Int>(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
+ <Int>(BEAMED_GROUP_TUPLET_BASE,
+ m_groupUntupledLength / m_groupUntupledCount);
+ e->set
+ <Int>(BEAMED_GROUP_TUPLED_COUNT,
+ m_groupTupledLength * m_groupUntupledCount /
+ m_groupUntupledLength);
+ e->set
+ <Int>(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
+ <Int>("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;
+ 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<string> RG21Loader::convertRG21ChordMods(int chordMods)
+{
+ vector<string> 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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_RG21LOADER_H_
+#define _RG_RG21LOADER_H_
+
+#include "base/MidiProgram.h"
+#include "base/NotationTypes.h"
+#include "gui/general/ProgressReporter.h"
+#include <map>
+#include <string>
+#include <qstring.h>
+#include <qstringlist.h>
+#include <vector>
+#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<std::string> 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<int, Event *> 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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ This file is Copyright 2005
+ Toni Arnold <toni__arnold@bluewin.ch>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either 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 <qobject.h>
+#include <qsocketnotifier.h>
+#include <fcntl.h>
+#include <cstdlib>
+
+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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ This file is Copyright 2005
+ Toni Arnold <toni__arnold@bluewin.ch>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 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 <qobject.h>
+#include <lirc/lirc_client.h>
+
+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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ This file is Copyright 2005
+ Toni Arnold <toni__arnold@bluewin.ch>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either 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 <qobject.h>
+
+
+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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ This file is Copyright 2005
+ Toni Arnold <toni__arnold@bluewin.ch>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 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 <qobject.h>
+#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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "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 <kcmdlineargs.h>
+#include <klocale.h>
+#include <kmainwindow.h>
+#include <kmessagebox.h>
+#include <kprocess.h>
+#include <kuniqueapplication.h>
+#include <qcstring.h>
+#include <qeventloop.h>
+#include <qsessionmanager.h>
+#include <qstring.h>
+#include <dcopclient.h>
+#include <kconfig.h>
+#include <kstatusbar.h>
+
+
+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<KMainWindow*>(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<RosegardenApplication*>(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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_ROSEGARDENAPPLICATION_H_
+#define _RG_ROSEGARDENAPPLICATION_H_
+
+#include <kuniqueapplication.h>
+#include <qcstring.h>
+#include <qstring.h>
+
+
+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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <bownie@bownie.com>
+
+ 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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "RosegardenGUIApp.h"
+#include <kapplication.h>
+
+#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 <dcopclient.h>
+#include <dcopobject.h>
+#include <dcopref.h>
+#include <kaction.h>
+#include <kconfig.h>
+#include <kdcopactionproxy.h>
+#include <kdockwidget.h>
+#include <kedittoolbar.h>
+#include <kfiledialog.h>
+#include <kglobal.h>
+#include <kinputdialog.h>
+#include <kio/netaccess.h>
+#include <kkeydialog.h>
+#include <klocale.h>
+#include <kmainwindow.h>
+#include <kmessagebox.h>
+#include <kmimetype.h>
+#include <kprocess.h>
+#include <kstatusbar.h>
+#include <kstdaccel.h>
+#include <kstdaction.h>
+#include <kstddirs.h>
+#include <ktempfile.h>
+#include <ktip.h>
+#include <ktoolbar.h>
+#include <kurl.h>
+#include <kxmlguiclient.h>
+#include <qaccel.h>
+#include <qcanvas.h>
+#include <qcstring.h>
+#include <qcursor.h>
+#include <qdatastream.h>
+#include <qdialog.h>
+#include <qdir.h>
+#include <qfile.h>
+#include <qfileinfo.h>
+#include <qiconset.h>
+#include <qinputdialog.h>
+#include <qlabel.h>
+#include <qobject.h>
+#include <qobjectlist.h>
+#include <qpixmap.h>
+#include <qpopupmenu.h>
+#include <qpushbutton.h>
+#include <qregexp.h>
+#include <qslider.h>
+#include <qstring.h>
+#include <qstringlist.h>
+#include <qtextcodec.h>
+#include <qtimer.h>
+#include <qvaluevector.h>
+#include <qwidget.h>
+
+#ifdef HAVE_LIBJACK
+#include <jack/jack.h>
+#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<QPopupMenu*>(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<QButton*>(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<double> zoomSizes; // in units-per-pixel
+ double defaultBarWidth44 = 100.0;
+ double duration44 = TimeSignature(4, 4).getBarDuration();
+ static double factors[] = { 0.025, 0.05, 0.1, 0.2, 0.5,
+ 1.0, 1.5, 2.5, 5.0, 10.0 , 20.0 };
+
+ 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<double>
+ (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
+ <String>
+ (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<KToggleAction *>
+ (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
+ <Int>
+ (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<timeT, timeT> 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<AudioSegmentRescaleCommand *> 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<KToggleAction*>(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<TrackId> 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
+ <String>
+ (Text::TextPropertyName, text)) {
+
+ if (!codec)
+ codec = guessTextCodec(text);
+
+ if (codec) {
+ (*j)->set
+ <String>
+ (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<timeT, TimeSignature> t1 =
+ c1.getTimeSignatureChange(i);
+ std::pair<timeT, TimeSignature> 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<timeT, tempoT> t1 = c1.getTempoChange(i);
+ std::pair<timeT, tempoT> 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<timeT, timeT> 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<String>
+ (DocumentConfiguration::TransportMode) != modeAsString) {
+
+ m_doc->getConfiguration().set<String>
+ (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("<h3>Helper programs not found</h3><p>Rosegarden could not find one or more helper programs which it needs to provide some features. The following features will not be available:</p>");
+ message += i18n("<ul>");
+ for (int i = 0; i < missingFeatures.count(); ++i) {
+ message += i18n("<li>%1</li>").arg(missingFeatures[i]);
+ }
+ message += i18n("</ul>");
+ message += i18n("<p>To fix this, you should install the following additional programs:</p>");
+ message += i18n("<ul>");
+ for (int i = 0; i < allMissing.count(); ++i) {
+ message += i18n("<li>%1</li>").arg(allMissing[i]);
+ }
+ message += i18n("</ul>");
+
+ 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<KProcess *, KTempFile *> 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<Int>
+ (DocumentConfiguration::ZoomLevel) != newZoom) {
+
+ m_doc->getConfiguration().set<Int>
+ (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<timeT, tempoT> tc =
+ comp.getTempoChange(index);
+ std::pair<bool, tempoT> 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<TransportDialog*>(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<KToolBar> 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<QString>
+RosegardenGUIApp::createRecordAudioFiles(const QValueVector<InstrumentId> &recordInstruments)
+{
+ QValueVector<QString> 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<InstrumentId>
+RosegardenGUIApp::getArmedInstruments()
+{
+ std::set
+ <InstrumentId> 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<InstrumentId> iv;
+ for (std::set
+ <InstrumentId>::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
+ <ControlEditorDialog *>::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
+ <ControlEditorDialog *>::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<QPopupMenu*>(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<DeviceId> midiPlayDevices;
+
+ for (DeviceList::const_iterator i =
+ oldStudio.begin(); i != oldStudio.end(); ++i) {
+
+ MidiDevice *md =
+ dynamic_cast<MidiDevice *>(*i);
+
+ if (md && (md->getDirection() == MidiDevice::Play)) {
+ midiPlayDevices.push_back((*i)->getId());
+ }
+ }
+
+ std::vector<DeviceId>::iterator di(midiPlayDevices.begin());
+
+ for (DeviceList::const_iterator i =
+ newStudio.begin(); i != newStudio.end(); ++i) {
+
+ MidiDevice *md =
+ dynamic_cast<MidiDevice *>(*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<QDialog *>(*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("<h3>Newer version available</h3><p>A newer version of Rosegarden may be available.<br>Please consult the <a href=\"http://www.rosegardenmusic.com/getting/\">Rosegarden website</a> for more information.</p>"),
+ 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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "RosegardenGUIApp.h"
+#include <kapplication.h>
+
+#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 <dcopclient.h>
+#include <dcopobject.h>
+#include <dcopref.h>
+#include <kaction.h>
+#include <kconfig.h>
+#include <kdcopactionproxy.h>
+#include <kdockwidget.h>
+#include <kedittoolbar.h>
+#include <kfiledialog.h>
+#include <kglobal.h>
+#include <kinputdialog.h>
+#include <kio/netaccess.h>
+#include <kkeydialog.h>
+#include <klocale.h>
+#include <kmainwindow.h>
+#include <kmessagebox.h>
+#include <kmimetype.h>
+#include <kprocess.h>
+#include <kstatusbar.h>
+#include <kstdaccel.h>
+#include <kstdaction.h>
+#include <kstddirs.h>
+#include <ktempfile.h>
+#include <ktip.h>
+#include <ktoolbar.h>
+#include <kurl.h>
+#include <kxmlguiclient.h>
+#include <qaccel.h>
+#include <qcanvas.h>
+#include <qcstring.h>
+#include <qcursor.h>
+#include <qdatastream.h>
+#include <qdialog.h>
+#include <qdir.h>
+#include <qfile.h>
+#include <qfileinfo.h>
+#include <qiconset.h>
+#include <qinputdialog.h>
+#include <qlabel.h>
+#include <qobject.h>
+#include <qobjectlist.h>
+#include <qpixmap.h>
+#include <qpopupmenu.h>
+#include <qpushbutton.h>
+#include <qregexp.h>
+#include <qslider.h>
+#include <qstring.h>
+#include <qstringlist.h>
+#include <qtextcodec.h>
+#include <qtimer.h>
+#include <qvaluevector.h>
+#include <qwidget.h>
+
+#ifdef HAVE_LIBJACK
+#include <jack/jack.h>
+#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<QPopupMenu*>(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<QButton*>(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<double> zoomSizes; // in units-per-pixel
+ double defaultBarWidth44 = 100.0;
+ double duration44 = TimeSignature(4, 4).getBarDuration();
+ static double factors[] = { 0.025, 0.05, 0.1, 0.2, 0.5,
+ 1.0, 1.5, 2.5, 5.0, 10.0 , 20.0 };
+
+ 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<double>
+ (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
+ <String>
+ (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<KToggleAction *>
+ (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
+ <Int>
+ (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<timeT, timeT> 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<AudioSegmentRescaleCommand *> 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<KToggleAction*>(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<TrackId> 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
+ <String>
+ (Text::TextPropertyName, text)) {
+
+ if (!codec)
+ codec = guessTextCodec(text);
+
+ if (codec) {
+ (*j)->set
+ <String>
+ (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<timeT, TimeSignature> t1 =
+ c1.getTimeSignatureChange(i);
+ std::pair<timeT, TimeSignature> 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<timeT, tempoT> t1 = c1.getTempoChange(i);
+ std::pair<timeT, tempoT> 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<timeT, timeT> 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<String>
+ (DocumentConfiguration::TransportMode) != modeAsString) {
+
+ m_doc->getConfiguration().set<String>
+ (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("<h3>Helper programs not found</h3><p>Rosegarden could not find one or more helper programs which it needs to provide some features. The following features will not be available:</p>");
+ message += i18n("<ul>");
+ for (int i = 0; i < missingFeatures.count(); ++i) {
+ message += i18n("<li>%1</li>").arg(missingFeatures[i]);
+ }
+ message += i18n("</ul>");
+ message += i18n("<p>To fix this, you should install the following additional programs:</p>");
+ message += i18n("<ul>");
+ for (int i = 0; i < allMissing.count(); ++i) {
+ message += i18n("<li>%1</li>").arg(allMissing[i]);
+ }
+ message += i18n("</ul>");
+
+ 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<KProcess *, KTempFile *> 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<Int>
+ (DocumentConfiguration::ZoomLevel) != newZoom) {
+
+ m_doc->getConfiguration().set<Int>
+ (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<timeT, tempoT> tc =
+ comp.getTempoChange(index);
+ std::pair<bool, tempoT> 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<TransportDialog*>(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<KToolBar> 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<QString>
+RosegardenGUIApp::createRecordAudioFiles(const QValueVector<InstrumentId> &recordInstruments)
+{
+ QValueVector<QString> 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<InstrumentId>
+RosegardenGUIApp::getArmedInstruments()
+{
+ std::set
+ <InstrumentId> 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<InstrumentId> iv;
+ for (std::set
+ <InstrumentId>::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
+ <ControlEditorDialog *>::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
+ <ControlEditorDialog *>::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<QPopupMenu*>(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<DeviceId> midiPlayDevices;
+
+ for (DeviceList::const_iterator i =
+ oldStudio.begin(); i != oldStudio.end(); ++i) {
+
+ MidiDevice *md =
+ dynamic_cast<MidiDevice *>(*i);
+
+ if (md && (md->getDirection() == MidiDevice::Play)) {
+ midiPlayDevices.push_back((*i)->getId());
+ }
+ }
+
+ std::vector<DeviceId>::iterator di(midiPlayDevices.begin());
+
+ for (DeviceList::const_iterator i =
+ newStudio.begin(); i != newStudio.end(); ++i) {
+
+ MidiDevice *md =
+ dynamic_cast<MidiDevice *>(*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<QDialog *>(*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("<h3>Newer version available</h3><p>A newer version of Rosegarden may be available.<br>Please consult the <a href=\"http://www.rosegardenmusic.com/getting/\">Rosegarden website</a> for more information.</p>"),
+ 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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_ROSEGARDENGUIAPP_H_
+#define _RG_ROSEGARDENGUIAPP_H_
+
+#include <map>
+#include <set>
+#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 <kdockwidget.h>
+#include <qstring.h>
+#include <qvaluevector.h>
+
+
+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<QString> createRecordAudioFiles(const QValueVector<InstrumentId> &);
+
+ QString getAudioFilePath();
+
+ //!!!mtr
+ QValueVector<InstrumentId> 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<double> *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<ControlEditorDialog *> m_controlEditors;
+ std::map<int, AudioPluginDialog*> m_pluginDialogs;
+#ifdef HAVE_LIBLO
+ AudioPluginOSCGUIManager *m_pluginGUIManager;
+#endif
+
+ static RosegardenGUIApp *m_myself;
+
+ static std::map<KProcess *, KTempFile *> 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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "RosegardenGUIView.h"
+#include <kapplication.h>
+
+#include "sound/Midi.h"
+#include "gui/editors/segment/TrackButtons.h"
+#include <klocale.h>
+#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 <kcommand.h>
+#include <kconfig.h>
+#include <kmessagebox.h>
+#include <kprocess.h>
+#include <qapplication.h>
+#include <qcursor.h>
+#include <qdialog.h>
+#include <qfileinfo.h>
+#include <qobject.h>
+#include <qstring.h>
+#include <qvbox.h>
+#include <qwidget.h>
+
+
+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<Segment *> 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<const QWidget *>(sender());
+ QWidget *w = const_cast<QWidget *>(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<Segment *> 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<Segment *> segmentsToEdit)
+{
+ NotationView *view = createNotationView(segmentsToEdit);
+ if (view)
+ view->show();
+}
+
+NotationView *
+RosegardenGUIView::createNotationView(std::vector<Segment *> 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<Segment *>)),
+ this, SLOT(slotEditSegmentsNotation(std::vector<Segment *>)));
+ connect(notationView, SIGNAL(openInMatrix(std::vector<Segment *>)),
+ this, SLOT(slotEditSegmentsMatrix(std::vector<Segment *>)));
+ connect(notationView, SIGNAL(openInPercussionMatrix(std::vector<Segment *>)),
+ this, SLOT(slotEditSegmentsPercussionMatrix(std::vector<Segment *>)));
+ connect(notationView, SIGNAL(openInEventList(std::vector<Segment *>)),
+ this, SLOT(slotEditSegmentsEventList(std::vector<Segment *>)));
+/* 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<Segment *> 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<Segment *> 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<Segment *> segmentsToEdit)
+{
+ int count = 0;
+ for (std::vector<Segment *>::iterator i = segmentsToEdit.begin();
+ i != segmentsToEdit.end(); ++i) {
+ std::vector<Segment *> tmpvec;
+ tmpvec.push_back(*i);
+ MatrixView *view = createMatrixView(tmpvec, false);
+ if (view) {
+ view->show();
+ if (++count == maxEditorsToOpen)
+ break;
+ }
+ }
+}
+
+void RosegardenGUIView::slotEditSegmentsPercussionMatrix(std::vector<Segment *> segmentsToEdit)
+{
+ int count = 0;
+ for (std::vector<Segment *>::iterator i = segmentsToEdit.begin();
+ i != segmentsToEdit.end(); ++i) {
+ std::vector<Segment *> tmpvec;
+ tmpvec.push_back(*i);
+ MatrixView *view = createMatrixView(tmpvec, true);
+ if (view) {
+ view->show();
+ if (++count == maxEditorsToOpen)
+ break;
+ }
+ }
+}
+
+MatrixView *
+RosegardenGUIView::createMatrixView(std::vector<Segment *> 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<Segment *>)),
+ this, SLOT(slotEditSegmentsNotation(std::vector<Segment *>)));
+ connect(matrixView, SIGNAL(openInMatrix(std::vector<Segment *>)),
+ this, SLOT(slotEditSegmentsMatrix(std::vector<Segment *>)));
+ connect(matrixView, SIGNAL(openInEventList(std::vector<Segment *>)),
+ this, SLOT(slotEditSegmentsEventList(std::vector<Segment *>)));
+ 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<Segment *> 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<Segment *> segmentsToEdit)
+{
+ int count = 0;
+ for (std::vector<Segment *>::iterator i = segmentsToEdit.begin();
+ i != segmentsToEdit.end(); ++i) {
+ std::vector<Segment *> 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<Segment *> 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<InstrumentId, int> StateMap;
+ static StateMap states;
+ static StateMap recStates;
+
+ typedef std::map<InstrumentId, LevelInfo> 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<TrackId> 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<Segment *> 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<const QWidget *>(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<MidiDevice *>
+ (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<Segment *> 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<Segment *>)),
+ this, SLOT(slotEditSegmentsNotation(std::vector<Segment *>)));
+ connect(eventView, SIGNAL(openInMatrix(std::vector<Segment *>)),
+ this, SLOT(slotEditSegmentsMatrix(std::vector<Segment *>)));
+ connect(eventView, SIGNAL(openInPercussionMatrix(std::vector<Segment *>)),
+ this, SLOT(slotEditSegmentsPercussionMatrix(std::vector<Segment *>)));
+ connect(eventView, SIGNAL(openInEventList(std::vector<Segment *>)),
+ this, SLOT(slotEditSegmentsEventList(std::vector<Segment *>)));
+ 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<RosegardenGUIApp*>(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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#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 <qstring.h>
+#include <qvbox.h>
+
+
+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<Segment*>);
+ void slotEditSegmentMatrix(Segment*);
+ void slotEditSegmentsMatrix(std::vector<Segment*>);
+ void slotEditSegmentPercussionMatrix(Segment*);
+ void slotEditSegmentsPercussionMatrix(std::vector<Segment*>);
+ void slotEditSegmentEventList(Segment*);
+ void slotEditSegmentsEventList(std::vector<Segment*>);
+ 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<TrackId> 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<Segment *>);
+ MatrixView *createMatrixView (std::vector<Segment *>, bool drumMode);
+ EventView *createEventView (std::vector<Segment *>);
+
+ 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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Parts of this file are from KDE Konqueror : KonqMainWindowIface
+ Copyright (C) 2000 Simon Hausmann <hausmann@kde.org>
+ Copyright (C) 2000 David Faure <faure@kde.org>
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either 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 <dcopobject.h>
+#include <dcopref.h>
+#include <kaction.h>
+#include <kdcopactionproxy.h>
+#include <kmainwindow.h>
+#include <qcstring.h>
+#include <qstring.h>
+#include <qvaluelist.h>
+#include <kapplication.h>
+#include <dcopclient.h>
+
+
+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<KAction *> lst = m_dcopActionProxy->actions();
+ QValueList<KAction *>::ConstIterator it = lst.begin();
+ QValueList<KAction *>::ConstIterator end = lst.end();
+ for (; it != end; ++it )
+ res.append( (*it)->name() );
+
+ return res;
+}
+
+QMap<QCString,DCOPRef> 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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Parts of this file are from KDE Konqueror : KonqMainWindowIface
+ Copyright (C) 2000 Simon Hausmann <hausmann@kde.org>
+ Copyright (C) 2000 David Faure <faure@kde.org>
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 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 <dcopobject.h>
+#include <dcopref.h>
+#include <qmap.h>
+#include <qstring.h>
+#include <qvaluevector.h>
+
+#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<QString> createRecordAudioFiles
+ (const QValueVector<InstrumentId> &recordInstruments) = 0;
+ virtual QString getAudioFilePath() = 0;
+
+ virtual QValueVector<InstrumentId> getArmedInstruments() = 0;
+
+ virtual void showError(QString error) = 0;
+
+ // Actions proxy
+ //
+ DCOPRef action( const QCString &name );
+ QCStringList actions();
+ QMap<QCString,DCOPRef> 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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "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 <kcursor.h>
+#include <qcursor.h>
+#include <qwidget.h>
+#include <kapplication.h>
+
+namespace Rosegarden
+{
+
+SetWaitCursor::SetWaitCursor()
+ : m_guiApp(dynamic_cast<RosegardenGUIApp*>(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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_SETWAITCURSOR_H_
+#define _RG_SETWAITCURSOR_H_
+
+#include <qcursor.h>
+
+
+
+
+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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "StartupTester.h"
+
+#include "misc/Debug.h"
+#include "gui/dialogs/LilyPondOptionsDialog.h"
+
+#include <kprocess.h>
+#include <qmutex.h>
+#include <qthread.h>
+#include <qregexp.h>
+
+
+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<QHttp *>(dynamic_cast<const QHttp *>(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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_STARTUPTESTER_H_
+#define _RG_STARTUPTESTER_H_
+
+#include <qmutex.h>
+#include <qthread.h>
+#include <qstringlist.h>
+#include <qobject.h>
+#include <qhttp.h>
+
+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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <bownie@bownie.com>
+
+ 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 <qtimer.h>
+#include <kapplication.h>
+#include <sys/time.h>
+#include "base/RealTime.h"
+
+#include <kcmdlineargs.h>
+#include <kaboutdata.h>
+#include <klocale.h>
+#include <dcopclient.h>
+#include <kconfig.h>
+#include <kmessagebox.h>
+#include <kstddirs.h>
+#include <ktip.h>
+#include <kprocess.h>
+#include <kglobalsettings.h>
+
+#include <qstringlist.h>
+#include <qregexp.h>
+#include <qvbox.h>
+#include <qlabel.h>
+
+#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 <X11/Xlib.h>
+#include <X11/Xutil.h>
+#include <X11/Xatom.h>
+#include <X11/SM/SMlib.h>
+
+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"), "<markh@linuxfromscratch.org>");
+ 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"), "<toni__arnold@bluewin.ch>");
+ 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("<h2>Welcome to Rosegarden!</h2><p>Welcome to the Rosegarden audio and MIDI sequencer and musical notation editor.</p><ul><li>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.</li><br><br><li>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.</li><br><br><li>Rosegarden has comprehensive documentation: see the Help menu for the handbook, tutorials, and other information!</li></ul><p>Rosegarden was brought to you by a team of volunteers across the world. To learn more, go to <a href=\"http://www.rosegardenmusic.com/\">http://www.rosegardenmusic.com/</a>.</p>"));
+ 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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "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 <kcombobox.h>
+#include <kconfig.h>
+#include <kfiledialog.h>
+#include <qcheckbox.h>
+#include <qcombobox.h>
+#include <qcstring.h>
+#include <qdatastream.h>
+#include <qframe.h>
+#include <qlabel.h>
+#include <qlineedit.h>
+#include <qobject.h>
+#include <qpushbutton.h>
+#include <qlayout.h>
+#include <qslider.h>
+#include <qspinbox.h>
+#include <qstring.h>
+#include <qstringlist.h>
+#include <qtabwidget.h>
+#include <qtooltip.h>
+#include <qwidget.h>
+#include <kmessagebox.h>
+
+
+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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_AUDIOCONFIGURATIONPAGE_H_
+#define _RG_AUDIOCONFIGURATIONPAGE_H_
+
+#include "TabbedConfigurationPage.h"
+#include <qstring.h>
+#include <klocale.h>
+#include <qlineedit.h>
+
+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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "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 <kconfig.h>
+#include <kdiskfreesp.h>
+#include <kfiledialog.h>
+#include <kfile.h>
+#include <qcstring.h>
+#include <qdatastream.h>
+#include <qdialog.h>
+#include <qframe.h>
+#include <qlabel.h>
+#include <qpushbutton.h>
+#include <qstring.h>
+#include <qtabwidget.h>
+#include <qwidget.h>
+#include <qlayout.h>
+
+
+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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_AUDIOPROPERTIESPAGE_H_
+#define _RG_AUDIOPROPERTIESPAGE_H_
+
+#include "TabbedConfigurationPage.h"
+#include <qstring.h>
+#include <klocale.h>
+
+
+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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "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 <kcolordialog.h>
+#include <kconfig.h>
+#include <kinputdialog.h>
+#include <qcolor.h>
+#include <qframe.h>
+#include <qpushbutton.h>
+#include <qstring.h>
+#include <qtabwidget.h>
+#include <qwidget.h>
+#include <qlayout.h>
+
+
+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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_COLOURCONFIGURATIONPAGE_H_
+#define _RG_COLOURCONFIGURATIONPAGE_H_
+
+#include "base/ColourMap.h"
+#include "gui/widgets/ColourTable.h"
+#include "TabbedConfigurationPage.h"
+#include <qstring.h>
+#include <klocale.h>
+
+
+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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "ConfigurationPage.h"
+
+#include "document/RosegardenGUIDoc.h"
+#include <kconfig.h>
+#include <qwidget.h>
+#include <algorithm>
+
+
+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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ 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 <qwidget.h>
+
+
+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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "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 <kconfig.h>
+#include <klistview.h>
+#include <qframe.h>
+#include <qlabel.h>
+#include <qlistview.h>
+#include <qpixmap.h>
+#include <qpushbutton.h>
+#include <qstring.h>
+#include <qtable.h>
+#include <qtabwidget.h>
+#include <qwidget.h>
+#include <qlayout.h>
+
+
+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
+ <TrackId> 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<long> notesOn;
+ std::multimap<timeT, long> 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<Int>(BaseProperties::PITCH, pitch);
+ notesOn.insert(pitch);
+ noteOffs.insert(std::multimap<timeT, long>::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<PropertyName> fixedKeys =
+ CompositionMetadataKeys::getFixedKeys();
+ std::vector<PropertyName>::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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_DOCUMENTMETACONFIGURATIONPAGE_H_
+#define _RG_DOCUMENTMETACONFIGURATIONPAGE_H_
+
+#include "TabbedConfigurationPage.h"
+#include <qstring.h>
+#include <klocale.h>
+
+
+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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "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 <kcombobox.h>
+#include <kconfig.h>
+#include <kfiledialog.h>
+#include <kmessagebox.h>
+#include <qcheckbox.h>
+#include <qcombobox.h>
+#include <qfileinfo.h>
+#include <qframe.h>
+#include <qlabel.h>
+#include <qlineedit.h>
+#include <qpushbutton.h>
+#include <qspinbox.h>
+#include <qstring.h>
+#include <qtabwidget.h>
+#include <qvbox.h>
+#include <qwidget.h>
+#include <qlayout.h>
+
+
+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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_GENERALCONFIGURATIONPAGE_H_
+#define _RG_GENERALCONFIGURATIONPAGE_H_
+
+#include "TabbedConfigurationPage.h"
+#include "gui/editors/eventlist/EventView.h"
+#include <qstring.h>
+#include <qcheckbox.h>
+#include <qspinbox.h>
+#include <qcombobox.h>
+#include <qlineedit.h>
+#include <klocale.h>
+#include <kiconloader.h>
+
+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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#include "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 <kapplication.h>
+#include <kconfig.h>
+#include <klistview.h>
+#include <klocale.h>
+#include <qgroupbox.h>
+#include <qlabel.h>
+#include <qlayout.h>
+#include <qlineedit.h>
+#include <qpushbutton.h>
+#include <qstring.h>
+#include <qtabwidget.h>
+#include <qtooltip.h>
+#include <qvbox.h>
+#include <qwidget.h>
+#include <qfont.h>
+
+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<std::string> propertyNames = metadata.getPropertyNames();
+ std::vector<PropertyName> fixedKeys =
+ CompositionMetadataKeys::getFixedKeys();
+
+ std::set<std::string> 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<String>(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<std::string> 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<String>(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<String>(CompositionMetadataKeys::Dedication, qstrtostr(m_editDedication->text()));
+ metadata.set<String>(CompositionMetadataKeys::Title, qstrtostr(m_editTitle->text()));
+ metadata.set<String>(CompositionMetadataKeys::Subtitle, qstrtostr(m_editSubtitle->text()));
+ metadata.set<String>(CompositionMetadataKeys::Subsubtitle, qstrtostr(m_editSubsubtitle->text()));
+ metadata.set<String>(CompositionMetadataKeys::Poet, qstrtostr(m_editPoet->text()));
+ metadata.set<String>(CompositionMetadataKeys::Composer, qstrtostr(m_editComposer->text()));
+ metadata.set<String>(CompositionMetadataKeys::Meter, qstrtostr(m_editMeter->text()));
+ metadata.set<String>(CompositionMetadataKeys::Opus, qstrtostr(m_editOpus->text()));
+ metadata.set<String>(CompositionMetadataKeys::Arranger, qstrtostr(m_editArranger->text()));
+ metadata.set<String>(CompositionMetadataKeys::Instrument, qstrtostr(m_editInstrument->text()));
+ metadata.set<String>(CompositionMetadataKeys::Piece, qstrtostr(m_editPiece->text()));
+ metadata.set<String>(CompositionMetadataKeys::Copyright, qstrtostr(m_editCopyright->text()));
+ metadata.set<String>(CompositionMetadataKeys::Tagline, qstrtostr(m_editTagline->text()));
+
+ for (QListViewItem *item = m_metadata->firstChild();
+ item != 0; item = item->nextSibling()) {
+
+ metadata.set<String>(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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_HEADERSCONFIGURATIONPAGE_H_
+#define _RG_HEADERSCONFIGURATIONPAGE_H_
+
+#include <qvbox.h>
+
+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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "LatencyConfigurationPage.h"
+#include <qlayout.h>
+
+#include "document/ConfigGroups.h"
+#include "ConfigurationPage.h"
+#include "document/RosegardenGUIDoc.h"
+#include "TabbedConfigurationPage.h"
+#include <kconfig.h>
+#include <qframe.h>
+#include <qlabel.h>
+#include <qpushbutton.h>
+#include <qslider.h>
+#include <qstring.h>
+#include <qtabwidget.h>
+#include <qwidget.h>
+
+
+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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_LATENCYCONFIGURATIONPAGE_H_
+#define _RG_LATENCYCONFIGURATIONPAGE_H_
+
+#include "TabbedConfigurationPage.h"
+#include <qstring.h>
+#include <klocale.h>
+#include <qslider.h>
+
+
+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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "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 <kcombobox.h>
+#include <kconfig.h>
+#include <kfiledialog.h>
+#include <qcheckbox.h>
+#include <qcombobox.h>
+#include <qcstring.h>
+#include <qdatastream.h>
+#include <qframe.h>
+#include <qlabel.h>
+#include <qlineedit.h>
+#include <qobject.h>
+#include <qpushbutton.h>
+#include <qlayout.h>
+#include <qslider.h>
+#include <qspinbox.h>
+#include <qstring.h>
+#include <qstringlist.h>
+#include <qtabwidget.h>
+#include <qtooltip.h>
+#include <qwidget.h>
+#include <qcheckbox.h>
+#include <qhbox.h>
+
+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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_MIDICONFIGURATIONPAGE_H_
+#define _RG_MIDICONFIGURATIONPAGE_H_
+
+#include "TabbedConfigurationPage.h"
+#include <qstring.h>
+#include <klocale.h>
+#include <qlineedit.h>
+#include <qcheckbox.h>
+
+
+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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "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 <kconfig.h>
+#include <qframe.h>
+#include <qlabel.h>
+#include <qstring.h>
+#include <qtabwidget.h>
+#include <qwidget.h>
+#include <qlayout.h>
+
+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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_MATRIXCONFIGURATIONPAGE_H_
+#define _RG_MATRIXCONFIGURATIONPAGE_H_
+
+#include "TabbedConfigurationPage.h"
+#include <qstring.h>
+#include <klocale.h>
+
+
+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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "NotationConfigurationPage.h"
+#include <qlayout.h>
+
+#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 <kcombobox.h>
+#include <kconfig.h>
+#include <kfontrequester.h>
+#include <kmessagebox.h>
+#include <qcheckbox.h>
+#include <qcombobox.h>
+#include <qfont.h>
+#include <qframe.h>
+#include <qgroupbox.h>
+#include <qhbox.h>
+#include <qlabel.h>
+#include <qobject.h>
+#include <qpushbutton.h>
+#include <qstring.h>
+#include <qstringlist.h>
+#include <qtabwidget.h>
+#include <qwidget.h>
+#include <qtooltip.h>
+#include <algorithm>
+
+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<int> s = NotationHLayout::getAvailableSpacings();
+ int defaultSpacing = m_cfg->readNumEntry("spacing", 100);
+
+ for (std::vector<int>::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<int>::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<NoteStyleName> styles
+ (NoteStyleFactory::getAvailableStyleNames());
+
+ for (std::vector<NoteStyleName>::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
+ <std::string> fs(NoteFontFactory::getFontNames(rescan));
+ std::vector<std::string> f(fs.begin(), fs.end());
+ std::sort(f.begin(), f.end());
+
+ m_untranslatedFont.clear();
+ m_font->clear();
+
+ for (std::vector<std::string>::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<int> sizes = NoteFontFactory::getScreenSizes(font);
+ combo->clear();
+
+ for (std::vector<int>::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<int> 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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_NOTATIONCONFIGURATIONPAGE_H_
+#define _RG_NOTATIONCONFIGURATIONPAGE_H_
+
+#include <string>
+#include "TabbedConfigurationPage.h"
+#include <qstring.h>
+#include <qstringlist.h>
+#include <klocale.h>
+
+
+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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "TabbedConfigurationPage.h"
+
+#include "ConfigurationPage.h"
+#include "document/RosegardenGUIDoc.h"
+#include <kconfig.h>
+#include <kdialog.h>
+#include <qstring.h>
+#include <qtabwidget.h>
+#include <qwidget.h>
+#include <qlayout.h>
+
+
+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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_TABBEDCONFIGURATIONPAGE_H_
+#define _RG_TABBEDCONFIGURATIONPAGE_H_
+
+#include "ConfigurationPage.h"
+#include <qstring.h>
+
+
+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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "AddTracksDialog.h"
+
+#include <klocale.h>
+#include <kdialogbase.h>
+#include <qhbox.h>
+#include <qlabel.h>
+#include <qspinbox.h>
+#include <kcombobox.h>
+#include <qvbox.h>
+#include <qwidget.h>
+#include <kapp.h>
+#include <kconfig.h>
+
+#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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_ADDTRACKSDIALOG_H_
+#define _RG_ADDTRACKSDIALOG_H_
+
+#include <kdialogbase.h>
+
+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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "AudioManagerDialog.h"
+#include <kapplication.h>
+
+#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 <klocale.h>
+#include <kstddirs.h>
+#include <kaction.h>
+#include <kcommand.h>
+#include <kfiledialog.h>
+#include <kglobal.h>
+#include <klineeditdlg.h>
+#include <klistview.h>
+#include <kmainwindow.h>
+#include <kmessagebox.h>
+#include <kstdaction.h>
+#include <kurl.h>
+#include <kxmlguiclient.h>
+#include <kio/netaccess.h>
+#include <qaccel.h>
+#include <qcstring.h>
+#include <qdatastream.h>
+#include <qdialog.h>
+#include <qdragobject.h>
+#include <qfile.h>
+#include <qfileinfo.h>
+#include <qiconset.h>
+#include <qlabel.h>
+#include <qlistview.h>
+#include <qpainter.h>
+#include <qpixmap.h>
+#include <qstring.h>
+#include <qstrlist.h>
+#include <qtimer.h>
+#include <qvbox.h>
+#include <qwidget.h>
+
+
+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<AudioListItem*>(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("<no audio files>"), 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<Segment*> segments;
+ std::vector<Segment*>::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<AudioFile*>::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("<no preview>"));
+ }
+
+ //!!! 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<AudioListItem*>(m_fileList->selectedItem());
+ if (item == 0)
+ return 0;
+
+ std::vector<AudioFile*>::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<WAVAudioFile*>(getCurrentSelection());
+
+ AudioListItem *item =
+ dynamic_cast<AudioListItem*>(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<AudioListItem*>(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<AudioListItem*>(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<AudioListItem*>(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<AudioFile*>::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
+ <AudioFileId> 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<AudioFileId> toDelete;
+ for (std::vector<AudioFile*>::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<AudioFileId>::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
+ <AudioFileId> 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<QString> toDelete;
+ std::map<QString, AudioFileId> nameMap;
+
+ for (std::vector<AudioFile*>::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<QString> names = dialog->getSelectedAudioFileNames();
+
+ if (names.size() > 0) {
+
+ QString question =
+ i18n("<qt>About to delete 1 audio file permanently from the hard disk.<br>This action cannot be undone, and there will be no way to recover this file.<br>Are you sure?</qt>\n", "<qt>About to delete %n audio files permanently from the hard disk.<br>This action cannot be undone, and there will be no way to recover these files.<br>Are you sure?</qt>", 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<AudioListItem*>(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<AudioListItem*>(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<AudioListItem*>(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<RosegardenGUIView>& 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<timeT> 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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_AUDIOMANAGERDIALOG_H_
+#define _RG_AUDIOMANAGERDIALOG_H_
+
+#include "sound/AudioFile.h"
+#include <kmainwindow.h>
+#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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "AudioPlayingDialog.h"
+
+#include <klocale.h>
+#include <kdialogbase.h>
+#include <qhbox.h>
+#include <qlabel.h>
+#include <qstring.h>
+#include <qwidget.h>
+
+
+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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_AUDIOPLAYINGDIALOG_H_
+#define _RG_AUDIOPLAYINGDIALOG_H_
+
+#include <kdialogbase.h>
+#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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "AudioPluginDialog.h"
+#include <qlayout.h>
+
+#include <klocale.h>
+#include "misc/Debug.h"
+#include "misc/Strings.h"
+#include "base/AudioPluginInstance.h"
+#include "base/Instrument.h"
+#include "base/MidiProgram.h"
+#include "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 <kcombobox.h>
+#include <kdialogbase.h>
+#include <qaccel.h>
+#include <qcheckbox.h>
+#include <qframe.h>
+#include <qgroupbox.h>
+#include <qhbox.h>
+#include <qlabel.h>
+#include <qpushbutton.h>
+#include <qsizepolicy.h>
+#include <qstring.h>
+#include <qstringlist.h>
+#include <qtooltip.h>
+#include <qvbox.h>
+#include <qwidget.h>
+#include <set>
+
+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("<ports>"), h);
+ m_insOuts->setAlignment(AlignRight);
+ QToolTip::add
+ (m_insOuts, i18n("Input and output port counts."));
+
+ m_pluginId = new QLabel(i18n("<id>"), 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
+ <QString> 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
+ <QString>::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<int, AudioPlugin *> PluginPair;
+ typedef std::map<QString, PluginPair> 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("<no plugin>"));
+ m_insOuts->setText(i18n("<ports>"));
+ m_pluginId->setText(i18n("<id>"));
+
+ 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<QWidget*>(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("<none selected>"));
+ 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("<none selected>"));
+ 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 &current)
+{
+ 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<const PluginControl*>(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)) { // "<none set>"
+ 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<PluginControl *>::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<PluginControl *>::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("<none selected>"));
+ 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("<none selected>"));
+ 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("<none selected>"));
+ 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<PluginControl*>::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<int>::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<std::string, std::string>::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<PluginControl *>::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<PluginControl *>::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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_AUDIOPLUGINDIALOG_H_
+#define _RG_AUDIOPLUGINDIALOG_H_
+
+#include "base/Instrument.h"
+#include "base/MidiProgram.h"
+#include <kdialogbase.h>
+#include <qstring.h>
+#include <qstringlist.h>
+#include <vector>
+
+
+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 &current);
+
+ //--------------- 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<int> 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<PluginControl*> 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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "AudioSplitDialog.h"
+#include <kapplication.h>
+
+#include <klocale.h>
+#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 <kdialogbase.h>
+#include <qcanvas.h>
+#include <qhbox.h>
+#include <qlabel.h>
+#include <qpalette.h>
+#include <qscrollview.h>
+#include <qspinbox.h>
+#include <qstring.h>
+#include <qvbox.h>
+#include <qwidget.h>
+
+
+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<float> 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("<no preview generated for this audio file>"));
+ 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<float>::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<SplitPointPair> splitPoints =
+ aFM.getSplitPoints(m_segment->getAudioFileId(),
+ startTime,
+ endTime,
+ threshold);
+
+ std::vector<SplitPointPair>::iterator it;
+ std::vector<QCanvasRectangle*> 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<QCanvasRectangle*>::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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_AUDIOSPLITDIALOG_H_
+#define _RG_AUDIOSPLITDIALOG_H_
+
+#include <kdialogbase.h>
+#include <vector>
+#include <qspinbox.h>
+
+
+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<QCanvasRectangle*> 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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "BeatsBarsDialog.h"
+#include <qlayout.h>
+
+#include <klocale.h>
+#include "base/Segment.h"
+#include <kcombobox.h>
+#include <kdialogbase.h>
+#include <qframe.h>
+#include <qgroupbox.h>
+#include <qhbox.h>
+#include <qspinbox.h>
+#include <qwidget.h>
+
+
+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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_BEATSBARSDIALOG_H_
+#define _RG_BEATSBARSDIALOG_H_
+
+#include <kdialogbase.h>
+#include <qspinbox.h>
+#include <kcombobox.h>
+
+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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "ClefDialog.h"
+
+#include "base/NotationTypes.h"
+#include "gui/editors/notation/NotePixmapFactory.h"
+#include "gui/widgets/BigArrowButton.h"
+#include <klocale.h>
+#include <kdialogbase.h>
+#include <qbuttongroup.h>
+#include <qgroupbox.h>
+#include <qhbox.h>
+#include <qlabel.h>
+#include <qobject.h>
+#include <qpixmap.h>
+#include <qradiobutton.h>
+#include <qstring.h>
+#include <qtooltip.h>
+#include <qvbox.h>
+#include <qwidget.h>
+
+
+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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_CLEFDIALOG_H_
+#define _RG_CLEFDIALOG_H_
+
+#include "base/NotationTypes.h"
+#include <kdialogbase.h>
+
+
+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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "CompositionLengthDialog.h"
+
+#include <klocale.h>
+#include "base/Composition.h"
+#include <kdialogbase.h>
+#include <qhbox.h>
+#include <qlabel.h>
+#include <qspinbox.h>
+#include <qvbox.h>
+#include <qwidget.h>
+
+
+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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_COMPOSITIONLENGTHDIALOG_H_
+#define _RG_COMPOSITIONLENGTHDIALOG_H_
+
+#include <kdialogbase.h>
+#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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "ConfigureDialog.h"
+#include <qlayout.h>
+
+#include <klocale.h>
+#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 <kconfig.h>
+#include <kdialogbase.h>
+#include <kstddirs.h>
+#include <qstring.h>
+#include <qwidget.h>
+
+
+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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "ConfigureDialogBase.h"
+
+#include <klocale.h>
+#include "gui/configuration/ConfigurationPage.h"
+#include <kdialogbase.h>
+#include <qstring.h>
+#include <qwidget.h>
+
+
+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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_CONFIGUREDIALOGBASE_H_
+#define _RG_CONFIGUREDIALOGBASE_H_
+
+#include <kdialogbase.h>
+#include <qstring.h>
+#include <vector>
+
+
+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<ConfigurationPage*> 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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "CountdownBar.h"
+
+#include "gui/general/GUIPalette.h"
+#include <qframe.h>
+#include <qpainter.h>
+#include <qwidget.h>
+
+
+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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_COUNTDOWNBAR_H_
+#define _RG_COUNTDOWNBAR_H_
+
+#include <qframe.h>
+
+
+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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "CountdownDialog.h"
+#include <qlayout.h>
+
+#include <klocale.h>
+#include "CountdownBar.h"
+#include <qaccel.h>
+#include <qdialog.h>
+#include <qhbox.h>
+#include <qlabel.h>
+#include <qpushbutton.h>
+#include <qstring.h>
+#include <qwidget.h>
+#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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_COUNTDOWNDIALOG_H_
+#define _RG_COUNTDOWNDIALOG_H_
+
+#include <kdialogbase.h>
+#include <qdialog.h>
+
+
+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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "DocumentConfigureDialog.h"
+#include <qlayout.h>
+
+#include <klocale.h>
+#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 <kdialogbase.h>
+#include <qstring.h>
+#include <qwidget.h>
+#include <kstddirs.h>
+
+
+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<AudioPropertiesPage *>(*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<DocumentMetaConfigurationPage *>(*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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_DOCUMENTCONFIGUREDIALOG_H_
+#define _RG_DOCUMENTCONFIGUREDIALOG_H_
+
+#include "ConfigureDialogBase.h"
+#include <qstring.h>
+
+
+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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "EventEditDialog.h"
+
+#include <klocale.h>
+#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 <kdialogbase.h>
+#include <kmessagebox.h>
+#include <qcheckbox.h>
+#include <qfont.h>
+#include <qgrid.h>
+#include <qgroupbox.h>
+#include <qhbox.h>
+#include <qlabel.h>
+#include <qlineedit.h>
+#include <qobject.h>
+#include <qobjectlist.h>
+#include <qpushbutton.h>
+#include <qscrollview.h>
+#include <qsize.h>
+#include <qspinbox.h>
+#include <qstring.h>
+#include <qtooltip.h>
+#include <qvbox.h>
+#include <qwidget.h>
+
+
+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<Int>(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<UInt>(name));
+ QObject::connect(spinBox, SIGNAL(valueChanged(int)),
+ this, SLOT(slotIntPropertyChanged(int)));
+ spinBox->show();
+ break;
+ }
+ case RealTimeT: {
+ RealTime realTime = m_originalEvent.get<RealTimeT>(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<Bool>(name));
+ QObject::connect(checkBox, SIGNAL(activated()),
+ this, SLOT(slotBoolPropertyChanged()));
+ checkBox->show();
+ break;
+ }
+
+ case String: {
+ QLineEdit *lineEdit = new QLineEdit
+ (strtoqstr(m_originalEvent.get<String>(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<const QSpinBox *>(s);
+ if (!spinBox)
+ return ;
+
+ m_modified = true;
+ QString propertyName = spinBox->name();
+ m_event.set<Int>(qstrtostr(propertyName), value);
+}
+
+void
+EventEditDialog::slotRealTimePropertyChanged(int value)
+{
+ const QObject *s = sender();
+ const QSpinBox *spinBox = dynamic_cast<const QSpinBox *>(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<RealTimeT>(qstrtostr(propertyName));
+
+ if (nsecOrSec == "sec")
+ realTime.sec = value;
+ else
+ realTime.nsec = value;
+
+ m_event.set<Int>(qstrtostr(propertyName), value);
+}
+
+void
+EventEditDialog::slotBoolPropertyChanged()
+{
+ const QObject *s = sender();
+ const QCheckBox *checkBox = dynamic_cast<const QCheckBox *>(s);
+ if (!checkBox)
+ return ;
+
+ m_modified = true;
+ QString propertyName = checkBox->name();
+ bool checked = checkBox->isChecked();
+
+ m_event.set<Bool>(qstrtostr(propertyName), checked);
+}
+
+void
+EventEditDialog::slotStringPropertyChanged(const QString &value)
+{
+ const QObject *s = sender();
+ const QLineEdit *lineEdit = dynamic_cast<const QLineEdit *>(s);
+ if (!lineEdit)
+ return ;
+
+ m_modified = true;
+ QString propertyName = lineEdit->name();
+ m_event.set<String>(qstrtostr(propertyName), qstrtostr(value));
+}
+
+void
+EventEditDialog::slotPropertyDeleted()
+{
+ const QObject *s = sender();
+ const QPushButton *pushButton = dynamic_cast<const QPushButton *>(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<const QPushButton *>(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<Int>
+ (qstrtostr(propertyName),
+ m_originalEvent.get<Int>
+ (qstrtostr(propertyName)));
+ break;
+
+ case UInt:
+ m_event.set<UInt>
+ (qstrtostr(propertyName),
+ m_originalEvent.get<UInt>
+ (qstrtostr(propertyName)));
+ break;
+
+ case RealTimeT:
+ m_event.set<RealTimeT>
+ (qstrtostr(propertyName),
+ m_originalEvent.get<RealTimeT>
+ (qstrtostr(propertyName)));
+ break;
+
+ case Bool:
+ m_event.set<Bool>
+ (qstrtostr(propertyName),
+ m_originalEvent.get<Bool>
+ (qstrtostr(propertyName)));
+ break;
+
+ case String:
+ m_event.set<String>
+ (qstrtostr(propertyName),
+ m_originalEvent.get<String>
+ (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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_EVENTEDITDIALOG_H_
+#define _RG_EVENTEDITDIALOG_H_
+
+#include "base/Event.h"
+#include "gui/editors/notation/NotePixmapFactory.h"
+#include <string>
+#include <kdialogbase.h>
+
+
+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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ This file is Copyright 2003-2006
+ D. Michael McIntyre <dmmcintyr@users.sourceforge.net>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either 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 <kapplication.h>
+#include <kconfig.h>
+#include <kdialogbase.h>
+#include <klocale.h>
+#include <qcheckbox.h>
+#include <qcombobox.h>
+#include <qdialog.h>
+#include <qframe.h>
+#include <qgroupbox.h>
+#include <qlabel.h>
+#include <qlayout.h>
+#include <qpixmap.h>
+#include <qpushbutton.h>
+#include <qsizepolicy.h>
+#include <qspinbox.h>
+#include <qstring.h>
+#include <qtooltip.h>
+#include <qvbox.h>
+#include <qwidget.h>
+
+
+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<Int>(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<Int>(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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ This file is Copyright 2003-2006
+ D. Michael McIntyre <dmmcintyr@users.sourceforge.net>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 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 <kdialogbase.h>
+#include <utility>
+#include <vector>
+#include "base/Event.h"
+#include <qcheckbox.h>
+#include <qcombobox.h>
+
+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<long, long> 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<timeT> 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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "EventParameterDialog.h"
+
+#include <klocale.h>
+#include "misc/Debug.h"
+#include "misc/Strings.h"
+#include "base/PropertyName.h"
+#include <kcombobox.h>
+#include <kdialogbase.h>
+#include <qhbox.h>
+#include <qlabel.h>
+#include <qstring.h>
+#include <qvbox.h>
+#include <qwidget.h>
+
+
+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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_EVENTPARAMETERDIALOG_H_
+#define _RG_EVENTPARAMETERDIALOG_H_
+
+#include "base/PropertyName.h"
+#include <kdialogbase.h>
+#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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "ExportDeviceDialog.h"
+
+#include <klocale.h>
+#include <kdialogbase.h>
+#include <qbuttongroup.h>
+#include <qlabel.h>
+#include <qradiobutton.h>
+#include <qstring.h>
+#include <qvbox.h>
+#include <qwidget.h>
+
+
+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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_EXPORTDEVICEDIALOG_H_
+#define _RG_EXPORTDEVICEDIALOG_H_
+
+#include <kdialogbase.h>
+#include <qstring.h>
+
+
+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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "FileLocateDialog.h"
+
+#include <klocale.h>
+#include "misc/Debug.h"
+#include <kdialogbase.h>
+#include <kfiledialog.h>
+#include <qfileinfo.h>
+#include <qhbox.h>
+#include <qlabel.h>
+#include <qstring.h>
+#include <qwidget.h>
+
+
+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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_FILELOCATEDIALOG_H_
+#define _RG_FILELOCATEDIALOG_H_
+
+#include <kdialogbase.h>
+#include <qstring.h>
+
+
+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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "FileMergeDialog.h"
+
+#include <klocale.h>
+#include <kcombobox.h>
+#include <kdialogbase.h>
+#include <qcheckbox.h>
+#include <qhbox.h>
+#include <qlabel.h>
+#include <qstring.h>
+#include <qvbox.h>
+#include <qwidget.h>
+#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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_FILEMERGEDIALOG_H_
+#define _RG_FILEMERGEDIALOG_H_
+
+#include <kdialogbase.h>
+#include <qstring.h>
+
+
+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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "FloatEdit.h"
+
+#include "gui/widgets/HSpinBox.h"
+#include <kdialogbase.h>
+#include <qgroupbox.h>
+#include <qlabel.h>
+#include <qstring.h>
+#include <qvbox.h>
+#include <qwidget.h>
+#include <cmath>
+
+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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_ROSEGARDENFLOATEDIT_H_
+#define _RG_ROSEGARDENFLOATEDIT_H_
+
+#include <kdialogbase.h>
+
+
+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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "IdentifyTextCodecDialog.h"
+
+#include <klocale.h>
+#include "misc/Strings.h"
+#include "base/NotationTypes.h"
+#include <kcombobox.h>
+#include <kdialogbase.h>
+#include <qfont.h>
+#include <qlabel.h>
+#include <qstring.h>
+#include <qtextcodec.h>
+#include <qvbox.h>
+#include <qwidget.h>
+
+
+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<std::string, QString> 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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_IDENTIFYTEXTCODECDIALOG_H_
+#define _RG_IDENTIFYTEXTCODECDIALOG_H_
+
+#include <string>
+#include <kdialogbase.h>
+#include <deque>
+
+
+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<std::string> 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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "ImportDeviceDialog.h"
+#include <qlayout.h>
+#include <kapplication.h>
+
+#include <klocale.h>
+#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 <kcombobox.h>
+#include <kconfig.h>
+#include <kdialogbase.h>
+#include <kmessagebox.h>
+#include <kurl.h>
+#include <kio/netaccess.h>
+#include <qbuttongroup.h>
+#include <qcheckbox.h>
+#include <qgroupbox.h>
+#include <qhbox.h>
+#include <qlabel.h>
+#include <qradiobutton.h>
+#include <qstring.h>
+#include <qvbox.h>
+#include <qwidget.h>
+
+
+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<MidiDevice *>::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<MidiDevice*>(*it);
+
+ if (device) {
+ std::vector<MidiBank> banks =
+ device->getBanks();
+
+ // DMM - check for controllers too, because some users have
+ // created .rgd files that contain only controllers
+ // see bug #1183522
+ //
+ std::vector<ControlParameter> 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<MidiBank> banks;
+ std::vector<MidiProgram> 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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_IMPORTDEVICEDIALOG_H_
+#define _RG_IMPORTDEVICEDIALOG_H_
+
+#include "base/MidiDevice.h"
+#include <string>
+#include <kdialogbase.h>
+#include <qstring.h>
+#include <vector>
+#include <kurl.h>
+
+
+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<MidiDevice *> 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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "InterpretDialog.h"
+#include <kapplication.h>
+
+#include <klocale.h>
+#include "document/ConfigGroups.h"
+#include "commands/notation/InterpretCommand.h"
+#include <kconfig.h>
+#include <kdialogbase.h>
+#include <qcheckbox.h>
+#include <qgroupbox.h>
+#include <qvbox.h>
+#include <qwidget.h>
+
+
+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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_INTERPRETDIALOG_H_
+#define _RG_INTERPRETDIALOG_H_
+
+#include <kdialogbase.h>
+
+
+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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "IntervalDialog.h"
+#include <qlayout.h>
+
+#include <iostream>
+#include <klocale.h>
+#include "misc/Strings.h"
+#include "base/MidiDevice.h"
+#include "base/NotationRules.h"
+#include <kcombobox.h>
+#include <kdialogbase.h>
+#include <qframe.h>
+#include <qgroupbox.h>
+#include <qcheckbox.h>
+#include <qlabel.h>
+#include <qradiobutton.h>
+#include <qbuttongroup.h>
+#include <qsizepolicy.h>
+#include <qstring.h>
+#include <qvbox.h>
+#include <qwidget.h>
+
+
+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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_INTERVALDIALOG_H_
+#define _RG_INTERVALDIALOG_H_
+
+#include <kdialogbase.h>
+#include <vector>
+#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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "KeySignatureDialog.h"
+
+#include <klocale.h>
+#include "misc/Strings.h"
+#include "base/NotationTypes.h"
+#include "gui/editors/notation/NotePixmapFactory.h"
+#include "gui/widgets/BigArrowButton.h"
+#include <kcombobox.h>
+#include <kdialogbase.h>
+#include <qbuttongroup.h>
+#include <qgroupbox.h>
+#include <qhbox.h>
+#include <qlabel.h>
+#include <qobject.h>
+#include <qpixmap.h>
+#include <qradiobutton.h>
+#include <qstring.h>
+#include <qtooltip.h>
+#include <qvbox.h>
+#include <qwidget.h>
+#include <qcheckbox.h>
+#include <algorithm>
+
+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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_KEYSIGNATUREDIALOG_H_
+#define _RG_KEYSIGNATUREDIALOG_H_
+
+#include "base/NotationTypes.h"
+#include <string>
+#include <kdialogbase.h>
+#include <qstring.h>
+#include <qcheckbox.h>
+
+
+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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "LilyPondOptionsDialog.h"
+#include "document/io/LilyPondExporter.h"
+#include "gui/configuration/HeadersConfigurationPage.h"
+
+#include <qlayout.h>
+#include <kapplication.h>
+
+#include "document/ConfigGroups.h"
+#include "document/RosegardenGUIDoc.h"
+#include "misc/Strings.h"
+#include <kcombobox.h>
+#include <klineedit.h>
+#include <kconfig.h>
+#include <kdialogbase.h>
+#include <kglobal.h>
+#include <klocale.h>
+#include <qcheckbox.h>
+#include <qcombobox.h>
+#include <qframe.h>
+#include <qgroupbox.h>
+#include <qlabel.h>
+#include <qstring.h>
+#include <qtabwidget.h>
+#include <qtooltip.h>
+#include <qvbox.h>
+#include <qwidget.h>
+#include <iostream>
+
+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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_LILYPONDOPTIONSDIALOG_H_
+#define _RG_LILYPONDOPTIONSDIALOG_H_
+
+#include <kdialogbase.h>
+#include <qstring.h>
+
+#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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "LyricEditDialog.h"
+
+#include "base/Event.h"
+#include "base/BaseProperties.h"
+#include <klocale.h>
+#include "misc/Strings.h"
+#include "misc/Debug.h"
+#include "base/Composition.h"
+#include "base/NotationTypes.h"
+#include "base/Segment.h"
+#include <kdialogbase.h>
+#include <qgroupbox.h>
+#include <qregexp.h>
+#include <qstring.h>
+#include <qtextedit.h>
+#include <qvbox.h>
+#include <qwidget.h>
+#include <kcombobox.h>
+#include <qlabel.h>
+#include <qpushbutton.h>
+
+
+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<String>(Text::TextTypePropertyName, textType) &&
+ textType == Text::Lyric) {
+
+ long verse = 0;
+ (*i)->get<Int>(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<int, bool> 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<String>(Text::TextTypePropertyName, textType) &&
+ textType == Text::Lyric) {
+ isLyric = true;
+ }
+ }
+ } else {
+ if ((*i)->has(BaseProperties::TIED_BACKWARD) &&
+ (*i)->get<Bool>(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<String>(Text::TextPropertyName, ssyllable);
+
+ long verse = 0;
+ (*i)->get<Int>(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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_LYRICEDITDIALOG_H_
+#define _RG_LYRICEDITDIALOG_H_
+
+#include <kdialogbase.h>
+#include <qstring.h>
+#include <vector>
+
+
+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<QString> 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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "MakeOrnamentDialog.h"
+
+#include <klocale.h>
+#include "gui/widgets/PitchChooser.h"
+#include <kdialogbase.h>
+#include <qgroupbox.h>
+#include <qhbox.h>
+#include <qlabel.h>
+#include <qlineedit.h>
+#include <qstring.h>
+#include <qvbox.h>
+#include <qwidget.h>
+
+
+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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_MAKEORNAMENTDIALOG_H_
+#define _RG_MAKEORNAMENTDIALOG_H_
+
+#include <kdialogbase.h>
+#include <qstring.h>
+
+
+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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "ManageMetronomeDialog.h"
+#include <qlayout.h>
+
+#include <klocale.h>
+#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 <kcombobox.h>
+#include <kdialogbase.h>
+#include <qcheckbox.h>
+#include <qframe.h>
+#include <qgroupbox.h>
+#include <qhbox.h>
+#include <qlabel.h>
+#include <qspinbox.h>
+#include <qstring.h>
+#include <qvbox.h>
+#include <qwidget.h>
+
+
+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<MidiDevice*>(*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<MidiDevice*>(*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<MidiDevice*>(*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<MidiDevice*>(*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<MidiDevice*>(*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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_MANAGEMETRONOMEDIALOG_H_
+#define _RG_MANAGEMETRONOMEDIALOG_H_
+
+#include "base/MidiProgram.h"
+#include <kdialogbase.h>
+
+
+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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "MarkerModifyDialog.h"
+#include <qlayout.h>
+
+#include <klocale.h>
+#include "base/Composition.h"
+#include "document/RosegardenGUIDoc.h"
+#include "gui/widgets/TimeWidget.h"
+#include <kdialogbase.h>
+#include <qframe.h>
+#include <qgroupbox.h>
+#include <qlabel.h>
+#include <qlineedit.h>
+#include <qspinbox.h>
+#include <qstring.h>
+#include <qvbox.h>
+#include <qwidget.h>
+#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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_MARKERMODIFYDIALOG_H_
+#define _RG_MARKERMODIFYDIALOG_H_
+
+#include <kdialogbase.h>
+#include <qstring.h>
+#include <qlineedit.h>
+
+#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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "PasteNotationDialog.h"
+
+#include <klocale.h>
+#include "commands/edit/PasteEventsCommand.h"
+#include <kdialogbase.h>
+#include <qbuttongroup.h>
+#include <qcheckbox.h>
+#include <qobject.h>
+#include <qradiobutton.h>
+#include <qvbox.h>
+#include <qwidget.h>
+
+
+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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_PASTENOTATIONDIALOG_H_
+#define _RG_PASTENOTATIONDIALOG_H_
+
+#include "commands/edit/PasteEventsCommand.h"
+#include <kdialogbase.h>
+#include <vector>
+
+
+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<QRadioButton *> 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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "PitchDialog.h"
+
+#include <klocale.h>
+#include "gui/widgets/PitchChooser.h"
+#include <kdialogbase.h>
+#include <qstring.h>
+#include <qvbox.h>
+#include <qwidget.h>
+
+
+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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_PITCHDIALOG_H_
+#define _RG_PITCHDIALOG_H_
+
+#include <kdialogbase.h>
+#include <qstring.h>
+
+
+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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "PitchPickerDialog.h"
+#include <klocale.h>
+#include <kdialogbase.h>
+#include <qlayout.h>
+#include <qframe.h>
+#include <qstring.h>
+#include <qvbox.h>
+#include <qwidget.h>
+
+
+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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_PITCHPICKERDIALOG_H_
+#define _RG_PITCHPICKERDIALOG_H_
+
+#include "gui/widgets/PitchChooser.h"
+#include <kdialogbase.h>
+
+
+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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "QuantizeDialog.h"
+
+#include <klocale.h>
+#include "base/Quantizer.h"
+#include "gui/widgets/QuantizeParameters.h"
+#include <kdialogbase.h>
+#include <qvbox.h>
+#include <qwidget.h>
+
+
+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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_QUANTIZEDIALOG_H_
+#define _RG_QUANTIZEDIALOG_H_
+
+#include <kdialogbase.h>
+
+
+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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "RescaleDialog.h"
+
+#include <klocale.h>
+#include "document/ConfigGroups.h"
+#include "base/Composition.h"
+#include "gui/widgets/TimeWidget.h"
+#include <kconfig.h>
+#include <kdialogbase.h>
+#include <qcheckbox.h>
+#include <qgroupbox.h>
+#include <qstring.h>
+#include <qvbox.h>
+#include <qwidget.h>
+#include <kapplication.h>
+
+
+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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_RESCALEDIALOG_H_
+#define _RG_RESCALEDIALOG_H_
+
+#include <kdialogbase.h>
+#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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "ShowSequencerStatusDialog.h"
+
+#include <klocale.h>
+#include <kdialogbase.h>
+#include <qcstring.h>
+#include <qdatastream.h>
+#include <qlabel.h>
+#include <qstring.h>
+#include <qtextedit.h>
+#include <qvbox.h>
+#include <qwidget.h>
+#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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_SHOWSEQUENCERSTATUSDIALOG_H_
+#define _RG_SHOWSEQUENCERSTATUSDIALOG_H_
+
+#include <kdialogbase.h>
+
+
+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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "SimpleEventEditDialog.h"
+#include <qlayout.h>
+
+#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 <kcombobox.h>
+#include <kdialogbase.h>
+#include <kfiledialog.h>
+#include <klocale.h>
+#include <qcheckbox.h>
+#include <qdialog.h>
+#include <qfile.h>
+#include <qframe.h>
+#include <qgroupbox.h>
+#include <qlabel.h>
+#include <qlineedit.h>
+#include <qpushbutton.h>
+#include <qspinbox.h>
+#include <qstring.h>
+#include <qvbox.h>
+#include <qwidget.h>
+
+
+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("<none>"), 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<Int>(PITCH));
+ } catch (Event::NoData) {
+ m_pitchSpinBox->setValue(60);
+ }
+
+ try {
+ m_velocitySpinBox->setValue(m_event.get<Int>(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<Int>
+ (Controller::NUMBER));
+ } catch (Event::NoData) {
+ m_pitchSpinBox->setValue(0);
+ }
+
+ try {
+ m_velocitySpinBox->setValue(m_event.get<Int>
+ (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<Int>
+ (KeyPressure::PITCH));
+ } catch (Event::NoData) {
+ m_pitchSpinBox->setValue(0);
+ }
+
+ try {
+ m_velocitySpinBox->setValue(m_event.get<Int>
+ (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<Int>
+ (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<Int>
+ (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<Int>
+ (PitchBend::MSB));
+ } catch (Event::NoData) {
+ m_pitchSpinBox->setValue(0);
+ }
+
+ try {
+ m_velocitySpinBox->setValue(m_event.get<Int>
+ (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("<none>"));
+ }
+
+ 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("<none>"));
+ m_metaEdit->setText(i18n("<none>"));
+ }
+
+ 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("<none>"));
+ }
+
+ 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("<none>"));
+ }
+
+ 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("<none>"));
+ // m_metaEdit->setText(i18n("<none>"));
+ }
+
+ 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<String>(Indication::IndicationTypePropertyName,
+ qstrtostr(m_metaEdit->text()));
+
+ } else if (m_type == Text::EventType) {
+
+ event.set<String>(Text::TextTypePropertyName,
+ qstrtostr(m_controllerLabelValue->text()));
+ event.set<String>(Text::TextPropertyName,
+ qstrtostr(m_metaEdit->text()));
+
+ } else if (m_type == Clef::EventType) {
+
+ event.set<String>(Clef::ClefPropertyName,
+ qstrtostr(m_controllerLabelValue->text()));
+
+ } else if (m_type == ::Rosegarden::Key::EventType) {
+
+ event.set<String>(::Rosegarden::Key::KeyPropertyName,
+ qstrtostr(m_controllerLabelValue->text()));
+
+ } else if (m_type == SystemExclusive::EventType) {
+
+ event.set<String>(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<Int>(BaseProperties::PITCH, value);
+
+ } else if (m_type == Controller::EventType) {
+ m_event.set<Int>(Controller::NUMBER, value);
+
+ } else if (m_type == KeyPressure::EventType) {
+ m_event.set<Int>(KeyPressure::PITCH, value);
+
+ } else if (m_type == ChannelPressure::EventType) {
+ m_event.set<Int>(ChannelPressure::PRESSURE, value);
+
+ } else if (m_type == ProgramChange::EventType) {
+ if (value < 1)
+ value = 1;
+ m_event.set<Int>(ProgramChange::PROGRAM, value - 1);
+
+ } else if (m_type == PitchBend::EventType) {
+ m_event.set<Int>(PitchBend::MSB, value);
+ }
+ //!!!??? sysex?
+}
+
+void
+SimpleEventEditDialog::slotVelocityChanged(int value)
+{
+ m_modified = true;
+
+ if (m_type == Note::EventType) {
+ m_event.set<Int>(BaseProperties::VELOCITY, value);
+
+ } else if (m_type == Controller::EventType) {
+ m_event.set<Int>(Controller::VALUE, value);
+
+ } else if (m_type == KeyPressure::EventType) {
+ m_event.set<Int>(KeyPressure::PRESSURE, value);
+
+ } else if (m_type == PitchBend::EventType) {
+ m_event.set<Int>(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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_SIMPLEEVENTEDITDIALOG_H_
+#define _RG_SIMPLEEVENTEDITDIALOG_H_
+
+#include "base/Event.h"
+#include <string>
+#include <kdialogbase.h>
+
+
+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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "SplitByPitchDialog.h"
+
+#include <klocale.h>
+#include "commands/segment/SegmentSplitByPitchCommand.h"
+#include "gui/general/ClefIndex.h"
+#include "gui/widgets/PitchChooser.h"
+#include <kcombobox.h>
+#include <kdialogbase.h>
+#include <qcheckbox.h>
+#include <qframe.h>
+#include <qlabel.h>
+#include <qvbox.h>
+#include <qwidget.h>
+#include <qlayout.h>
+
+
+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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_SPLITBYPITCHDIALOG_H_
+#define _RG_SPLITBYPITCHDIALOG_H_
+
+#include <kdialogbase.h>
+
+
+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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "SplitByRecordingSrcDialog.h"
+#include <qlayout.h>
+
+#include <klocale.h>
+#include "misc/Strings.h"
+#include "base/MidiDevice.h"
+#include "document/RosegardenGUIDoc.h"
+#include <kcombobox.h>
+#include <kdialogbase.h>
+#include <qframe.h>
+#include <qgroupbox.h>
+#include <qlabel.h>
+#include <qsizepolicy.h>
+#include <qstring.h>
+#include <qvbox.h>
+#include <qwidget.h>
+
+
+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<MidiDevice*>(*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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_SPLITBYRECORDINGSRCDIALOG_H_
+#define _RG_SPLITBYRECORDINGSRCDIALOG_H_
+
+#include <kdialogbase.h>
+#include <vector>
+#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<int> 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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "TempoDialog.h"
+#include <qlayout.h>
+
+#include <klocale.h>
+#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 <kdialogbase.h>
+#include <qbuttongroup.h>
+#include <qcheckbox.h>
+#include <qframe.h>
+#include <qgroupbox.h>
+#include <qhbox.h>
+#include <qlabel.h>
+#include <qradiobutton.h>
+#include <qstring.h>
+#include <qvbox.h>
+#include <qwidget.h>
+
+
+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<bool, tempoT> 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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_TEMPODIALOG_H_
+#define _RG_TEMPODIALOG_H_
+
+#include <kdialogbase.h>
+#include "base/Event.h"
+#include "base/Composition.h"
+#include <qpushbutton.h>
+#include <qdatetime.h>
+
+
+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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "TextEventDialog.h"
+#include <kapplication.h>
+
+#include <klocale.h>
+#include "misc/Strings.h"
+#include "document/ConfigGroups.h"
+#include "base/NotationTypes.h"
+#include "gui/editors/notation/NotePixmapFactory.h"
+#include <kcombobox.h>
+#include <kconfig.h>
+#include <kdialogbase.h>
+#include <qbitmap.h>
+#include <qgrid.h>
+#include <qgroupbox.h>
+#include <qlabel.h>
+#include <qlineedit.h>
+#include <qobject.h>
+#include <qpainter.h>
+#include <qpixmap.h>
+#include <qstring.h>
+#include <qvbox.h>
+#include <qwidget.h>
+#include <qspinbox.h>
+
+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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_TEXTEVENTDIALOG_H_
+#define _RG_TEXTEVENTDIALOG_H_
+
+#include "base/NotationTypes.h"
+#include <string>
+#include <kdialogbase.h>
+#include <qstring.h>
+#include <vector>
+
+
+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<std::string> m_styles;
+// std::vector<std::string> 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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "TimeDialog.h"
+
+#include <klocale.h>
+#include "base/Composition.h"
+#include "gui/widgets/TimeWidget.h"
+#include <kdialogbase.h>
+#include <qstring.h>
+#include <qvbox.h>
+#include <qwidget.h>
+
+
+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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_TIMEDIALOG_H_
+#define _RG_TIMEDIALOG_H_
+
+#include <kdialogbase.h>
+#include <qstring.h>
+#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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "TimeSignatureDialog.h"
+#include <kapplication.h>
+
+#include <klocale.h>
+#include "document/ConfigGroups.h"
+#include "base/Composition.h"
+#include "base/NotationTypes.h"
+#include "gui/widgets/TimeWidget.h"
+#include "gui/widgets/BigArrowButton.h"
+#include <kconfig.h>
+#include <kdialogbase.h>
+#include <qbuttongroup.h>
+#include <qcheckbox.h>
+#include <qfont.h>
+#include <qgroupbox.h>
+#include <qhbox.h>
+#include <qlabel.h>
+#include <qobject.h>
+#include <qradiobutton.h>
+#include <qstring.h>
+#include <qvbox.h>
+#include <qwidget.h>
+
+
+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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_TIMESIGNATUREDIALOG_H_
+#define _RG_TIMESIGNATUREDIALOG_H_
+
+#include "base/NotationTypes.h"
+#include <kdialogbase.h>
+#include <qstring.h>
+#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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "TransportDialog.h"
+
+#include <klocale.h>
+#include <kstddirs.h>
+#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 <kconfig.h>
+#include <kglobal.h>
+#include <qaccel.h>
+#include <qcolor.h>
+#include <qcstring.h>
+#include <qdatastream.h>
+#include <qfont.h>
+#include <qhbox.h>
+#include <qlabel.h>
+#include <qpalette.h>
+#include <qpixmap.h>
+#include <qpushbutton.h>
+#include <qstring.h>
+#include <qtimer.h>
+#include <qwidget.h>
+
+
+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<std::string, TimeDisplayMode>::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<std::string, TimeDisplayMode>::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<QWidget *>(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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_ROSEGARDENTRANSPORTDIALOG_H_
+#define _RG_ROSEGARDENTRANSPORTDIALOG_H_
+
+#include <map>
+#include <kdockwidget.h>
+#include <qcolor.h>
+#include <qpixmap.h>
+#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<int, QPixmap> 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<std::string, TimeDisplayMode> 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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "TriggerSegmentDialog.h"
+#include <kapplication.h>
+
+#include "base/BaseProperties.h"
+#include <klocale.h>
+#include "misc/Strings.h"
+#include "document/ConfigGroups.h"
+#include "base/Composition.h"
+#include "base/TriggerSegment.h"
+#include <kcombobox.h>
+#include <kconfig.h>
+#include <kdialogbase.h>
+#include <qcheckbox.h>
+#include <qframe.h>
+#include <qlabel.h>
+#include <qstring.h>
+#include <qvbox.h>
+#include <qwidget.h>
+#include <qlayout.h>
+
+
+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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_TRIGGERSEGMENTDIALOG_H_
+#define _RG_TRIGGERSEGMENTDIALOG_H_
+
+#include "base/TriggerSegment.h"
+#include <string>
+#include <kdialogbase.h>
+
+
+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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "TupletDialog.h"
+#include <qlayout.h>
+
+#include <klocale.h>
+#include "base/NotationTypes.h"
+#include "gui/editors/notation/NotationStrings.h"
+#include "gui/editors/notation/NotePixmapFactory.h"
+#include <kcombobox.h>
+#include <kdialogbase.h>
+#include <qcheckbox.h>
+#include <qframe.h>
+#include <qgrid.h>
+#include <qgroupbox.h>
+#include <qlabel.h>
+#include <qobject.h>
+#include <qstring.h>
+#include <qvbox.h>
+#include <qwidget.h>
+
+
+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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_TUPLETDIALOG_H_
+#define _RG_TUPLETDIALOG_H_
+
+#include "base/NotationTypes.h"
+#include <kdialogbase.h>
+#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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "UnusedAudioSelectionDialog.h"
+
+#include <klocale.h>
+#include <kdialogbase.h>
+#include <klistview.h>
+#include <qfileinfo.h>
+#include <qlabel.h>
+#include <qlistview.h>
+#include <qstring.h>
+#include <qvbox.h>
+#include <qwidget.h>
+
+
+namespace Rosegarden
+{
+
+UnusedAudioSelectionDialog::UnusedAudioSelectionDialog(QWidget *parent,
+ QString introductoryText,
+ std::vector<QString> 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<QString>
+UnusedAudioSelectionDialog::getSelectedAudioFileNames() const
+{
+ std::vector<QString> 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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_UNUSEDAUDIOSELECTIONDIALOG_H_
+#define _RG_UNUSEDAUDIOSELECTIONDIALOG_H_
+
+#include <kdialogbase.h>
+#include <qstring.h>
+#include <vector>
+
+
+class QWidget;
+class QListView;
+
+
+namespace Rosegarden
+{
+
+
+
+class UnusedAudioSelectionDialog : public KDialogBase
+{
+public:
+ UnusedAudioSelectionDialog(QWidget *,
+ QString introductoryText,
+ std::vector<QString> fileNames,
+ bool offerCancel = true);
+
+ std::vector<QString> 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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "UseOrnamentDialog.h"
+#include <qlayout.h>
+#include <kapplication.h>
+
+#include "base/BaseProperties.h"
+#include <klocale.h>
+#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 <kcombobox.h>
+#include <kconfig.h>
+#include <kdialogbase.h>
+#include <qcheckbox.h>
+#include <qframe.h>
+#include <qgroupbox.h>
+#include <qlabel.h>
+#include <qlineedit.h>
+#include <qstring.h>
+#include <qvbox.h>
+#include <qwidget.h>
+
+
+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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_USEORNAMENTDIALOG_H_
+#define _RG_USEORNAMENTDIALOG_H_
+
+#include "base/TriggerSegment.h"
+#include "base/NotationTypes.h"
+#include <string>
+#include <kdialogbase.h>
+#include <vector>
+
+
+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<Mark> 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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "EventView.h"
+#include "EventViewItem.h"
+#include "TrivialVelocityDialog.h"
+#include "base/BaseProperties.h"
+#include "misc/Debug.h"
+#include "misc/Strings.h"
+#include "base/Clipboard.h"
+#include "base/Event.h"
+#include "base/MidiTypes.h"
+#include "base/NotationTypes.h"
+#include "base/RealTime.h"
+#include "base/Segment.h"
+#include "base/SegmentPerformanceHelper.h"
+#include "base/Selection.h"
+#include "base/Track.h"
+#include "base/TriggerSegment.h"
+#include "commands/edit/CopyCommand.h"
+#include "commands/edit/CutCommand.h"
+#include "commands/edit/EraseCommand.h"
+#include "commands/edit/EventEditCommand.h"
+#include "commands/edit/PasteEventsCommand.h"
+#include "commands/edit/EventInsertionCommand.h"
+#include "commands/segment/SegmentLabelCommand.h"
+#include "commands/segment/SetTriggerSegmentBasePitchCommand.h"
+#include "commands/segment/SetTriggerSegmentBaseVelocityCommand.h"
+#include "commands/segment/SetTriggerSegmentDefaultRetuneCommand.h"
+#include "commands/segment/SetTriggerSegmentDefaultTimeAdjustCommand.h"
+#include "document/RosegardenGUIDoc.h"
+#include "document/ConfigGroups.h"
+#include "gui/dialogs/EventEditDialog.h"
+#include "gui/dialogs/PitchDialog.h"
+#include "gui/dialogs/SimpleEventEditDialog.h"
+#include "gui/general/EditViewBase.h"
+#include "gui/general/MidiPitchLabel.h"
+#include "gui/kdeext/KTmpStatusMsg.h"
+#include "gui/dialogs/EventFilterDialog.h"
+#include <kaction.h>
+#include <kconfig.h>
+#include <klocale.h>
+#include <kstatusbar.h>
+#include <kstddirs.h>
+#include <kglobal.h>
+#include <klineeditdlg.h>
+#include <klistview.h>
+#include <kxmlguiclient.h>
+#include <qbuttongroup.h>
+#include <qcanvas.h>
+#include <qcheckbox.h>
+#include <qdialog.h>
+#include <qframe.h>
+#include <qgroupbox.h>
+#include <qiconset.h>
+#include <qlabel.h>
+#include <qlayout.h>
+#include <qlistview.h>
+#include <qpixmap.h>
+#include <qpoint.h>
+#include <qpopupmenu.h>
+#include <qpushbutton.h>
+#include <qsize.h>
+#include <qstring.h>
+#include <qwidget.h>
+#include <algorithm>
+
+
+namespace Rosegarden
+{
+
+int
+EventView::m_lastSetEventFilter = -1;
+
+
+EventView::EventView(RosegardenGUIDoc *doc,
+ std::vector<Segment *> segments,
+ QWidget *parent):
+ EditViewBase(doc, segments, 2, parent, "eventview"),
+ m_eventFilter(Note | Text | SystemExclusive | Controller |
+ ProgramChange | PitchBend | Indication | Other),
+ m_menu(0)
+{
+ m_isTriggerSegment = false;
+ m_triggerName = m_triggerPitch = m_triggerVelocity = 0;
+
+ if (!segments.empty()) {
+ Segment *s = *segments.begin();
+ if (s->getComposition()) {
+ int id = s->getComposition()->getTriggerSegmentId(s);
+ if (id >= 0)
+ m_isTriggerSegment = true;
+ }
+ }
+
+ if (m_lastSetEventFilter < 0)
+ m_lastSetEventFilter = m_eventFilter;
+ else
+ m_eventFilter = m_lastSetEventFilter;
+
+ initStatusBar();
+ setupActions();
+
+ // define some note filtering buttons in a group
+ //
+ m_filterGroup =
+ new QButtonGroup(1, Horizontal, i18n("Event filters"), getCentralWidget());
+
+ m_noteCheckBox = new QCheckBox(i18n("Note"), m_filterGroup);
+ m_programCheckBox = new QCheckBox(i18n("Program Change"), m_filterGroup);
+ m_controllerCheckBox = new QCheckBox(i18n("Controller"), m_filterGroup);
+ m_pitchBendCheckBox = new QCheckBox(i18n("Pitch Bend"), m_filterGroup);
+ m_sysExCheckBox = new QCheckBox(i18n("System Exclusive"), m_filterGroup);
+ m_keyPressureCheckBox = new QCheckBox(i18n("Key Pressure"), m_filterGroup);
+ m_channelPressureCheckBox = new QCheckBox(i18n("Channel Pressure"), m_filterGroup);
+ m_restCheckBox = new QCheckBox(i18n("Rest"), m_filterGroup);
+ m_indicationCheckBox = new QCheckBox(i18n("Indication"), m_filterGroup);
+ m_textCheckBox = new QCheckBox(i18n("Text"), m_filterGroup);
+ m_otherCheckBox = new QCheckBox(i18n("Other"), m_filterGroup);
+ m_grid->addWidget(m_filterGroup, 2, 0);
+
+ // Connect up
+ //
+ connect(m_filterGroup, SIGNAL(released(int)),
+ SLOT(slotModifyFilter(int)));
+
+ m_eventList = new KListView(getCentralWidget());
+ m_eventList->setItemsRenameable(true);
+
+ m_grid->addWidget(m_eventList, 2, 1);
+
+ if (m_isTriggerSegment) {
+
+ int id = segments[0]->getComposition()->getTriggerSegmentId(segments[0]);
+ TriggerSegmentRec *rec =
+ segments[0]->getComposition()->getTriggerSegmentRec(id);
+
+ QGroupBox *groupBox = new QGroupBox
+ (1, Horizontal, i18n("Triggered Segment Properties"), getCentralWidget());
+
+ QFrame *frame = new QFrame(groupBox);
+ QGridLayout *layout = new QGridLayout(frame, 5, 3, 5, 5);
+
+ layout->addWidget(new QLabel(i18n("Label: "), frame), 0, 0);
+ QString label = strtoqstr(segments[0]->getLabel());
+ if (label == "")
+ label = i18n("<no label>");
+ m_triggerName = new QLabel(label, frame);
+ layout->addWidget(m_triggerName, 0, 1);
+ QPushButton *editButton = new QPushButton(i18n("edit"), frame);
+ layout->addWidget(editButton, 0, 2);
+ connect(editButton, SIGNAL(clicked()), this, SLOT(slotEditTriggerName()));
+
+ layout->addWidget(new QLabel(i18n("Base pitch: "), frame), 1, 0);
+ m_triggerPitch = new QLabel(QString("%1").arg(rec->getBasePitch()), frame);
+ layout->addWidget(m_triggerPitch, 1, 1);
+ editButton = new QPushButton(i18n("edit"), frame);
+ layout->addWidget(editButton, 1, 2);
+ connect(editButton, SIGNAL(clicked()), this, SLOT(slotEditTriggerPitch()));
+
+ layout->addWidget(new QLabel(i18n("Base velocity: "), frame), 2, 0);
+ m_triggerVelocity = new QLabel(QString("%1").arg(rec->getBaseVelocity()), frame);
+ layout->addWidget(m_triggerVelocity, 2, 1);
+ editButton = new QPushButton(i18n("edit"), frame);
+ layout->addWidget(editButton, 2, 2);
+ connect(editButton, SIGNAL(clicked()), this, SLOT(slotEditTriggerVelocity()));
+
+ /*!!! Comment out these two options, which are not yet used
+ anywhere else -- intended for use with library ornaments, not
+ yet implemented
+
+ layout->addWidget(new QLabel(i18n("Default timing: "), frame), 3, 0);
+
+ KComboBox *adjust = new KComboBox(frame);
+ layout->addMultiCellWidget(adjust, 3, 3, 1, 2);
+ adjust->insertItem(i18n("As stored"));
+ adjust->insertItem(i18n("Truncate if longer than note"));
+ adjust->insertItem(i18n("End at same time as note"));
+ adjust->insertItem(i18n("Stretch or squash segment to note duration"));
+
+ std::string timing = rec->getDefaultTimeAdjust();
+ if (timing == BaseProperties::TRIGGER_SEGMENT_ADJUST_NONE) {
+ adjust->setCurrentItem(0);
+ } else if (timing == BaseProperties::TRIGGER_SEGMENT_ADJUST_SQUISH) {
+ adjust->setCurrentItem(3);
+ } else if (timing == BaseProperties::TRIGGER_SEGMENT_ADJUST_SYNC_START) {
+ adjust->setCurrentItem(1);
+ } else if (timing == BaseProperties::TRIGGER_SEGMENT_ADJUST_SYNC_END) {
+ adjust->setCurrentItem(2);
+ }
+
+ connect(adjust, SIGNAL(activated(int)), this, SLOT(slotTriggerTimeAdjustChanged(int)));
+
+ QCheckBox *retune = new QCheckBox(i18n("Adjust pitch to trigger note by default"), frame);
+ retune->setChecked(rec->getDefaultRetune());
+ connect(retune, SIGNAL(clicked()), this, SLOT(slotTriggerRetuneChanged()));
+ layout->addMultiCellWidget(retune, 4, 4, 1, 2);
+
+ */
+
+ m_grid->addWidget(groupBox, 2, 2);
+
+ }
+
+ updateViewCaption();
+
+ for (unsigned int i = 0; i < m_segments.size(); ++i) {
+ m_segments[i]->addObserver(this);
+ }
+
+ // Connect double clicker
+ //
+ connect(m_eventList, SIGNAL(doubleClicked(QListViewItem*)),
+ SLOT(slotPopupEventEditor(QListViewItem*)));
+
+ connect(m_eventList,
+ SIGNAL(rightButtonPressed(QListViewItem*, const QPoint&, int)),
+ SLOT(slotPopupMenu(QListViewItem*, const QPoint&, int)));
+
+ m_eventList->setAllColumnsShowFocus(true);
+ m_eventList->setSelectionMode(QListView::Extended);
+
+ m_eventList->addColumn(i18n("Time "));
+ m_eventList->addColumn(i18n("Duration "));
+ m_eventList->addColumn(i18n("Event Type "));
+ m_eventList->addColumn(i18n("Pitch "));
+ m_eventList->addColumn(i18n("Velocity "));
+ m_eventList->addColumn(i18n("Type (Data1) "));
+ m_eventList->addColumn(i18n("Value (Data2) "));
+
+ for (int col = 0; col < m_eventList->columns(); ++col)
+ m_eventList->setRenameable(col, true);
+
+ readOptions();
+ setButtonsToFilter();
+ applyLayout();
+
+ makeInitialSelection(doc->getComposition().getPosition());
+
+ slotCompositionStateUpdate();
+
+ setOutOfCtor();
+}
+
+EventView::~EventView()
+{
+ for (unsigned int i = 0; i < m_segments.size(); ++i) {
+ RG_DEBUG << "~EventView - removing this observer from " << m_segments[i] << endl;
+ m_segments[i]->removeObserver(this);
+ }
+}
+
+void
+EventView::eventRemoved(const Segment *, Event *e)
+{
+ m_deletedEvents.insert(e);
+}
+
+void
+EventView::segmentDeleted(const Segment *s)
+{
+ std::vector<Segment *>::iterator i = std::find(m_segments.begin(), m_segments.end(), s);
+
+ if (i != m_segments.end()) {
+ m_segments.erase(i);
+ } else {
+ RG_DEBUG << "%%% WARNING - EventView::segmentDeleted() called on non-registered segment - should not happen\n";
+ }
+
+}
+
+bool
+EventView::applyLayout(int /*staffNo*/)
+{
+ // If no selection has already been set then we copy what's
+ // already set and try to replicate this after the rebuild
+ // of the view.
+ //
+ if (m_listSelection.size() == 0) {
+ QPtrList<QListViewItem> selection = m_eventList->selectedItems();
+
+ if (selection.count()) {
+ QPtrListIterator<QListViewItem> it(selection);
+ QListViewItem *listItem;
+
+ while ((listItem = it.current()) != 0) {
+ m_listSelection.push_back(m_eventList->itemIndex(*it));
+ ++it;
+ }
+ }
+ }
+
+ // Ok, recreate list
+ //
+ m_eventList->clear();
+
+ m_config->setGroup(EventViewConfigGroup);
+ int timeMode = m_config->readNumEntry("timemode", 0);
+
+ for (unsigned int i = 0; i < m_segments.size(); i++) {
+ SegmentPerformanceHelper helper(*m_segments[i]);
+
+ for (Segment::iterator it = m_segments[i]->begin();
+ m_segments[i]->isBeforeEndMarker(it); it++) {
+ timeT eventTime =
+ helper.getSoundingAbsoluteTime(it);
+
+ QString velyStr;
+ QString pitchStr;
+ QString data1Str = "";
+ QString data2Str = "";
+ QString durationStr;
+
+ // Event filters
+ //
+
+ if ((*it)->isa(Note::EventRestType)) {
+ if (!(m_eventFilter & Rest))
+ continue;
+
+ } else if ((*it)->isa(Note::EventType)) {
+ if (!(m_eventFilter & Note))
+ continue;
+
+ } else if ((*it)->isa(Indication::EventType)) {
+ if (!(m_eventFilter & Indication))
+ continue;
+
+ } else if ((*it)->isa(PitchBend::EventType)) {
+ if (!(m_eventFilter & PitchBend))
+ continue;
+
+ } else if ((*it)->isa(SystemExclusive::EventType)) {
+ if (!(m_eventFilter & SystemExclusive))
+ continue;
+
+ } else if ((*it)->isa(ProgramChange::EventType)) {
+ if (!(m_eventFilter & ProgramChange))
+ continue;
+
+ } else if ((*it)->isa(ChannelPressure::EventType)) {
+ if (!(m_eventFilter & ChannelPressure))
+ continue;
+
+ } else if ((*it)->isa(KeyPressure::EventType)) {
+ if (!(m_eventFilter & KeyPressure))
+ continue;
+
+ } else if ((*it)->isa(Controller::EventType)) {
+ if (!(m_eventFilter & Controller))
+ continue;
+
+ } else if ((*it)->isa(Text::EventType)) {
+ if (!(m_eventFilter & Text))
+ continue;
+
+ } else {
+ if (!(m_eventFilter & Other))
+ continue;
+ }
+
+ // avoid debug stuff going to stderr if no properties found
+
+ if ((*it)->has(BaseProperties::PITCH)) {
+ int p = (*it)->get
+ <Int>(BaseProperties::PITCH);
+ pitchStr = QString("%1 %2 ")
+ .arg(p).arg(MidiPitchLabel(p).getQString());
+ } else if ((*it)->isa(Note::EventType)) {
+ pitchStr = "<not set>";
+ }
+
+ if ((*it)->has(BaseProperties::VELOCITY)) {
+ velyStr = QString("%1 ").
+ arg((*it)->get
+ <Int>(BaseProperties::VELOCITY));
+ } else if ((*it)->isa(Note::EventType)) {
+ velyStr = "<not set>";
+ }
+
+ if ((*it)->has(Controller::NUMBER)) {
+ data1Str = QString("%1 ").
+ arg((*it)->get
+ <Int>(Controller::NUMBER));
+ } else if ((*it)->has(Text::TextTypePropertyName)) {
+ data1Str = QString("%1 ").
+ arg(strtoqstr((*it)->get
+ <String>
+ (Text::TextTypePropertyName)));
+ } else if ((*it)->has(Indication::
+ IndicationTypePropertyName)) {
+ data1Str = QString("%1 ").
+ arg(strtoqstr((*it)->get
+ <String>
+ (Indication::
+ IndicationTypePropertyName)));
+ } else if ((*it)->has(::Rosegarden::Key::KeyPropertyName)) {
+ data1Str = QString("%1 ").
+ arg(strtoqstr((*it)->get
+ <String>
+ (::Rosegarden::Key::KeyPropertyName)));
+ } else if ((*it)->has(Clef::ClefPropertyName)) {
+ data1Str = QString("%1 ").
+ arg(strtoqstr((*it)->get
+ <String>
+ (Clef::ClefPropertyName)));
+ } else if ((*it)->has(PitchBend::MSB)) {
+ data1Str = QString("%1 ").
+ arg((*it)->get
+ <Int>(PitchBend::MSB));
+ } else if ((*it)->has(BaseProperties::BEAMED_GROUP_TYPE)) {
+ data1Str = QString("%1 ").
+ arg(strtoqstr((*it)->get
+ <String>
+ (BaseProperties::BEAMED_GROUP_TYPE)));
+ }
+
+ if ((*it)->has(Controller::VALUE)) {
+ data2Str = QString("%1 ").
+ arg((*it)->get
+ <Int>(Controller::VALUE));
+ } else if ((*it)->has(Text::TextPropertyName)) {
+ data2Str = QString("%1 ").
+ arg(strtoqstr((*it)->get
+ <String>
+ (Text::TextPropertyName)));
+ /*!!!
+ } else if ((*it)->has(Indication::
+ IndicationTypePropertyName)) {
+ data2Str = QString("%1 ").
+ arg((*it)->get<Int>(Indication::
+ IndicationDurationPropertyName));
+ */
+ } else if ((*it)->has(PitchBend::LSB)) {
+ data2Str = QString("%1 ").
+ arg((*it)->get
+ <Int>(PitchBend::LSB));
+ } else if ((*it)->has(BaseProperties::BEAMED_GROUP_ID)) {
+ data2Str = i18n("(group %1) ").
+ arg((*it)->get
+ <Int>(BaseProperties::BEAMED_GROUP_ID));
+ }
+
+ if ((*it)->has(ProgramChange::PROGRAM)) {
+ data1Str = QString("%1 ").
+ arg((*it)->get
+ <Int>(ProgramChange::PROGRAM) + 1);
+ }
+
+ if ((*it)->has(ChannelPressure::PRESSURE)) {
+ data1Str = QString("%1 ").
+ arg((*it)->get
+ <Int>(ChannelPressure::PRESSURE));
+ }
+
+ if ((*it)->isa(KeyPressure::EventType) &&
+ (*it)->has(KeyPressure::PITCH)) {
+ data1Str = QString("%1 ").
+ arg((*it)->get
+ <Int>(KeyPressure::PITCH));
+ }
+
+ if ((*it)->has(KeyPressure::PRESSURE)) {
+ data2Str = QString("%1 ").
+ arg((*it)->get
+ <Int>(KeyPressure::PRESSURE));
+ }
+
+
+ if ((*it)->getDuration() > 0 ||
+ (*it)->isa(Note::EventType) ||
+ (*it)->isa(Note::EventRestType)) {
+ durationStr = makeDurationString(eventTime,
+ (*it)->getDuration(),
+ timeMode);
+ }
+
+ QString timeStr = makeTimeString(eventTime, timeMode);
+
+ new EventViewItem(m_segments[i],
+ *it,
+ m_eventList,
+ timeStr,
+ durationStr,
+ strtoqstr((*it)->getType()),
+ pitchStr,
+ velyStr,
+ data1Str,
+ data2Str);
+ }
+ }
+
+
+ if (m_eventList->childCount() == 0) {
+ if (m_segments.size())
+ new QListViewItem(m_eventList,
+ i18n("<no events at this filter level>"));
+ else
+ new QListViewItem(m_eventList, i18n("<no events>"));
+
+ m_eventList->setSelectionMode(QListView::NoSelection);
+ stateChanged("have_selection", KXMLGUIClient::StateReverse);
+ } else {
+ m_eventList->setSelectionMode(QListView::Extended);
+
+ // If no selection then select the first event
+ if (m_listSelection.size() == 0)
+ m_listSelection.push_back(0);
+
+ stateChanged("have_selection", KXMLGUIClient::StateNoReverse);
+ }
+
+ // Set a selection from a range of indexes
+ //
+ std::vector<int>::iterator sIt = m_listSelection.begin();
+ int index = 0;
+
+ for (; sIt != m_listSelection.end(); ++sIt) {
+ index = *sIt;
+
+ while (index > 0 && !m_eventList->itemAtIndex(index))
+ index--;
+
+ m_eventList->setSelected(m_eventList->itemAtIndex(index), true);
+ m_eventList->setCurrentItem(m_eventList->itemAtIndex(index));
+
+ // ensure visible
+ m_eventList->ensureItemVisible(m_eventList->itemAtIndex(index));
+ }
+
+ m_listSelection.clear();
+ m_deletedEvents.clear();
+
+ return true;
+}
+
+void
+EventView::makeInitialSelection(timeT time)
+{
+ m_listSelection.clear();
+
+ EventViewItem *goodItem = 0;
+ int goodItemNo = 0;
+
+ int i = 0;
+
+ for (QListViewItem *child = m_eventList->firstChild();
+ child;
+ child = child->nextSibling()) {
+
+ EventViewItem * item = dynamic_cast<EventViewItem *>(child);
+
+ if (item) {
+ if (item->getEvent()->getAbsoluteTime() > time)
+ break;
+ goodItem = item;
+ goodItemNo = i;
+ }
+
+ ++i;
+ }
+ /*!!!
+ for (int i = 0; m_eventList->itemAtIndex(i); ++i) {
+
+ EventViewItem *item = dynamic_cast<EventViewItem *>
+ (m_eventList->itemAtIndex(i));
+
+ if (item) {
+ if (item->getEvent()->getAbsoluteTime() > time) break;
+ goodItem = item;
+ goodItemNo = i;
+ }
+ }
+ */
+ if (goodItem) {
+ m_listSelection.push_back(goodItemNo);
+ m_eventList->setSelected(goodItem, true);
+ m_eventList->ensureItemVisible(goodItem);
+ }
+}
+
+QString
+EventView::makeTimeString(timeT time, int timeMode)
+{
+ switch (timeMode) {
+
+ case 0: // musical time
+ {
+ int bar, beat, fraction, remainder;
+ getDocument()->getComposition().getMusicalTimeForAbsoluteTime
+ (time, bar, beat, fraction, remainder);
+ ++bar;
+ return QString("%1%2%3-%4%5-%6%7-%8%9 ")
+ .arg(bar / 100)
+ .arg((bar % 100) / 10)
+ .arg(bar % 10)
+ .arg(beat / 10)
+ .arg(beat % 10)
+ .arg(fraction / 10)
+ .arg(fraction % 10)
+ .arg(remainder / 10)
+ .arg(remainder % 10);
+ }
+
+ case 1: // real time
+ {
+ RealTime rt =
+ getDocument()->getComposition().getElapsedRealTime(time);
+ // return QString("%1 ").arg(rt.toString().c_str());
+ return QString("%1 ").arg(rt.toText().c_str());
+ }
+
+ default:
+ return QString("%1 ").arg(time);
+ }
+}
+
+QString
+EventView::makeDurationString(timeT time,
+ timeT duration, int timeMode)
+{
+ switch (timeMode) {
+
+ case 0: // musical time
+ {
+ int bar, beat, fraction, remainder;
+ getDocument()->getComposition().getMusicalTimeForDuration
+ (time, duration, bar, beat, fraction, remainder);
+ return QString("%1%2%3-%4%5-%6%7-%8%9 ")
+ .arg(bar / 100)
+ .arg((bar % 100) / 10)
+ .arg(bar % 10)
+ .arg(beat / 10)
+ .arg(beat % 10)
+ .arg(fraction / 10)
+ .arg(fraction % 10)
+ .arg(remainder / 10)
+ .arg(remainder % 10);
+ }
+
+ case 1: // real time
+ {
+ RealTime rt =
+ getDocument()->getComposition().getRealTimeDifference
+ (time, time + duration);
+ // return QString("%1 ").arg(rt.toString().c_str());
+ return QString("%1 ").arg(rt.toText().c_str());
+ }
+
+ default:
+ return QString("%1 ").arg(duration);
+ }
+}
+
+void
+EventView::refreshSegment(Segment * /*segment*/,
+ timeT /*startTime*/,
+ timeT /*endTime*/)
+{
+ RG_DEBUG << "EventView::refreshSegment" << endl;
+ applyLayout(0);
+}
+
+void
+EventView::updateView()
+{
+ m_eventList->update();
+}
+
+void
+EventView::slotEditTriggerName()
+{
+ bool ok = false;
+ QString newLabel = KLineEditDlg::getText(i18n("Segment label"), i18n("Label:"),
+ strtoqstr(m_segments[0]->getLabel()),
+ &ok, this);
+
+ if (ok) {
+ SegmentSelection selection;
+ selection.insert(m_segments[0]);
+ SegmentLabelCommand *cmd = new SegmentLabelCommand(selection, newLabel);
+ addCommandToHistory(cmd);
+ m_triggerName->setText(newLabel);
+ }
+}
+
+void
+EventView::slotEditTriggerPitch()
+{
+ int id = m_segments[0]->getComposition()->getTriggerSegmentId(m_segments[0]);
+
+ TriggerSegmentRec *rec =
+ m_segments[0]->getComposition()->getTriggerSegmentRec(id);
+
+ PitchDialog *dlg = new PitchDialog(this, i18n("Base pitch"), rec->getBasePitch());
+
+ if (dlg->exec() == QDialog::Accepted) {
+ addCommandToHistory(new SetTriggerSegmentBasePitchCommand
+ (&getDocument()->getComposition(), id, dlg->getPitch()));
+ m_triggerPitch->setText(QString("%1").arg(dlg->getPitch()));
+ }
+}
+
+void
+EventView::slotEditTriggerVelocity()
+{
+ int id = m_segments[0]->getComposition()->getTriggerSegmentId(m_segments[0]);
+
+ TriggerSegmentRec *rec =
+ m_segments[0]->getComposition()->getTriggerSegmentRec(id);
+
+ TrivialVelocityDialog *dlg = new TrivialVelocityDialog
+ (this, i18n("Base velocity"), rec->getBaseVelocity());
+
+ if (dlg->exec() == QDialog::Accepted) {
+ addCommandToHistory(new SetTriggerSegmentBaseVelocityCommand
+ (&getDocument()->getComposition(), id, dlg->getVelocity()));
+ m_triggerVelocity->setText(QString("%1").arg(dlg->getVelocity()));
+ }
+}
+
+void
+EventView::slotTriggerTimeAdjustChanged(int option)
+{
+ std::string adjust = BaseProperties::TRIGGER_SEGMENT_ADJUST_SQUISH;
+
+ switch (option) {
+
+ case 0:
+ adjust = BaseProperties::TRIGGER_SEGMENT_ADJUST_NONE;
+ break;
+ case 1:
+ adjust = BaseProperties::TRIGGER_SEGMENT_ADJUST_SYNC_START;
+ break;
+ case 2:
+ adjust = BaseProperties::TRIGGER_SEGMENT_ADJUST_SYNC_END;
+ break;
+ case 3:
+ adjust = BaseProperties::TRIGGER_SEGMENT_ADJUST_SQUISH;
+ break;
+
+ default:
+ break;
+ }
+
+ int id = m_segments[0]->getComposition()->getTriggerSegmentId(m_segments[0]);
+
+ TriggerSegmentRec *rec =
+ m_segments[0]->getComposition()->getTriggerSegmentRec(id);
+
+ addCommandToHistory(new SetTriggerSegmentDefaultTimeAdjustCommand
+ (&getDocument()->getComposition(), id, adjust));
+}
+
+void
+EventView::slotTriggerRetuneChanged()
+{
+ int id = m_segments[0]->getComposition()->getTriggerSegmentId(m_segments[0]);
+
+ TriggerSegmentRec *rec =
+ m_segments[0]->getComposition()->getTriggerSegmentRec(id);
+
+ addCommandToHistory(new SetTriggerSegmentDefaultRetuneCommand
+ (&getDocument()->getComposition(), id, !rec->getDefaultRetune()));
+}
+
+void
+EventView::slotEditCut()
+{
+ QPtrList<QListViewItem> selection = m_eventList->selectedItems();
+
+ if (selection.count() == 0)
+ return ;
+
+ RG_DEBUG << "EventView::slotEditCut - cutting "
+ << selection.count() << " items" << endl;
+
+ QPtrListIterator<QListViewItem> it(selection);
+ QListViewItem *listItem;
+ EventViewItem *item;
+ EventSelection *cutSelection = 0;
+ int itemIndex = -1;
+
+ while ((listItem = it.current()) != 0) {
+ item = dynamic_cast<EventViewItem*>((*it));
+
+ if (itemIndex == -1)
+ itemIndex = m_eventList->itemIndex(*it);
+
+ if (item) {
+ if (cutSelection == 0)
+ cutSelection =
+ new EventSelection(*(item->getSegment()));
+
+ cutSelection->addEvent(item->getEvent());
+ }
+ ++it;
+ }
+
+ if (cutSelection) {
+ if (itemIndex >= 0) {
+ m_listSelection.clear();
+ m_listSelection.push_back(itemIndex);
+ }
+
+ addCommandToHistory(new CutCommand(*cutSelection,
+ getDocument()->getClipboard()));
+ }
+}
+
+void
+EventView::slotEditCopy()
+{
+ QPtrList<QListViewItem> selection = m_eventList->selectedItems();
+
+ if (selection.count() == 0)
+ return ;
+
+ RG_DEBUG << "EventView::slotEditCopy - copying "
+ << selection.count() << " items" << endl;
+
+ QPtrListIterator<QListViewItem> it(selection);
+ QListViewItem *listItem;
+ EventViewItem *item;
+ EventSelection *copySelection = 0;
+
+ // clear the selection for post modification updating
+ //
+ m_listSelection.clear();
+
+ while ((listItem = it.current()) != 0) {
+ item = dynamic_cast<EventViewItem*>((*it));
+
+ m_listSelection.push_back(m_eventList->itemIndex(*it));
+
+ if (item) {
+ if (copySelection == 0)
+ copySelection =
+ new EventSelection(*(item->getSegment()));
+
+ copySelection->addEvent(item->getEvent());
+ }
+ ++it;
+ }
+
+ if (copySelection) {
+ addCommandToHistory(new CopyCommand(*copySelection,
+ getDocument()->getClipboard()));
+ }
+}
+
+void
+EventView::slotEditPaste()
+{
+ if (getDocument()->getClipboard()->isEmpty()) {
+ slotStatusHelpMsg(i18n("Clipboard is empty"));
+ return ;
+ }
+
+ KTmpStatusMsg msg(i18n("Inserting clipboard contents..."), this);
+
+ timeT insertionTime = 0;
+
+ QPtrList<QListViewItem> selection = m_eventList->selectedItems();
+ if (selection.count()) {
+ EventViewItem *item = dynamic_cast<EventViewItem*>(selection.at(0));
+
+ if (item)
+ insertionTime = item->getEvent()->getAbsoluteTime();
+
+ // remember the selection
+ //
+ m_listSelection.clear();
+
+ QPtrListIterator<QListViewItem> it(selection);
+ QListViewItem *listItem;
+
+ while ((listItem = it.current()) != 0) {
+ m_listSelection.push_back(m_eventList->itemIndex(*it));
+ ++it;
+ }
+ }
+
+
+ PasteEventsCommand *command = new PasteEventsCommand
+ (*m_segments[0], getDocument()->getClipboard(),
+ insertionTime, PasteEventsCommand::MatrixOverlay);
+
+ if (!command->isPossible()) {
+ slotStatusHelpMsg(i18n("Couldn't paste at this point"));
+ } else
+ addCommandToHistory(command);
+
+ RG_DEBUG << "EventView::slotEditPaste - pasting "
+ << selection.count() << " items" << endl;
+}
+
+void
+EventView::slotEditDelete()
+{
+ QPtrList<QListViewItem> selection = m_eventList->selectedItems();
+ if (selection.count() == 0)
+ return ;
+
+ RG_DEBUG << "EventView::slotEditDelete - deleting "
+ << selection.count() << " items" << endl;
+
+ QPtrListIterator<QListViewItem> it(selection);
+ QListViewItem *listItem;
+ EventViewItem *item;
+ EventSelection *deleteSelection = 0;
+ int itemIndex = -1;
+
+ while ((listItem = it.current()) != 0) {
+ item = dynamic_cast<EventViewItem*>((*it));
+
+ if (itemIndex == -1)
+ itemIndex = m_eventList->itemIndex(*it);
+
+ if (item) {
+ if (m_deletedEvents.find(item->getEvent()) != m_deletedEvents.end()) {
+ ++it;
+ continue;
+ }
+
+ if (deleteSelection == 0)
+ deleteSelection =
+ new EventSelection(*m_segments[0]);
+
+ deleteSelection->addEvent(item->getEvent());
+ }
+ ++it;
+ }
+
+ if (deleteSelection) {
+
+ if (itemIndex >= 0) {
+ m_listSelection.clear();
+ m_listSelection.push_back(itemIndex);
+ }
+
+ addCommandToHistory(new EraseCommand(*deleteSelection));
+
+ }
+}
+
+void
+EventView::slotEditInsert()
+{
+ RG_DEBUG << "EventView::slotEditInsert" << endl;
+
+ timeT insertTime = m_segments[0]->getStartTime();
+ timeT insertDuration = 960;
+
+ QPtrList<QListViewItem> selection = m_eventList->selectedItems();
+
+ if (selection.count() > 0) {
+ EventViewItem *item =
+ dynamic_cast<EventViewItem*>(selection.getFirst());
+
+ if (item) {
+ insertTime = item->getEvent()->getAbsoluteTime();
+ insertDuration = item->getEvent()->getDuration();
+ }
+ }
+
+ // Create default event
+ //
+ Event *event =
+ new Event(Note::EventType,
+ insertTime,
+ insertDuration);
+ event->set
+ <Int>(BaseProperties::PITCH, 70);
+ event->set
+ <Int>(BaseProperties::VELOCITY, 100);
+
+ SimpleEventEditDialog dialog(this, getDocument(), *event, true);
+
+ if (dialog.exec() == QDialog::Accepted) {
+ EventInsertionCommand *command =
+ new EventInsertionCommand(*m_segments[0],
+ new Event(dialog.getEvent()));
+ addCommandToHistory(command);
+ }
+}
+
+void
+EventView::slotEditEvent()
+{
+ RG_DEBUG << "EventView::slotEditEvent" << endl;
+
+ QPtrList<QListViewItem> selection = m_eventList->selectedItems();
+
+ if (selection.count() > 0) {
+ EventViewItem *item =
+ dynamic_cast<EventViewItem*>(selection.getFirst());
+
+ if (item) {
+ Event *event = item->getEvent();
+ SimpleEventEditDialog dialog(this, getDocument(), *event, false);
+
+ if (dialog.exec() == QDialog::Accepted && dialog.isModified()) {
+ EventEditCommand *command =
+ new EventEditCommand(*(item->getSegment()),
+ event,
+ dialog.getEvent());
+
+ addCommandToHistory(command);
+ }
+ }
+ }
+}
+
+void
+EventView::slotEditEventAdvanced()
+{
+ RG_DEBUG << "EventView::slotEditEventAdvanced" << endl;
+
+ QPtrList<QListViewItem> selection = m_eventList->selectedItems();
+
+ if (selection.count() > 0) {
+ EventViewItem *item =
+ dynamic_cast<EventViewItem*>(selection.getFirst());
+
+ if (item) {
+ Event *event = item->getEvent();
+ EventEditDialog dialog(this, *event);
+
+ if (dialog.exec() == QDialog::Accepted && dialog.isModified()) {
+ EventEditCommand *command =
+ new EventEditCommand(*(item->getSegment()),
+ event,
+ dialog.getEvent());
+
+ addCommandToHistory(command);
+ }
+ }
+ }
+}
+
+void
+EventView::slotSelectAll()
+{
+ m_listSelection.clear();
+ for (int i = 0; m_eventList->itemAtIndex(i); ++i) {
+ m_listSelection.push_back(i);
+ m_eventList->setSelected(m_eventList->itemAtIndex(i), true);
+ }
+}
+
+void
+EventView::slotClearSelection()
+{
+ m_listSelection.clear();
+ for (int i = 0; m_eventList->itemAtIndex(i); ++i) {
+ m_eventList->setSelected(m_eventList->itemAtIndex(i), false);
+ }
+}
+
+void
+EventView::slotFilterSelection()
+{
+ m_listSelection.clear();
+ QPtrList<QListViewItem> selection = m_eventList->selectedItems();
+ if (selection.count() == 0)
+ return ;
+
+ EventFilterDialog dialog(this);
+ if (dialog.exec() == QDialog::Accepted) {
+
+ QPtrListIterator<QListViewItem> it(selection);
+ QListViewItem *listItem;
+
+ while ((listItem = it.current()) != 0) {
+
+ EventViewItem * item = dynamic_cast<EventViewItem*>(*it);
+ if (!item) {
+ ++it;
+ continue;
+ }
+
+ if (!dialog.keepEvent(item->getEvent())) {
+ m_listSelection.push_back(m_eventList->itemIndex(*it));
+ m_eventList->setSelected(item, false);
+ }
+
+ ++it;
+ }
+ }
+}
+
+void
+EventView::setupActions()
+{
+ EditViewBase::setupActions("eventlist.rc");
+
+ QString pixmapDir = KGlobal::dirs()->findResource("appdata", "pixmaps/");
+ QIconSet icon(QPixmap(pixmapDir + "/toolbar/event-insert.png"));
+
+ new KAction(i18n("&Insert Event"), icon, Key_I, this,
+ SLOT(slotEditInsert()), actionCollection(),
+ "insert");
+
+ QCanvasPixmap pixmap(pixmapDir + "/toolbar/event-delete.png");
+ icon = QIconSet(pixmap);
+
+ new KAction(i18n("&Delete Event"), icon, Key_Delete, this,
+ SLOT(slotEditDelete()), actionCollection(),
+ "delete");
+
+ pixmap.load(pixmapDir + "/toolbar/event-edit.png");
+ icon = QIconSet(pixmap);
+
+ new KAction(i18n("&Edit Event"), icon, Key_E, this,
+ SLOT(slotEditEvent()), actionCollection(),
+ "edit_simple");
+
+ pixmap.load(pixmapDir + "/toolbar/event-edit-advanced.png");
+ icon = QIconSet(pixmap);
+
+ new KAction(i18n("&Advanced Event Editor"), icon, Key_A, this,
+ SLOT(slotEditEventAdvanced()), actionCollection(),
+ "edit_advanced");
+
+ // icon = QIconSet(QCanvasPixmap(pixmapDir + "/toolbar/eventfilter.xpm"));
+ new KAction(i18n("&Filter Selection"), "filter", Key_F, this,
+ SLOT(slotFilterSelection()), actionCollection(),
+ "filter_selection");
+
+ new KAction(i18n("Select &All"), Key_A + CTRL, this,
+ SLOT(slotSelectAll()), actionCollection(),
+ "select_all");
+
+ new KAction(i18n("Clear Selection"), Key_Escape, this,
+ SLOT(slotClearSelection()), actionCollection(),
+ "clear_selection");
+
+ m_config->setGroup(EventViewConfigGroup);
+ int timeMode = m_config->readNumEntry("timemode", 0);
+
+ KRadioAction *action;
+
+ pixmap.load(pixmapDir + "/toolbar/time-musical.png");
+ icon = QIconSet(pixmap);
+
+ action = new KRadioAction(i18n("&Musical Times"), icon, 0, this,
+ SLOT(slotMusicalTime()),
+ actionCollection(), "time_musical");
+ action->setExclusiveGroup("timeMode");
+ if (timeMode == 0)
+ action->setChecked(true);
+
+ pixmap.load(pixmapDir + "/toolbar/time-real.png");
+ icon = QIconSet(pixmap);
+
+ action = new KRadioAction(i18n("&Real Times"), icon, 0, this,
+ SLOT(slotRealTime()),
+ actionCollection(), "time_real");
+ action->setExclusiveGroup("timeMode");
+ if (timeMode == 1)
+ action->setChecked(true);
+
+ pixmap.load(pixmapDir + "/toolbar/time-raw.png");
+ icon = QIconSet(pixmap);
+
+ action = new KRadioAction(i18n("Ra&w Times"), icon, 0, this,
+ SLOT(slotRawTime()),
+ actionCollection(), "time_raw");
+ action->setExclusiveGroup("timeMode");
+ if (timeMode == 2)
+ action->setChecked(true);
+
+ if (m_isTriggerSegment) {
+ KAction *action = actionCollection()->action("open_in_matrix");
+ if (action)
+ delete action;
+ action = actionCollection()->action("open_in_notation");
+ if (action)
+ delete action;
+ }
+
+ createGUI(getRCFileName());
+}
+
+void
+EventView::initStatusBar()
+{
+ KStatusBar* sb = statusBar();
+
+ /*
+ m_hoveredOverNoteName = new QLabel(sb);
+ m_hoveredOverAbsoluteTime = new QLabel(sb);
+
+ m_hoveredOverNoteName->setMinimumWidth(32);
+ m_hoveredOverAbsoluteTime->setMinimumWidth(160);
+
+ sb->addWidget(m_hoveredOverAbsoluteTime);
+ sb->addWidget(m_hoveredOverNoteName);
+ */
+
+ sb->insertItem(KTmpStatusMsg::getDefaultMsg(),
+ KTmpStatusMsg::getDefaultId(), 1);
+ sb->setItemAlignment(KTmpStatusMsg::getDefaultId(),
+ AlignLeft | AlignVCenter);
+
+ //m_selectionCounter = new QLabel(sb);
+ //sb->addWidget(m_selectionCounter);
+}
+
+QSize
+EventView::getViewSize()
+{
+ return m_eventList->size();
+}
+
+void
+EventView::setViewSize(QSize s)
+{
+ m_eventList->setFixedSize(s);
+}
+
+void
+EventView::readOptions()
+{
+ m_config->setGroup(EventViewConfigGroup);
+ EditViewBase::readOptions();
+ m_eventFilter = m_config->readNumEntry("eventfilter", m_eventFilter);
+ m_eventList->restoreLayout(m_config, EventViewLayoutConfigGroupName);
+}
+
+void
+EventView::slotSaveOptions()
+{
+ m_config->setGroup(EventViewConfigGroup);
+ m_config->writeEntry("eventfilter", m_eventFilter);
+ m_eventList->saveLayout(m_config, EventViewLayoutConfigGroupName);
+}
+
+Segment *
+EventView::getCurrentSegment()
+{
+ if (m_segments.empty())
+ return 0;
+ else
+ return *m_segments.begin();
+}
+
+void
+EventView::slotModifyFilter(int button)
+{
+ QCheckBox *checkBox = dynamic_cast<QCheckBox*>(m_filterGroup->find(button));
+
+ if (checkBox == 0)
+ return ;
+
+ if (checkBox->isChecked()) {
+ switch (button) {
+ case 0:
+ m_eventFilter |= EventView::Note;
+ break;
+
+ case 1:
+ m_eventFilter |= EventView::ProgramChange;
+ break;
+
+ case 2:
+ m_eventFilter |= EventView::Controller;
+ break;
+
+ case 3:
+ m_eventFilter |= EventView::PitchBend;
+ break;
+
+ case 4:
+ m_eventFilter |= EventView::SystemExclusive;
+ break;
+
+ case 5:
+ m_eventFilter |= EventView::KeyPressure;
+ break;
+
+ case 6:
+ m_eventFilter |= EventView::ChannelPressure;
+ break;
+
+ case 7:
+ m_eventFilter |= EventView::Rest;
+ break;
+
+ case 8:
+ m_eventFilter |= EventView::Indication;
+ break;
+
+ case 9:
+ m_eventFilter |= EventView::Text;
+ break;
+
+ case 10:
+ m_eventFilter |= EventView::Other;
+ break;
+
+ default:
+ break;
+ }
+
+ } else {
+ switch (button) {
+ case 0:
+ m_eventFilter ^= EventView::Note;
+ break;
+
+ case 1:
+ m_eventFilter ^= EventView::ProgramChange;
+ break;
+
+ case 2:
+ m_eventFilter ^= EventView::Controller;
+ break;
+
+ case 3:
+ m_eventFilter ^= EventView::PitchBend;
+ break;
+
+ case 4:
+ m_eventFilter ^= EventView::SystemExclusive;
+ break;
+
+ case 5:
+ m_eventFilter ^= EventView::KeyPressure;
+ break;
+
+ case 6:
+ m_eventFilter ^= EventView::ChannelPressure;
+ break;
+
+ case 7:
+ m_eventFilter ^= EventView::Rest;
+ break;
+
+ case 8:
+ m_eventFilter ^= EventView::Indication;
+ break;
+
+ case 9:
+ m_eventFilter ^= EventView::Text;
+ break;
+
+ case 10:
+ m_eventFilter ^= EventView::Other;
+ break;
+
+ default:
+ break;
+ }
+ }
+
+ m_lastSetEventFilter = m_eventFilter;
+
+ applyLayout(0);
+}
+
+void
+EventView::setButtonsToFilter()
+{
+ if (m_eventFilter & Note)
+ m_noteCheckBox->setChecked(true);
+ else
+ m_noteCheckBox->setChecked(false);
+
+ if (m_eventFilter & ProgramChange)
+ m_programCheckBox->setChecked(true);
+ else
+ m_programCheckBox->setChecked(false);
+
+ if (m_eventFilter & Controller)
+ m_controllerCheckBox->setChecked(true);
+ else
+ m_controllerCheckBox->setChecked(false);
+
+ if (m_eventFilter & SystemExclusive)
+ m_sysExCheckBox->setChecked(true);
+ else
+ m_sysExCheckBox->setChecked(false);
+
+ if (m_eventFilter & Text)
+ m_textCheckBox->setChecked(true);
+ else
+ m_textCheckBox->setChecked(false);
+
+ if (m_eventFilter & Rest)
+ m_restCheckBox->setChecked(true);
+ else
+ m_restCheckBox->setChecked(false);
+
+ if (m_eventFilter & PitchBend)
+ m_pitchBendCheckBox->setChecked(true);
+ else
+ m_pitchBendCheckBox->setChecked(false);
+
+ if (m_eventFilter & ChannelPressure)
+ m_channelPressureCheckBox->setChecked(true);
+ else
+ m_channelPressureCheckBox->setChecked(false);
+
+ if (m_eventFilter & KeyPressure)
+ m_keyPressureCheckBox->setChecked(true);
+ else
+ m_keyPressureCheckBox->setChecked(false);
+
+ if (m_eventFilter & Indication) {
+ m_indicationCheckBox->setChecked(true);
+ } else {
+ m_indicationCheckBox->setChecked(false);
+ }
+
+ if (m_eventFilter & Other) {
+ m_otherCheckBox->setChecked(true);
+ } else {
+ m_otherCheckBox->setChecked(false);
+ }
+}
+
+void
+EventView::slotMusicalTime()
+{
+ m_config->setGroup(EventViewConfigGroup);
+ m_config->writeEntry("timemode", 0);
+ applyLayout();
+}
+
+void
+EventView::slotRealTime()
+{
+ m_config->setGroup(EventViewConfigGroup);
+ m_config->writeEntry("timemode", 1);
+ applyLayout();
+}
+
+void
+EventView::slotRawTime()
+{
+ m_config->setGroup(EventViewConfigGroup);
+ m_config->writeEntry("timemode", 2);
+ applyLayout();
+}
+
+void
+EventView::slotPopupEventEditor(QListViewItem *item)
+{
+ EventViewItem *eItem = dynamic_cast<EventViewItem*>(item);
+
+ //!!! trigger events
+
+ if (eItem) {
+ Event *event = eItem->getEvent();
+ SimpleEventEditDialog *dialog =
+ new SimpleEventEditDialog(this, getDocument(), *event, false);
+
+ if (dialog->exec() == QDialog::Accepted && dialog->isModified()) {
+ EventEditCommand *command =
+ new EventEditCommand(*(eItem->getSegment()),
+ event,
+ dialog->getEvent());
+
+ addCommandToHistory(command);
+ }
+
+ }
+}
+
+void
+EventView::slotPopupMenu(QListViewItem *item, const QPoint &pos, int)
+{
+ if (!item)
+ return ;
+
+ EventViewItem *eItem = dynamic_cast<EventViewItem*>(item);
+ if (!eItem || !eItem->getEvent())
+ return ;
+
+ if (!m_menu)
+ createMenu();
+
+ if (m_menu)
+ //m_menu->exec(QCursor::pos());
+ m_menu->exec(pos);
+ else
+ RG_DEBUG << "EventView::showMenu() : no menu to show\n";
+}
+
+void
+EventView::createMenu()
+{
+ m_menu = new QPopupMenu(this);
+ m_menu->insertItem(i18n("Open in Event Editor"), 0);
+ m_menu->insertItem(i18n("Open in Expert Event Editor"), 1);
+
+ connect(m_menu, SIGNAL(activated(int)),
+ SLOT(slotMenuActivated(int)));
+}
+
+void
+EventView::slotMenuActivated(int value)
+{
+ RG_DEBUG << "EventView::slotMenuActivated - value = " << value << endl;
+
+ if (value == 0) {
+ EventViewItem *eItem = dynamic_cast<EventViewItem*>
+ (m_eventList->currentItem());
+
+ if (eItem) {
+ Event *event = eItem->getEvent();
+ SimpleEventEditDialog *dialog =
+ new SimpleEventEditDialog(this, getDocument(), *event, false);
+
+ if (dialog->exec() == QDialog::Accepted && dialog->isModified()) {
+ EventEditCommand *command =
+ new EventEditCommand(*(eItem->getSegment()),
+ event,
+ dialog->getEvent());
+
+ addCommandToHistory(command);
+ }
+
+ }
+ } else if (value == 1) {
+ EventViewItem *eItem = dynamic_cast<EventViewItem*>
+ (m_eventList->currentItem());
+
+ if (eItem) {
+ Event *event = eItem->getEvent();
+ EventEditDialog *dialog = new EventEditDialog(this, *event);
+
+ if (dialog->exec() == QDialog::Accepted && dialog->isModified()) {
+ EventEditCommand *command =
+ new EventEditCommand(*(eItem->getSegment()),
+ event,
+ dialog->getEvent());
+
+ addCommandToHistory(command);
+ }
+
+ }
+ }
+
+ return ;
+}
+
+void
+EventView::updateViewCaption()
+{
+ if (m_isTriggerSegment) {
+
+ setCaption(i18n("%1 - Triggered Segment: %2")
+ .arg(getDocument()->getTitle())
+ .arg(strtoqstr(m_segments[0]->getLabel())));
+
+
+ } else if (m_segments.size() == 1) {
+
+ TrackId trackId = m_segments[0]->getTrack();
+ Track *track =
+ m_segments[0]->getComposition()->getTrackById(trackId);
+
+ int trackPosition = -1;
+ if (track)
+ trackPosition = track->getPosition();
+
+ setCaption(i18n("%1 - Segment Track #%2 - Event List")
+ .arg(getDocument()->getTitle())
+ .arg(trackPosition + 1));
+
+ } else {
+
+ setCaption(i18n("%1 - %2 Segments - Event List")
+ .arg(getDocument()->getTitle())
+ .arg(m_segments.size()));
+ }
+
+}
+
+}
+#include "EventView.moc"
diff --git a/src/gui/editors/eventlist/EventView.h b/src/gui/editors/eventlist/EventView.h
new file mode 100644
index 0000000..4c540e6
--- /dev/null
+++ b/src/gui/editors/eventlist/EventView.h
@@ -0,0 +1,205 @@
+
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
+
+/*
+ Rosegarden
+ A MIDI and audio sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_EVENTVIEW_H_
+#define _RG_EVENTVIEW_H_
+
+#include "base/MidiTypes.h"
+#include "base/NotationTypes.h"
+#include "base/Segment.h"
+#include "gui/general/EditViewBase.h"
+#include <set>
+#include <qsize.h>
+#include <qstring.h>
+#include <vector>
+#include "base/Event.h"
+
+
+class QWidget;
+class QPopupMenu;
+class QPoint;
+class QListViewItem;
+class QLabel;
+class QCheckBox;
+class QButtonGroup;
+class KListView;
+
+
+namespace Rosegarden
+{
+
+class Segment;
+class RosegardenGUIDoc;
+class Event;
+
+
+class EventView : public EditViewBase, public SegmentObserver
+{
+ Q_OBJECT
+
+ // Event filters
+ //
+ enum EventFilter
+ {
+ None = 0x0000,
+ Note = 0x0001,
+ Rest = 0x0002,
+ Text = 0x0004,
+ SystemExclusive = 0x0008,
+ Controller = 0x0010,
+ ProgramChange = 0x0020,
+ PitchBend = 0x0040,
+ ChannelPressure = 0x0080,
+ KeyPressure = 0x0100,
+ Indication = 0x0200,
+ Other = 0x0400
+ };
+
+public:
+ EventView(RosegardenGUIDoc *doc,
+ std::vector<Segment *> segments,
+ QWidget *parent);
+
+ virtual ~EventView();
+
+ virtual bool applyLayout(int staffNo = -1);
+
+ virtual void refreshSegment(Segment *segment,
+ timeT startTime = 0,
+ timeT endTime = 0);
+
+ virtual void updateView();
+
+ virtual void setupActions();
+ virtual void initStatusBar();
+ virtual QSize getViewSize();
+ virtual void setViewSize(QSize);
+
+ // Set the button states to the current filter positions
+ //
+ void setButtonsToFilter();
+
+ // Menu creation and show
+ //
+ void createMenu();
+
+public slots:
+
+ // standard slots
+ virtual void slotEditCut();
+ virtual void slotEditCopy();
+ virtual void slotEditPaste();
+
+ // other edit slots
+ void slotEditDelete();
+ void slotEditInsert();
+ void slotEditEvent();
+ void slotEditEventAdvanced();
+
+ void slotFilterSelection();
+ void slotSelectAll();
+ void slotClearSelection();
+
+ void slotMusicalTime();
+ void slotRealTime();
+ void slotRawTime();
+
+ // Show RMB menu
+ //
+ void slotPopupMenu(QListViewItem*, const QPoint&, int);
+ void slotMenuActivated(int);
+
+ // on double click on the event list
+ //
+ void slotPopupEventEditor(QListViewItem*);
+
+ // Change filter parameters
+ //
+ void slotModifyFilter(int);
+
+ virtual void eventAdded(const Segment *, Event *) { }
+ virtual void eventRemoved(const Segment *, Event *);
+ virtual void endMarkerTimeChanged(const Segment *, bool) { }
+ virtual void segmentDeleted(const Segment *);
+
+signals:
+ void editTriggerSegment(int);
+
+protected slots:
+ virtual void slotSaveOptions();
+
+ void slotEditTriggerName();
+ void slotEditTriggerPitch();
+ void slotEditTriggerVelocity();
+ void slotTriggerTimeAdjustChanged(int);
+ void slotTriggerRetuneChanged();
+
+protected:
+
+ virtual void readOptions();
+ void makeInitialSelection(timeT);
+ QString makeTimeString(timeT time, int timeMode);
+ QString makeDurationString(timeT time,
+ timeT duration, int timeMode);
+ virtual Segment *getCurrentSegment();
+
+ virtual void updateViewCaption();
+
+ //--------------- Data members ---------------------------------
+
+ bool m_isTriggerSegment;
+ QLabel *m_triggerName;
+ QLabel *m_triggerPitch;
+ QLabel *m_triggerVelocity;
+
+ KListView *m_eventList;
+ int m_eventFilter;
+
+ static int m_lastSetEventFilter;
+
+ QButtonGroup *m_filterGroup;
+ QCheckBox *m_noteCheckBox;
+ QCheckBox *m_textCheckBox;
+ QCheckBox *m_sysExCheckBox;
+ QCheckBox *m_programCheckBox;
+ QCheckBox *m_controllerCheckBox;
+ QCheckBox *m_restCheckBox;
+ QCheckBox *m_pitchBendCheckBox;
+ QCheckBox *m_keyPressureCheckBox;
+ QCheckBox *m_channelPressureCheckBox;
+ QCheckBox *m_indicationCheckBox;
+ QCheckBox *m_otherCheckBox;
+
+ std::vector<int> m_listSelection;
+ std::set<Event *> m_deletedEvents; // deleted since last refresh
+
+ QPopupMenu *m_menu;
+
+};
+
+
+}
+
+#endif
diff --git a/src/gui/editors/eventlist/EventViewItem.cpp b/src/gui/editors/eventlist/EventViewItem.cpp
new file mode 100644
index 0000000..4435a2b
--- /dev/null
+++ b/src/gui/editors/eventlist/EventViewItem.cpp
@@ -0,0 +1,68 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
+
+/*
+ Rosegarden
+ A MIDI and audio sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#include "EventViewItem.h"
+#include "base/Event.h"
+
+namespace Rosegarden
+{
+
+// Reimplementation of sort for numeric columns - taking the
+// right hand argument from the left is equivalent to the
+// the QString compare().
+//
+int
+EventViewItem::compare(QListViewItem *i, int col, bool ascending) const
+{
+ EventViewItem *ei = dynamic_cast<EventViewItem *>(i);
+ if (!ei) return QListViewItem::compare(i, col, ascending);
+
+ if (col == 0) { // time
+ Rosegarden::Event &e1 = *m_event;
+ Rosegarden::Event &e2 = *ei->m_event;
+ if (e2 < e1) return 1;
+ else if (e1 < e2) return -1;
+ else return 0;
+ } else if (col == 2 || col == 5 || col == 6) { // event type, data1, data2
+ // we have to do string compares even for data1/data2 which are
+ // often numeric, just because they aren't _always_ numeric and
+ // we don't want to prevent the user being able to separate
+ // e.g. crescendo from decrescendo
+ if (key(col, ascending).compare(i->key(col, ascending)) == 0) {
+ return compare(i, 0, ascending);
+ } else {
+ return key(col, ascending).compare(i->key(col, ascending));
+ }
+ } else if (col == 3) { // pitch
+ // numeric comparison for pitch used to work when we only
+ // showed the numeric pitch number, but then we added the MIDI
+ // pitch name as well and that broke plain numeric comparison
+ return key(col, ascending).section(' ', 0, 0).toInt() -
+ i->key(col, ascending).section(' ', 0, 0).toInt();
+ } else { // numeric comparison
+ return key(col, ascending).toInt() - i->key(col, ascending).toInt();
+ }
+}
+
+}
diff --git a/src/gui/editors/eventlist/EventViewItem.h b/src/gui/editors/eventlist/EventViewItem.h
new file mode 100644
index 0000000..832e652
--- /dev/null
+++ b/src/gui/editors/eventlist/EventViewItem.h
@@ -0,0 +1,101 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
+
+/*
+ Rosegarden
+ A MIDI and audio sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_EVENTVIEWITEM_H_
+#define _RG_EVENTVIEWITEM_H_
+
+#include <klistview.h>
+
+namespace Rosegarden
+{
+
+class Segment;
+class Event;
+
+// EventView specialisation of a QListViewItem with the
+// addition of a segment pointer
+//
+class EventViewItem : public KListViewItem
+{
+public:
+ EventViewItem(Rosegarden::Segment *segment,
+ Rosegarden::Event *event,
+ KListView *parent) :
+ KListViewItem(parent),
+ m_segment(segment),
+ m_event(event) {;}
+
+ EventViewItem(Rosegarden::Segment *segment,
+ Rosegarden::Event *event,
+ KListViewItem *parent) :
+ KListViewItem(parent),
+ m_segment(segment),
+ m_event(event) {;}
+
+ EventViewItem(Rosegarden::Segment *segment,
+ Rosegarden::Event *event,
+ QListView *parent, QString label1,
+ QString label2 = QString::null,
+ QString label3 = QString::null,
+ QString label4 = QString::null,
+ QString label5 = QString::null,
+ QString label6 = QString::null,
+ QString label7 = QString::null,
+ QString label8 = QString::null) :
+ KListViewItem(parent, label1, label2, label3, label4,
+ label5, label6, label7, label8),
+ m_segment(segment),
+ m_event(event) {;}
+
+ EventViewItem(Rosegarden::Segment *segment,
+ Rosegarden::Event *event,
+ KListViewItem *parent, QString label1,
+ QString label2 = QString::null,
+ QString label3 = QString::null,
+ QString label4 = QString::null,
+ QString label5 = QString::null,
+ QString label6 = QString::null,
+ QString label7 = QString::null,
+ QString label8 = QString::null) :
+ KListViewItem(parent, label1, label2, label3, label4,
+ label5, label6, label7, label8),
+ m_segment(segment),
+ m_event(event) {;}
+
+ Rosegarden::Segment* getSegment() { return m_segment; }
+ Rosegarden::Event* getEvent() { return m_event; }
+
+ // Reimplement so that we can sort numerically
+ //
+ virtual int compare(QListViewItem *i, int col, bool ascending) const;
+
+protected:
+
+ Rosegarden::Segment *m_segment;
+ Rosegarden::Event *m_event;
+};
+
+}
+
+#endif /*EVENTVIEWITEM_H_*/
diff --git a/src/gui/editors/eventlist/TrivialVelocityDialog.cpp b/src/gui/editors/eventlist/TrivialVelocityDialog.cpp
new file mode 100644
index 0000000..4e609d4
--- /dev/null
+++ b/src/gui/editors/eventlist/TrivialVelocityDialog.cpp
@@ -0,0 +1,48 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
+
+/*
+ Rosegarden
+ A MIDI and audio sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#include "TrivialVelocityDialog.h"
+
+#include <qspinbox.h>
+#include <qlabel.h>
+#include <qhbox.h>
+
+namespace Rosegarden {
+
+TrivialVelocityDialog::TrivialVelocityDialog(QWidget *parent, QString label, int deft) :
+ KDialogBase(parent, 0, true, label, Ok | Cancel)
+ {
+ QHBox *hbox = makeHBoxMainWidget();
+ new QLabel(label, hbox);
+ m_spin = new QSpinBox(0, 127, 1, hbox);
+ m_spin->setValue(deft);
+ }
+
+int
+TrivialVelocityDialog::getVelocity()
+{
+ return m_spin->value();
+}
+
+}
diff --git a/src/gui/editors/eventlist/TrivialVelocityDialog.h b/src/gui/editors/eventlist/TrivialVelocityDialog.h
new file mode 100644
index 0000000..ca19de9
--- /dev/null
+++ b/src/gui/editors/eventlist/TrivialVelocityDialog.h
@@ -0,0 +1,48 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
+
+/*
+ Rosegarden
+ A MIDI and audio sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_TRIVIALVELOCITYDIALOG_H_
+#define _RG_TRIVIALVELOCITYDIALOG_H_
+
+#include <kdialogbase.h>
+
+class QHBox;
+class QSpinBox;
+
+namespace Rosegarden {
+
+class TrivialVelocityDialog : public KDialogBase
+{
+public:
+ TrivialVelocityDialog(QWidget *parent, QString label, int deft);
+
+ int getVelocity();
+
+protected:
+ QSpinBox *m_spin;
+};
+
+}
+
+#endif /*TRIVIALVELOCITYDIALOG_H_*/
diff --git a/src/gui/editors/guitar/Chord.cpp b/src/gui/editors/guitar/Chord.cpp
new file mode 100644
index 0000000..23efe7d
--- /dev/null
+++ b/src/gui/editors/guitar/Chord.cpp
@@ -0,0 +1,113 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
+
+/*
+ Rosegarden
+ A MIDI and audio sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#include "Chord.h"
+#include "base/Event.h"
+
+#include <qstring.h>
+
+namespace Rosegarden
+{
+
+namespace Guitar
+{
+const std::string Chord::EventType = "guitarchord";
+const short Chord::EventSubOrdering = -60;
+const PropertyName Chord::RootPropertyName = "root";
+const PropertyName Chord::ExtPropertyName = "ext";
+const PropertyName Chord::FingeringPropertyName = "fingering";
+
+
+Chord::Chord()
+ : m_isUserChord(false)
+{
+}
+
+Chord::Chord(const QString& root, const QString& ext)
+ : m_root(root),
+ m_ext(ext),
+ m_isUserChord(false)
+{
+ if (m_ext.isEmpty())
+ m_ext = QString::null;
+}
+
+Chord::Chord(const Event& e)
+ : m_isUserChord(false)
+{
+ std::string f;
+ bool ok;
+
+ ok = e.get<String>(RootPropertyName, f);
+ if (ok)
+ m_root = f;
+
+ ok = e.get<String>(ExtPropertyName, f);
+ if (ok) {
+ if (f.length() == 0)
+ m_ext = QString::null;
+ else
+ m_ext = f;
+ }
+
+ ok = e.get<String>(FingeringPropertyName, f);
+ if (ok) {
+ QString qf(f);
+ QString errString;
+
+ Fingering fingering = Fingering::parseFingering(qf, errString);
+ setFingering(fingering);
+ }
+}
+
+Event* Chord::getAsEvent(timeT absoluteTime) const
+{
+ Event *e = new Event(EventType, absoluteTime, 0, EventSubOrdering);
+ e->set<String>(RootPropertyName, m_root);
+ e->set<String>(ExtPropertyName, m_ext);
+ e->set<String>(FingeringPropertyName, getFingering().toString());
+ return e;
+}
+
+const QRegExp Chord::ALT_BASS_REGEXP("/[A-G]");
+
+bool operator<(const Chord& a, const Chord& b)
+{
+ if (a.m_root != b.m_root) {
+ return a.m_root < b.m_root;
+ } else if (a.m_ext != b.m_ext) {
+ if (a.m_ext.isEmpty()) // chords with no ext need to be stored first
+ return true;
+ if (b.m_ext.isEmpty())
+ return false;
+ return a.m_ext < b.m_ext;
+ } else {
+ return a.m_fingering < b.m_fingering;
+ }
+
+}
+
+}
+
+}
diff --git a/src/gui/editors/guitar/Chord.h b/src/gui/editors/guitar/Chord.h
new file mode 100644
index 0000000..9e84cc3
--- /dev/null
+++ b/src/gui/editors/guitar/Chord.h
@@ -0,0 +1,106 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
+
+/*
+ Rosegarden
+ A MIDI and audio sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_CHORD_H_
+#define _RG_CHORD_H_
+
+#include "Fingering.h"
+#include "base/Event.h"
+#include "misc/Debug.h"
+
+#include <vector>
+#include <qstring.h>
+#include <qregexp.h>
+
+namespace Rosegarden
+{
+
+class Event;
+
+namespace Guitar
+{
+
+class Chord
+{
+ friend bool operator<(const Chord&, const Chord&);
+
+public:
+ static const std::string EventType;
+ static const short EventSubOrdering;
+ static const PropertyName RootPropertyName;
+ static const PropertyName ExtPropertyName;
+ static const PropertyName FingeringPropertyName;
+
+ Chord();
+ Chord(const QString& root, const QString& ext = QString::null);
+ Chord(const Event&);
+
+ Event* getAsEvent(timeT absoluteTime) const;
+
+ bool isEmpty() const { return m_root.isEmpty(); }
+ bool operator!() const { return !m_root; }
+
+ bool isUserChord() const { return m_isUserChord; }
+ void setUserChord(bool c) { m_isUserChord = c; }
+
+ QString getRoot() const { return m_root; }
+ void setRoot(QString r) { m_root = r; }
+
+ QString getExt() const { return m_ext; }
+ void setExt(QString r) { m_ext = r.isEmpty() ? QString::null : r; }
+
+ bool hasAltBass() const { return m_ext.contains(ALT_BASS_REGEXP); }
+
+ Fingering getFingering() const { return m_fingering; }
+ void setFingering(Fingering f) { m_fingering = f; }
+
+ struct ChordCmp
+ {
+ bool operator()(const Chord &e1, const Chord &e2) const {
+ return e1 < e2;
+ }
+ bool operator()(const Chord *e1, const Chord *e2) const {
+ return *e1 < *e2;
+ }
+ };
+
+protected:
+
+ static const QRegExp ALT_BASS_REGEXP;
+
+ QString m_root;
+ QString m_ext;
+
+ Fingering m_fingering;
+
+ bool m_isUserChord;
+};
+
+bool operator<(const Chord&, const Chord&);
+
+}
+
+}
+
+#endif /*_RG_CHORD2_H_*/
diff --git a/src/gui/editors/guitar/ChordMap.cpp b/src/gui/editors/guitar/ChordMap.cpp
new file mode 100644
index 0000000..06662d9
--- /dev/null
+++ b/src/gui/editors/guitar/ChordMap.cpp
@@ -0,0 +1,223 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
+
+/*
+ Rosegarden
+ A MIDI and audio sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#include "misc/Debug.h"
+#include "ChordMap.h"
+
+#include <qfile.h>
+#include <qtextstream.h>
+
+namespace Rosegarden
+{
+
+namespace Guitar
+{
+
+ChordMap::ChordMap()
+ : m_needSave(false)
+{
+}
+
+void ChordMap::insert(const Chord& c)
+{
+ m_map.insert(c);
+ m_needSave = true;
+}
+
+
+ChordMap::chordarray
+ChordMap::getChords(const QString& root, const QString& ext) const
+{
+ chordarray res;
+
+ Chord tmp(root, ext);
+ NOTATION_DEBUG << "ChordMap::getChords : chord = " << tmp << " - ext is empty : " << ext.isEmpty() << endl;
+
+ for (chordset::const_iterator i = m_map.lower_bound(tmp); i != m_map.end(); ++i) {
+ NOTATION_DEBUG << "ChordMap::getChords : checking chord " << *i << endl;
+
+ if (i->getRoot() != root)
+ break;
+
+ if (/* ext.isNull() || */ i->getExt() == ext) {
+ NOTATION_DEBUG << "ChordMap::getChords : adding chord " << *i << endl;
+ res.push_back(*i);
+ } else {
+ break;
+ }
+ }
+
+ return res;
+}
+
+QStringList
+ChordMap::getRootList() const
+{
+ static QStringList rootNotes;
+
+ if (rootNotes.count() == 0) {
+ rootNotes = QStringList::split(QString(","), "A,A#/Bb,B,C,C#/Db,D,D#/Eb,E,F,F#/Gb,G,G#/Ab");
+ }
+
+ // extract roots from map itself - not a very good idea
+ //
+// QString currentRoot;
+//
+// for(chordset::const_iterator i = m_map.begin(); i != m_map.end(); ++i) {
+// const Chord& chord = *i;
+// if (chord.getRoot() != currentRoot) {
+// rootNotes.push_back(chord.getRoot());
+// currentRoot = chord.getRoot();
+// }
+// }
+
+ return rootNotes;
+}
+
+QStringList
+ChordMap::getExtList(const QString& root) const
+{
+ QStringList extList;
+ QString currentExt = "ZZ";
+
+ Chord tmp(root);
+
+ for(chordset::const_iterator i = m_map.lower_bound(tmp); i != m_map.end(); ++i) {
+ const Chord& chord = *i;
+// NOTATION_DEBUG << "ChordMap::getExtList : chord = " << chord << endl;
+
+ if (chord.getRoot() != root)
+ break;
+
+ if (chord.getExt() != currentExt) {
+// NOTATION_DEBUG << "ChordMap::getExtList : adding ext " << chord.getExt() << " for root " << root << endl;
+ extList.push_back(chord.getExt());
+ currentExt = chord.getExt();
+ }
+ }
+
+ return extList;
+}
+
+void
+ChordMap::substitute(const Chord& oldChord, const Chord& newChord)
+{
+ remove(oldChord);
+ insert(newChord);
+}
+
+void
+ChordMap::remove(const Chord& c)
+{
+ m_map.erase(c);
+ m_needSave = true;
+}
+
+bool ChordMap::saveDocument(const QString& filename, bool userChordsOnly, QString& errMsg)
+{
+ QFile file(filename);
+ file.open(IO_WriteOnly);
+
+ QTextStream outStream(&file);
+
+ outStream.setEncoding(QTextStream::UnicodeUTF8);
+
+ outStream << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
+ << "<!DOCTYPE rosegarden-chord-data>\n"
+ << "<rosegarden-chord-data version=\"" << VERSION
+ << "\" format-version-major=\"" << FILE_FORMAT_VERSION_MAJOR
+ << "\" format-version-minor=\"" << FILE_FORMAT_VERSION_MINOR
+ << "\" format-version-point=\"" << FILE_FORMAT_VERSION_POINT
+ << "\">\n";
+
+ outStream << "<chords>\n";
+
+ QString currentExt, currentRoot;
+
+ for(iterator i = begin(); i != end(); ++i) {
+ const Chord& chord = *i;
+
+ if (userChordsOnly && !chord.isUserChord())
+ continue; // skip non-user chords
+
+ if (chord.getRoot() != currentRoot) {
+
+ currentRoot = chord.getRoot();
+
+ // close current chordset (if there was one)
+ if (i != begin())
+ outStream << "\n</chordset>\n";
+
+ // open new chordset
+ outStream << "<chordset root=\"" << chord.getRoot() << "\">\n";
+ currentExt = "NEWEXT"; // to make sure we open a new chord right after that
+ }
+
+ if (chord.getExt() != currentExt) {
+
+ currentExt = chord.getExt();
+
+ // close current chord (if there was one)
+ if (i != begin())
+ outStream << "</chord>\n";
+
+ // open new chord
+ outStream << "<chord";
+ if (!chord.getExt().isEmpty())
+ outStream << " ext=\"" << chord.getExt() << "\"";
+ if (chord.isUserChord())
+ outStream << " user=\"true\"";
+
+ outStream << ">\n";
+ }
+
+ outStream << "<fingering>" << chord.getFingering().toString() << "</fingering>\n";
+ }
+
+ if (!m_map.empty())
+ outStream << "</chord>\n"; // close last written chord
+
+ outStream << "</chords>\n";
+ outStream << "</rosegarden-chord-data>\n";
+
+ return outStream.device()->status() == IO_Ok;
+}
+
+int ChordMap::FILE_FORMAT_VERSION_MAJOR = 1;
+int ChordMap::FILE_FORMAT_VERSION_MINOR = 0;
+int ChordMap::FILE_FORMAT_VERSION_POINT = 0;
+
+
+void
+ChordMap::debugDump() const
+{
+ for(chordset::const_iterator i = m_map.begin(); i != m_map.end(); ++i) {
+ Chord chord = *i;
+ NOTATION_DEBUG << "ChordMap::debugDump " << chord << endl;
+ }
+}
+
+}
+
+}
diff --git a/src/gui/editors/guitar/ChordMap.h b/src/gui/editors/guitar/ChordMap.h
new file mode 100644
index 0000000..5b7488d
--- /dev/null
+++ b/src/gui/editors/guitar/ChordMap.h
@@ -0,0 +1,87 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
+
+/*
+ Rosegarden
+ A MIDI and audio sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_CHORDMAP_H_
+#define _RG_CHORDMAP_H_
+
+#include "Chord.h"
+
+#include <qstringlist.h>
+#include <set>
+
+namespace Rosegarden
+{
+
+namespace Guitar
+{
+
+class ChordMap
+{
+ typedef std::set<Chord, Chord::ChordCmp> chordset;
+
+public:
+ typedef std::vector<Chord> chordarray;
+
+ typedef chordset::iterator iterator;
+ typedef chordset::const_iterator const_iterator;
+
+ static int FILE_FORMAT_VERSION_MAJOR;
+ static int FILE_FORMAT_VERSION_MINOR;
+ static int FILE_FORMAT_VERSION_POINT;
+
+ ChordMap();
+
+ void insert(const Chord&);
+ void substitute(const Chord& oldChord, const Chord& newChord);
+ void remove(const Chord&);
+
+ chordarray getChords(const QString& root, const QString& ext) const;
+
+ QStringList getRootList() const;
+ QStringList getExtList(const QString& root) const;
+
+ void debugDump() const;
+
+ bool needSave() const { return m_needSave; }
+ void clearNeedSave() { m_needSave = false; }
+
+ bool saveDocument(const QString& filename, bool userChordsOnly, QString& errMsg);
+
+ iterator begin() { return m_map.begin(); }
+ iterator end() { return m_map.end(); }
+ const_iterator begin() const { return m_map.begin(); }
+ const_iterator end() const { return m_map.end(); }
+
+protected:
+
+ chordset m_map;
+
+ bool m_needSave;
+};
+
+}
+
+}
+
+#endif /*_RG_CHORDMAP2_H_*/
diff --git a/src/gui/editors/guitar/ChordXmlHandler.cpp b/src/gui/editors/guitar/ChordXmlHandler.cpp
new file mode 100644
index 0000000..701c43c
--- /dev/null
+++ b/src/gui/editors/guitar/ChordXmlHandler.cpp
@@ -0,0 +1,154 @@
+// -*- c-basic-offset: 4 -*-
+
+/*
+ Rosegarden
+ A sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <bownie@bownie.com>
+
+ 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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#ifndef _RG_CHORDXMLHANDLER_H_
+#define _RG_CHORDXMLHANDLER_H_
+
+#include "gui/general/ProgressReporter.h"
+#include "Chord.h"
+#include "ChordMap.h"
+
+#include <qxml.h>
+
+
+namespace Rosegarden
+{
+
+class ChordXmlHandler : public ProgressReporter, public QXmlDefaultHandler
+{
+public:
+ ChordXmlHandler(Guitar::ChordMap&);
+ virtual ~ChordXmlHandler();
+
+ /// overloaded handler functions
+ virtual bool startDocument();
+ virtual bool startElement(const QString& namespaceURI,
+ const QString& localName,
+ const QString& qName,
+ const QXmlAttributes& atts);
+
+ virtual bool endElement(const QString& namespaceURI,
+ const QString& localName,
+ const QString& qName);
+
+ virtual bool characters(const QString& ch);
+
+ virtual bool endDocument ();
+
+ /// Return the error string set during the parsing (if any)
+ QString errorString() { return m_errorString; }
+ bool error(const QXmlParseException& exception);
+ bool fatalError(const QXmlParseException& exception);
+
+protected:
+
+ bool parseFingering(const QString& ch);
+
+ Guitar::Chord m_currentChord;
+ QString m_currentRoot;
+ QString m_errorString;
+ bool m_inFingering;
+ Guitar::ChordMap& m_chordMap;
+};
+
+}
+
+#endif /*_RG_CHORDXMLHANDLER_H_*/
diff --git a/src/gui/editors/guitar/Fingering.cpp b/src/gui/editors/guitar/Fingering.cpp
new file mode 100644
index 0000000..dd1edbd
--- /dev/null
+++ b/src/gui/editors/guitar/Fingering.cpp
@@ -0,0 +1,152 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
+
+/*
+ Rosegarden
+ A MIDI and audio sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#include "Fingering.h"
+
+#include "misc/Debug.h"
+
+#include <qstringlist.h>
+#include <sstream>
+#include <algorithm>
+#include <klocale.h>
+
+namespace Rosegarden
+{
+
+namespace Guitar
+{
+
+Fingering::Fingering(unsigned int nbStrings) :
+ m_strings(nbStrings, MUTED)
+{
+}
+
+Fingering::Fingering(QString s)
+{
+ QString errString;
+ Fingering t = parseFingering(s, errString);
+ m_strings = t.m_strings;
+}
+
+unsigned int
+Fingering::getStartFret() const
+{
+ int min = 999, max = 0;
+ for(std::vector<int>::const_iterator i = m_strings.begin(); i != m_strings.end(); ++i) {
+ if (*i < min && *i > 0)
+ min = *i;
+ if (*i > max)
+ max = *i;
+ }
+
+ if (max < 4)
+ min = 1;
+
+ return min == 999 ? 1 : min;
+}
+
+bool
+Fingering::hasBarre() const
+{
+ int lastStringStatus = m_strings[getNbStrings() - 1];
+
+ return ((m_strings[0] > OPEN && m_strings[0] == lastStringStatus) ||
+ (m_strings[1] > OPEN && m_strings[1] == lastStringStatus) ||
+ (m_strings[2] > OPEN && m_strings[2] == lastStringStatus));
+}
+
+Fingering::Barre
+Fingering::getBarre() const
+{
+ int lastStringStatus = m_strings[getNbStrings() - 1];
+
+ Barre res;
+
+ res.fret = lastStringStatus;
+
+ for(unsigned int i = 0; i < 3; ++i) {
+ if (m_strings[i] > OPEN && m_strings[i] == lastStringStatus)
+ res.start = i;
+ break;
+ }
+
+ res.end = 5;
+
+ return res;
+}
+
+Fingering
+Fingering::parseFingering(const QString& ch, QString& errorString)
+{
+ QStringList tokens = QStringList::split(' ', ch);
+
+ unsigned int idx = 0;
+ Fingering fingering;
+
+ for(QStringList::iterator i = tokens.begin(); i != tokens.end() && idx < fingering.getNbStrings(); ++i, ++idx) {
+ QString t = *i;
+ bool b = false;
+ unsigned int fn = t.toUInt(&b);
+ if (b) {
+// NOTATION_DEBUG << "Fingering::parseFingering : '" << t << "' = " << fn << endl;
+ fingering[idx] = fn;
+ } else if (t.lower() == "x") {
+// NOTATION_DEBUG << "Fingering::parseFingering : '" << t << "' = MUTED\n";
+ fingering[idx] = MUTED;
+ } else {
+ errorString = i18n("couldn't parse fingering '%1' in '%2'").arg(t).arg(ch);
+ }
+ }
+
+ return fingering;
+}
+
+
+std::string Fingering::toString() const
+{
+ std::stringstream s;
+
+ for(std::vector<int>::const_iterator i = m_strings.begin(); i != m_strings.end(); ++i) {
+ if (*i >= 0)
+ s << *i << ' ';
+ else
+ s << "x ";
+ }
+
+ return s.str();
+}
+
+bool operator<(const Fingering& a, const Fingering& b)
+{
+ for(unsigned int i = 0; i < Fingering::DEFAULT_NB_STRINGS; ++i) {
+ if (a.getStringStatus(i) != b.getStringStatus(i)) {
+ return a.getStringStatus(i) < b.getStringStatus(i);
+ }
+ }
+ return false;
+}
+
+}
+
+}
diff --git a/src/gui/editors/guitar/Fingering.h b/src/gui/editors/guitar/Fingering.h
new file mode 100644
index 0000000..41d9799
--- /dev/null
+++ b/src/gui/editors/guitar/Fingering.h
@@ -0,0 +1,95 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
+
+/*
+ Rosegarden
+ A MIDI and audio sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_FINGERING_H_
+#define _RG_FINGERING_H_
+
+#include <vector>
+#include <qstring.h>
+#include "base/Event.h"
+
+namespace Rosegarden
+{
+
+namespace Guitar
+{
+
+class Fingering
+{
+public:
+ friend bool operator<(const Fingering&, const Fingering&);
+
+ typedef std::vector<int>::iterator iterator;
+ typedef std::vector<int>::const_iterator const_iterator;
+
+ struct Barre {
+ unsigned int fret;
+ unsigned int start;
+ unsigned int end;
+ };
+
+ static const unsigned int DEFAULT_NB_STRINGS = 6;
+
+ Fingering(unsigned int nbStrings = DEFAULT_NB_STRINGS);
+ Fingering(QString);
+
+ enum { MUTED = -1, OPEN = 0 };
+
+ /**
+ * returns the fret number on which the string is pressed, or one of MUTED and OPEN
+ *
+ */
+ int getStringStatus(int stringNb) const { return m_strings[stringNb]; }
+ void setStringStatus(int stringNb, int status) { m_strings[stringNb] = status; }
+ unsigned int getStartFret() const;
+ unsigned int getNbStrings() const { return m_strings.size(); }
+
+ bool hasBarre() const;
+ Barre getBarre() const;
+
+ int operator[](int i) const { return m_strings[i]; }
+ int& operator[](int i) { return m_strings[i]; }
+
+ bool operator==(const Fingering& o) const { return m_strings == o.m_strings; }
+
+ iterator begin() { return m_strings.begin(); }
+ iterator end() { return m_strings.end(); }
+ const_iterator begin() const { return m_strings.begin(); }
+ const_iterator end() const { return m_strings.end(); }
+
+ static Fingering parseFingering(const QString&, QString& errorString);
+ std::string toString() const;
+
+protected:
+
+ std::vector<int> m_strings;
+};
+
+bool operator<(const Fingering&, const Fingering&);
+
+}
+
+}
+
+#endif /*_RG_FINGERING2_H_*/
diff --git a/src/gui/editors/guitar/FingeringBox.cpp b/src/gui/editors/guitar/FingeringBox.cpp
new file mode 100644
index 0000000..885ba83
--- /dev/null
+++ b/src/gui/editors/guitar/FingeringBox.cpp
@@ -0,0 +1,293 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
+
+/*
+ Rosegarden
+ A MIDI and audio sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+
+#ifndef _RG_FINGERINGBOX_H_
+#define _RG_FINGERINGBOX_H_
+
+#include <qframe.h>
+
+#include "NoteSymbols.h"
+#include "Fingering.h"
+
+namespace Rosegarden
+{
+
+class Fingering;
+
+class FingeringBox : public QFrame
+{
+ static const unsigned int IMG_WIDTH = 200;
+ static const unsigned int IMG_HEIGHT = 200;
+
+public:
+ FingeringBox(unsigned int nbFrets, unsigned int nbStrings, bool editable, QWidget *parent, const char* name = 0);
+ FingeringBox(bool editable, QWidget *parent, const char* name = 0);
+
+ void setStartFret(unsigned int f) { m_startFret = f; update(); }
+ unsigned int getStartFret() const { return m_startFret; }
+
+ void setFingering(const Guitar::Fingering&);
+ const Guitar::Fingering& getFingering() { return m_fingering; }
+
+ const Guitar::NoteSymbols& getNoteSymbols() const { return m_noteSymbols; }
+
+ static const unsigned int DEFAULT_NB_DISPLAYED_FRETS = 4;
+
+protected:
+ void init();
+
+ virtual void drawContents(QPainter*);
+
+ virtual void mousePressEvent(QMouseEvent*);
+ virtual void mouseReleaseEvent(QMouseEvent*);
+ virtual void mouseMoveEvent(QMouseEvent*);
+ virtual void leaveEvent(QEvent*);
+
+ void processMouseRelease( unsigned int release_string_num, unsigned int release_fret_num);
+
+ typedef std::pair<bool, unsigned int> PositionPair;
+
+ unsigned int getStringNumber(const QPoint&);
+
+ unsigned int getFretNumber(const QPoint&);
+
+ //! Maximum number of frets displayed by FingeringBox
+ unsigned int m_nbFretsDisplayed;
+
+ unsigned int m_startFret;
+
+ unsigned int m_nbStrings;
+
+ unsigned int m_transientFretNb;
+ unsigned int m_transientStringNb;
+
+ //! Present mode
+ bool m_editable;
+
+ //! Handle to the present fingering
+ Guitar::Fingering m_fingering;
+
+ //! String number where a mouse press event was located
+ unsigned int m_press_string_num;
+
+ //! Fret number where a mouse press event was located
+ unsigned int m_press_fret_num;
+
+ Guitar::NoteSymbols m_noteSymbols;
+
+ QRect m_r1, m_r2;
+};
+
+}
+
+#endif /*_RG_FINGERINGBOX2_H_*/
diff --git a/src/gui/editors/guitar/FingeringListBoxItem.cpp b/src/gui/editors/guitar/FingeringListBoxItem.cpp
new file mode 100644
index 0000000..31b92e9
--- /dev/null
+++ b/src/gui/editors/guitar/FingeringListBoxItem.cpp
@@ -0,0 +1,36 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
+
+/*
+ Rosegarden
+ A MIDI and audio sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#ifndef _RG_FINGERINGLISTBOXITEM_H_
+#define _RG_FINGERINGLISTBOXITEM_H_
+
+#include <qlistbox.h>
+#include "Chord.h"
+
+namespace Rosegarden {
+
+class FingeringListBoxItem : public QListBoxPixmap
+{
+public:
+ FingeringListBoxItem(const Guitar::Chord& chord, QListBox* parent, QPixmap pixmap, QString fingeringString);
+
+ const Guitar::Chord& getChord() { return m_chord; }
+protected:
+ Guitar::Chord m_chord;
+};
+
+}
+
+#endif /*_RG_FINGERINGLISTBOXITEM_H_*/
diff --git a/src/gui/editors/guitar/GuitarChordEditorDialog.cpp b/src/gui/editors/guitar/GuitarChordEditorDialog.cpp
new file mode 100644
index 0000000..60da8b6
--- /dev/null
+++ b/src/gui/editors/guitar/GuitarChordEditorDialog.cpp
@@ -0,0 +1,109 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
+
+/*
+ Rosegarden
+ A MIDI and audio sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#include "GuitarChordEditorDialog.h"
+#include "FingeringBox.h"
+#include "Chord.h"
+#include "ChordMap.h"
+
+#include <klineedit.h>
+#include <qcombobox.h>
+#include <qspinbox.h>
+#include <klocale.h>
+#include <kmessagebox.h>
+#include <kstddirs.h>
+#include <qlayout.h>
+#include <qlabel.h>
+
+namespace Rosegarden
+{
+
+GuitarChordEditorDialog::GuitarChordEditorDialog(Guitar::Chord& chord, const Guitar::ChordMap& chordMap, QWidget *parent)
+ : KDialogBase(parent, "GuitarChordEditor", true, i18n("Guitar Chord Editor"), Ok|Cancel),
+ m_chord(chord),
+ m_chordMap(chordMap)
+{
+ QWidget *page = new QWidget(this);
+ setMainWidget(page);
+ QGridLayout *topLayout = new QGridLayout(page, 7, 2, spacingHint());
+
+ topLayout->addWidget(new QLabel(i18n("Start fret"), page), 0, 1);
+ m_startFret = new QSpinBox(1, 24, 1, page);
+ topLayout->addWidget(m_startFret, 1, 1);
+
+ connect(m_startFret, SIGNAL(valueChanged(int)),
+ this, SLOT(slotStartFretChanged(int)));
+
+ topLayout->addWidget(new QLabel(i18n("Root"), page), 2, 1);
+ m_rootNotesList = new QComboBox(page);
+ topLayout->addWidget(m_rootNotesList, 3, 1);
+
+ topLayout->addWidget(new QLabel(i18n("Extension"), page), 4, 1);
+ m_ext = new QComboBox(true, page);
+ topLayout->addWidget(m_ext, 5, 1);
+
+ topLayout->addItem(new QSpacerItem(1, 1), 6, 1);
+
+ m_fingeringBox = new FingeringBox(true, page);
+ m_fingeringBox->setFingering(m_chord.getFingering());
+ topLayout->addMultiCellWidget(m_fingeringBox, 0, 7, 0, 0);
+
+ NOTATION_DEBUG << "GuitarChordEditorDialog : chord = " << m_chord << endl;
+
+
+ QStringList rootList = m_chordMap.getRootList();
+ if (rootList.count() > 0) {
+ m_rootNotesList->insertStringList(rootList);
+ m_rootNotesList->setCurrentItem(rootList.findIndex(m_chord.getRoot()));
+ }
+
+ QStringList extList = m_chordMap.getExtList(m_chord.getRoot());
+ if (extList.count() > 0) {
+ m_ext->insertStringList(extList);
+ m_ext->setCurrentItem(extList.findIndex(m_chord.getExt()));
+ }
+
+}
+
+void
+GuitarChordEditorDialog::slotStartFretChanged(int startFret)
+{
+ m_fingeringBox->setStartFret(startFret);
+}
+
+void
+GuitarChordEditorDialog::slotOk()
+{
+ m_chord.setFingering(m_fingeringBox->getFingering());
+ m_chord.setExt(m_ext->currentText());
+ m_chord.setRoot(m_rootNotesList->currentText());
+ m_chord.setUserChord(true);
+ KDialogBase::slotOk();
+}
+
+
+}
+
+#include "GuitarChordEditorDialog.moc"
+
diff --git a/src/gui/editors/guitar/GuitarChordEditorDialog.h b/src/gui/editors/guitar/GuitarChordEditorDialog.h
new file mode 100644
index 0000000..fc01605
--- /dev/null
+++ b/src/gui/editors/guitar/GuitarChordEditorDialog.h
@@ -0,0 +1,67 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
+
+/*
+ Rosegarden
+ A MIDI and audio sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_GUITARCHORDEDITOR2_H_
+#define _RG_GUITARCHORDEDITOR2_H_
+
+#include <kdialogbase.h>
+
+#include "Chord.h"
+#include "ChordMap.h"
+
+class QComboBox;
+class QSpinBox;
+
+namespace Rosegarden
+{
+
+class FingeringBox;
+
+
+class GuitarChordEditorDialog : public KDialogBase
+{
+ Q_OBJECT
+
+public:
+ GuitarChordEditorDialog(Guitar::Chord&, const Guitar::ChordMap& chordMap, QWidget *parent=0);
+
+protected slots:
+ void slotStartFretChanged(int);
+ virtual void slotOk();
+
+protected:
+
+ void populateExtensions(const QStringList&);
+
+ FingeringBox* m_fingeringBox;
+ QComboBox* m_rootNotesList;
+ QSpinBox* m_startFret;
+ QComboBox* m_ext;
+ Guitar::Chord& m_chord;
+ const Guitar::ChordMap& m_chordMap;
+};
+
+}
+
+#endif /*_RG_GUITARCHORDEDITOR2_H_*/
diff --git a/src/gui/editors/guitar/GuitarChordSelectorDialog.cpp b/src/gui/editors/guitar/GuitarChordSelectorDialog.cpp
new file mode 100644
index 0000000..bd62c1f
--- /dev/null
+++ b/src/gui/editors/guitar/GuitarChordSelectorDialog.cpp
@@ -0,0 +1,475 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
+
+/*
+ Rosegarden
+ A MIDI and audio sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#include "GuitarChordSelectorDialog.h"
+#include "GuitarChordEditorDialog.h"
+#include "ChordXmlHandler.h"
+#include "FingeringBox.h"
+#include "FingeringListBoxItem.h"
+
+#include "misc/Debug.h"
+#include <qlistbox.h>
+#include <qlayout.h>
+#include <qcombobox.h>
+#include <qpushbutton.h>
+#include <qlabel.h>
+#include <klocale.h>
+#include <kmessagebox.h>
+#include <kstddirs.h>
+
+namespace Rosegarden
+{
+
+GuitarChordSelectorDialog::GuitarChordSelectorDialog(QWidget *parent)
+ : KDialogBase(parent, "GuitarChordSelector", true, i18n("Guitar Chord Selector"), Ok|Cancel)
+{
+ QWidget *page = new QWidget(this);
+ setMainWidget(page);
+ QGridLayout *topLayout = new QGridLayout(page, 3, 4, spacingHint());
+
+ topLayout->addWidget(new QLabel(i18n("Root"), page), 0, 0);
+ m_rootNotesList = new QListBox(page);
+ topLayout->addWidget(m_rootNotesList, 1, 0);
+
+ topLayout->addWidget(new QLabel(i18n("Extension"), page), 0, 1);
+ m_chordExtList = new QListBox(page);
+ topLayout->addWidget(m_chordExtList, 1, 1);
+
+ m_newFingeringButton = new QPushButton(i18n("New"), page);
+ m_deleteFingeringButton = new QPushButton(i18n("Delete"), page);
+ m_editFingeringButton = new QPushButton(i18n("Edit"), page);
+
+ m_chordComplexityCombo = new QComboBox(page);
+ m_chordComplexityCombo->insertItem(i18n("beginner"));
+ m_chordComplexityCombo->insertItem(i18n("common"));
+ m_chordComplexityCombo->insertItem(i18n("all"));
+
+ connect(m_chordComplexityCombo, SIGNAL(activated(int)),
+ this, SLOT(slotComplexityChanged(int)));
+
+ QVBoxLayout* vboxLayout = new QVBoxLayout(page, 5);
+ topLayout->addMultiCellLayout(vboxLayout, 1, 3, 2, 2);
+ vboxLayout->addWidget(m_chordComplexityCombo);
+ vboxLayout->addStretch(10);
+ vboxLayout->addWidget(m_newFingeringButton);
+ vboxLayout->addWidget(m_deleteFingeringButton);
+ vboxLayout->addWidget(m_editFingeringButton);
+
+ connect(m_newFingeringButton, SIGNAL(clicked()),
+ this, SLOT(slotNewFingering()));
+ connect(m_deleteFingeringButton, SIGNAL(clicked()),
+ this, SLOT(slotDeleteFingering()));
+ connect(m_editFingeringButton, SIGNAL(clicked()),
+ this, SLOT(slotEditFingering()));
+
+ topLayout->addWidget(new QLabel(i18n("Fingerings"), page), 0, 3);
+ m_fingeringsList = new QListBox(page);
+ topLayout->addMultiCellWidget(m_fingeringsList, 1, 2, 3, 3);
+
+ m_fingeringBox = new FingeringBox(false, page);
+ topLayout->addMultiCellWidget(m_fingeringBox, 2, 2, 0, 1);
+
+ connect(m_rootNotesList, SIGNAL(highlighted(int)),
+ this, SLOT(slotRootHighlighted(int)));
+ connect(m_chordExtList, SIGNAL(highlighted(int)),
+ this, SLOT(slotChordExtHighlighted(int)));
+ connect(m_fingeringsList, SIGNAL(highlighted(QListBoxItem*)),
+ this, SLOT(slotFingeringHighlighted(QListBoxItem*)));
+}
+
+void
+GuitarChordSelectorDialog::init()
+{
+ // populate the listboxes
+ //
+ std::vector<QString> chordFiles = getAvailableChordFiles();
+
+ parseChordFiles(chordFiles);
+
+// m_chordMap.debugDump();
+
+ populate();
+}
+
+void
+GuitarChordSelectorDialog::populate()
+{
+ QStringList rootList = m_chordMap.getRootList();
+ if (rootList.count() > 0) {
+ m_rootNotesList->insertStringList(rootList);
+
+ QStringList extList = m_chordMap.getExtList(rootList.first());
+ populateExtensions(extList);
+
+ Guitar::ChordMap::chordarray chords = m_chordMap.getChords(rootList.first(), extList.first());
+ populateFingerings(chords);
+
+ m_chord.setRoot(rootList.first());
+ m_chord.setExt(extList.first());
+ }
+
+ m_rootNotesList->sort();
+
+ m_rootNotesList->setCurrentItem(0);
+}
+
+void
+GuitarChordSelectorDialog::clear()
+{
+ m_rootNotesList->clear();
+ m_chordExtList->clear();
+ m_fingeringsList->clear();
+}
+
+void
+GuitarChordSelectorDialog::refresh()
+{
+ clear();
+ populate();
+}
+
+void
+GuitarChordSelectorDialog::slotRootHighlighted(int i)
+{
+ NOTATION_DEBUG << "GuitarChordSelectorDialog::slotRootHighlighted " << i << endl;
+
+ m_chord.setRoot(m_rootNotesList->text(i));
+
+ QStringList extList = m_chordMap.getExtList(m_chord.getRoot());
+ populateExtensions(extList);
+ if (m_chordExtList->count() > 0)
+ m_chordExtList->setCurrentItem(0);
+ else
+ m_fingeringsList->clear(); // clear any previous fingerings
+}
+
+void
+GuitarChordSelectorDialog::slotChordExtHighlighted(int i)
+{
+ NOTATION_DEBUG << "GuitarChordSelectorDialog::slotChordExtHighlighted " << i << endl;
+
+ Guitar::ChordMap::chordarray chords = m_chordMap.getChords(m_chord.getRoot(), m_chordExtList->text(i));
+ populateFingerings(chords);
+
+ m_fingeringsList->setCurrentItem(0);
+}
+
+void
+GuitarChordSelectorDialog::slotFingeringHighlighted(QListBoxItem* listBoxItem)
+{
+ NOTATION_DEBUG << "GuitarChordSelectorDialog::slotFingeringHighlighted\n";
+
+ FingeringListBoxItem* fingeringItem = dynamic_cast<FingeringListBoxItem*>(listBoxItem);
+ if (fingeringItem) {
+ m_chord = fingeringItem->getChord();
+ m_fingeringBox->setFingering(m_chord.getFingering());
+ setEditionEnabled(m_chord.isUserChord());
+ }
+}
+
+void
+GuitarChordSelectorDialog::slotComplexityChanged(int)
+{
+ // simply repopulate the extension list box
+ //
+ QStringList extList = m_chordMap.getExtList(m_chord.getRoot());
+ populateExtensions(extList);
+ if (m_chordExtList->count() > 0)
+ m_chordExtList->setCurrentItem(0);
+ else
+ m_fingeringsList->clear(); // clear any previous fingerings
+}
+
+void
+GuitarChordSelectorDialog::slotNewFingering()
+{
+ Guitar::Chord newChord;
+ newChord.setRoot(m_chord.getRoot());
+ newChord.setExt(m_chord.getExt());
+
+ GuitarChordEditorDialog* chordEditorDialog = new GuitarChordEditorDialog(newChord, m_chordMap, this);
+
+ if (chordEditorDialog->exec() == QDialog::Accepted) {
+ m_chordMap.insert(newChord);
+ // populate lists
+ //
+ if (!m_rootNotesList->findItem(newChord.getRoot(), Qt::ExactMatch)) {
+ m_rootNotesList->insertItem(newChord.getRoot());
+ m_rootNotesList->sort();
+ }
+
+ if (!m_chordExtList->findItem(newChord.getExt(), Qt::ExactMatch)) {
+ m_chordExtList->insertItem(newChord.getExt());
+ m_chordExtList->sort();
+ }
+ }
+
+ delete chordEditorDialog;
+
+ refresh();
+}
+
+void
+GuitarChordSelectorDialog::slotDeleteFingering()
+{
+ if (m_chord.isUserChord()) {
+ m_chordMap.remove(m_chord);
+ delete m_fingeringsList->selectedItem();
+ }
+}
+
+void
+GuitarChordSelectorDialog::slotEditFingering()
+{
+ Guitar::Chord newChord = m_chord;
+ GuitarChordEditorDialog* chordEditorDialog = new GuitarChordEditorDialog(newChord, m_chordMap, this);
+
+ if (chordEditorDialog->exec() == QDialog::Accepted) {
+ NOTATION_DEBUG << "GuitarChordSelectorDialog::slotEditFingering() - current map state :\n";
+ m_chordMap.debugDump();
+ m_chordMap.substitute(m_chord, newChord);
+ NOTATION_DEBUG << "GuitarChordSelectorDialog::slotEditFingering() - new map state :\n";
+ m_chordMap.debugDump();
+ setChord(newChord);
+ }
+
+ delete chordEditorDialog;
+
+ refresh();
+}
+
+void
+GuitarChordSelectorDialog::slotOk()
+{
+ if (m_chordMap.needSave()) {
+ saveUserChordMap();
+ m_chordMap.clearNeedSave();
+ }
+
+ KDialogBase::slotOk();
+}
+
+void
+GuitarChordSelectorDialog::setChord(const Guitar::Chord& chord)
+{
+ NOTATION_DEBUG << "GuitarChordSelectorDialog::setChord " << chord << endl;
+
+ m_chord = chord;
+
+ // select the chord's root
+ //
+ m_rootNotesList->setCurrentItem(0);
+ QListBoxItem* correspondingRoot = m_rootNotesList->findItem(chord.getRoot(), Qt::ExactMatch);
+ if (correspondingRoot)
+ m_rootNotesList->setSelected(correspondingRoot, true);
+
+ // update the dialog's complexity setting if needed, then populate the extension list
+ //
+ QString chordExt = chord.getExt();
+ int complexityLevel = m_chordComplexityCombo->currentItem();
+ int chordComplexity = evaluateChordComplexity(chordExt);
+
+ if (chordComplexity > complexityLevel) {
+ m_chordComplexityCombo->setCurrentItem(chordComplexity);
+ }
+
+ QStringList extList = m_chordMap.getExtList(chord.getRoot());
+ populateExtensions(extList);
+
+ // select the chord's extension
+ //
+ if (chordExt.isEmpty()) {
+ chordExt = "";
+ m_chordExtList->setSelected(0, true);
+ } else {
+ QListBoxItem* correspondingExt = m_chordExtList->findItem(chordExt, Qt::ExactMatch);
+ if (correspondingExt)
+ m_chordExtList->setSelected(correspondingExt, true);
+ }
+
+ // populate fingerings and pass the current chord's fingering so it is selected
+ //
+ Guitar::ChordMap::chordarray similarChords = m_chordMap.getChords(chord.getRoot(), chord.getExt());
+ populateFingerings(similarChords, chord.getFingering());
+}
+
+void
+GuitarChordSelectorDialog::populateFingerings(const Guitar::ChordMap::chordarray& chords, const Guitar::Fingering& refFingering)
+{
+ m_fingeringsList->clear();
+
+ for(Guitar::ChordMap::chordarray::const_iterator i = chords.begin(); i != chords.end(); ++i) {
+ const Guitar::Chord& chord = *i;
+ QString fingeringString = chord.getFingering().toString();
+ NOTATION_DEBUG << "GuitarChordSelectorDialog::populateFingerings " << chord << endl;
+ QPixmap fingeringPixmap = getFingeringPixmap(chord.getFingering());
+ FingeringListBoxItem *item = new FingeringListBoxItem(chord, m_fingeringsList, fingeringPixmap, fingeringString);
+ if (refFingering == chord.getFingering()) {
+ NOTATION_DEBUG << "GuitarChordSelectorDialog::populateFingerings - fingering found " << fingeringString << endl;
+ m_fingeringsList->setSelected(item, true);
+ }
+ }
+
+}
+
+
+QPixmap
+GuitarChordSelectorDialog::getFingeringPixmap(const Guitar::Fingering& fingering) const
+{
+ QPixmap pixmap(FINGERING_PIXMAP_WIDTH, FINGERING_PIXMAP_HEIGHT);
+ pixmap.fill();
+
+ QPainter pp(&pixmap);
+ QPainter *p = &pp;
+
+ p->setViewport(FINGERING_PIXMAP_H_MARGIN, FINGERING_PIXMAP_W_MARGIN,
+ FINGERING_PIXMAP_WIDTH - FINGERING_PIXMAP_W_MARGIN,
+ FINGERING_PIXMAP_HEIGHT - FINGERING_PIXMAP_H_MARGIN);
+
+ Guitar::NoteSymbols::drawFingeringPixmap(fingering, m_fingeringBox->getNoteSymbols(), p);
+
+ return pixmap;
+}
+
+void
+GuitarChordSelectorDialog::populateExtensions(const QStringList& extList)
+{
+ m_chordExtList->clear();
+
+ if (m_chordComplexityCombo->currentItem() != COMPLEXITY_ALL) {
+ // some filtering needs to be done
+ int complexityLevel = m_chordComplexityCombo->currentItem();
+
+ QStringList filteredList;
+ for(QStringList::const_iterator i = extList.constBegin(); i != extList.constEnd(); ++i) {
+ if (evaluateChordComplexity((*i).lower().stripWhiteSpace()) <= complexityLevel) {
+ NOTATION_DEBUG << "GuitarChordSelectorDialog::populateExtensions - adding '" << *i << "'\n";
+ filteredList.append(*i);
+ }
+ }
+
+ m_chordExtList->insertStringList(filteredList);
+
+ } else {
+ m_chordExtList->insertStringList(extList);
+ }
+}
+
+int
+GuitarChordSelectorDialog::evaluateChordComplexity(const QString& ext)
+{
+ if (ext.isEmpty() ||
+ ext == "7" ||
+ ext == "m" ||
+ ext == "5")
+ return COMPLEXITY_BEGINNER;
+
+ if (ext == "dim" ||
+ ext == "dim7" ||
+ ext == "aug" ||
+ ext == "sus2" ||
+ ext == "sus4" ||
+ ext == "maj7" ||
+ ext == "m7" ||
+ ext == "mmaj7" ||
+ ext == "m7b5" ||
+ ext == "7sus4")
+
+ return COMPLEXITY_COMMON;
+
+ return COMPLEXITY_ALL;
+}
+
+void
+GuitarChordSelectorDialog::parseChordFiles(const std::vector<QString>& chordFiles)
+{
+ for(std::vector<QString>::const_iterator i = chordFiles.begin(); i != chordFiles.end(); ++i) {
+ parseChordFile(*i);
+ }
+}
+
+void
+GuitarChordSelectorDialog::parseChordFile(const QString& chordFileName)
+{
+ ChordXmlHandler handler(m_chordMap);
+ QFile chordFile(chordFileName);
+ bool ok = chordFile.open(IO_ReadOnly);
+ if (!ok)
+ KMessageBox::error(0, i18n("couldn't open file '%1'").arg(handler.errorString()));
+
+ QXmlInputSource source(chordFile);
+ QXmlSimpleReader reader;
+ reader.setContentHandler(&handler);
+ reader.setErrorHandler(&handler);
+ NOTATION_DEBUG << "GuitarChordSelectorDialog::parseChordFile() parsing " << chordFileName << endl;
+ reader.parse(source);
+ if (!ok)
+ KMessageBox::error(0, i18n("couldn't parse chord dictionnary : %1").arg(handler.errorString()));
+
+}
+
+void
+GuitarChordSelectorDialog::setEditionEnabled(bool enabled)
+{
+ m_deleteFingeringButton->setEnabled(enabled);
+ m_editFingeringButton->setEnabled(enabled);
+}
+
+std::vector<QString>
+GuitarChordSelectorDialog::getAvailableChordFiles()
+{
+ std::vector<QString> names;
+
+ // Read config for default directory
+ QStringList chordDictFiles = KGlobal::dirs()->findAllResources("appdata", "chords/*.xml");
+
+ for(QStringList::iterator i = chordDictFiles.begin(); i != chordDictFiles.end(); ++i) {
+ NOTATION_DEBUG << "GuitarChordSelectorDialog::getAvailableChordFiles : adding file " << *i << endl;
+ names.push_back(*i);
+ }
+
+ return names;
+}
+
+bool
+GuitarChordSelectorDialog::saveUserChordMap()
+{
+ // Read config for user directory
+ QString userDir = KGlobal::dirs()->saveLocation("appdata", "chords/");
+
+ QString userChordDictPath = userDir + "/user_chords.xml";
+
+ NOTATION_DEBUG << "GuitarChordSelectorDialog::saveUserChordMap() : saving user chord map to " << userChordDictPath << endl;
+ QString errMsg;
+
+ m_chordMap.saveDocument(userChordDictPath, true, errMsg);
+
+ return errMsg.isEmpty();
+}
+
+
+}
+
+
+#include "GuitarChordSelectorDialog.moc"
diff --git a/src/gui/editors/guitar/GuitarChordSelectorDialog.h b/src/gui/editors/guitar/GuitarChordSelectorDialog.h
new file mode 100644
index 0000000..6c8f1ad
--- /dev/null
+++ b/src/gui/editors/guitar/GuitarChordSelectorDialog.h
@@ -0,0 +1,120 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
+
+/*
+ Rosegarden
+ A MIDI and audio sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#ifndef _RG_GUITARCHORDSELECTORDIALOG_H_
+#define _RG_GUITARCHORDSELECTORDIALOG_H_
+
+#include "Chord.h"
+#include "ChordMap.h"
+
+#include <kdialogbase.h>
+#include <qstring.h>
+#include <vector>
+
+class QListBox;
+class QListBoxItem;
+class QComboBox;
+class QPushButton;
+
+namespace Rosegarden
+{
+
+class FingeringBox;
+
+class GuitarChordSelectorDialog : public KDialogBase
+{
+ Q_OBJECT
+
+ enum { COMPLEXITY_BEGINNER, COMPLEXITY_COMMON, COMPLEXITY_ALL };
+
+public:
+ GuitarChordSelectorDialog(QWidget *parent=0);
+
+ void init();
+
+ const Guitar::Chord& getChord() const { return m_chord; }
+
+ void setChord(const Guitar::Chord&);
+
+protected slots:
+ void slotRootHighlighted(int);
+ void slotChordExtHighlighted(int);
+ void slotFingeringHighlighted(QListBoxItem*);
+ void slotComplexityChanged(int);
+
+ void slotNewFingering();
+ void slotDeleteFingering();
+ void slotEditFingering();
+
+ virtual void slotOk();
+
+protected:
+
+ void parseChordFiles(const std::vector<QString>& chordFiles);
+ void parseChordFile(const QString& chordFileName);
+ void populateFingerings(const Guitar::ChordMap::chordarray&, const Guitar::Fingering& refFingering=Guitar::Fingering(0));
+ void populateExtensions(const QStringList& extList);
+
+ /// set enabled state of edit/delete buttons
+ void setEditionEnabled(bool);
+
+ void populate();
+ void clear();
+ void refresh();
+
+ bool saveUserChordMap();
+ int evaluateChordComplexity(const QString& ext);
+
+ QPixmap getFingeringPixmap(const Guitar::Fingering& fingering) const;
+
+ /// Find all chord list files on the system
+ std::vector<QString> getAvailableChordFiles();
+
+ Guitar::ChordMap m_chordMap;
+
+ /// current selected chord
+ Guitar::Chord m_chord;
+
+ // Chord data
+ QListBox* m_rootNotesList;
+ QListBox* m_chordExtList;
+ QListBox* m_fingeringsList;
+ FingeringBox* m_fingeringBox;
+
+ QComboBox* m_chordComplexityCombo;
+ QPushButton* m_newFingeringButton;
+ QPushButton* m_deleteFingeringButton;
+ QPushButton* m_editFingeringButton;
+
+ static const unsigned int FINGERING_PIXMAP_HEIGHT = 75;
+ static const unsigned int FINGERING_PIXMAP_WIDTH = 75;
+ static const unsigned int FINGERING_PIXMAP_H_MARGIN = 5;
+ static const unsigned int FINGERING_PIXMAP_W_MARGIN = 5;
+
+};
+
+}
+
+#endif /*_RG_GUITARCHORDSELECTORDIALOG_H_*/
diff --git a/src/gui/editors/guitar/NoteSymbols.cpp b/src/gui/editors/guitar/NoteSymbols.cpp
new file mode 100644
index 0000000..14379de
--- /dev/null
+++ b/src/gui/editors/guitar/NoteSymbols.cpp
@@ -0,0 +1,486 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
+
+/*
+ Rosegarden
+ A MIDI and audio sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ This file contains code from
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#include "NoteSymbols.h"
+#include "Fingering.h"
+#include "misc/Debug.h"
+
+namespace Rosegarden
+{
+
+namespace Guitar
+{
+NoteSymbols::posPair
+NoteSymbols::getX ( int imgWidth, unsigned int stringNb, unsigned int nbOfStrings ) const
+{
+ /*
+ std::cout << "NoteSymbols::getX - input values" << std::endl
+ << " position: " << position << std::endl
+ << " string #: " << string_num << std::endl
+ << " scale: " << scale << std::endl;
+ */
+ unsigned int lBorder = getLeftBorder( imgWidth );
+ unsigned int guitarChordWidth = getGuitarChordWidth( imgWidth );
+ unsigned int columnWidth = guitarChordWidth / nbOfStrings;
+ return std::make_pair( ( stringNb * columnWidth + lBorder ), columnWidth );
+}
+
+NoteSymbols::posPair
+NoteSymbols::getY ( int imgHeight, unsigned int fretNb, unsigned int nbOfFrets ) const
+{
+ /*
+ std::cout << "NoteSymbols::getY - input values" << std::endl
+ << " position: " << fret_pos << std::endl
+ << " max frets: " << maxFretNum << std::endl
+ << " scale: " << scale << std::endl;
+ */
+ unsigned int tBorder = getTopBorder( imgHeight );
+ unsigned int guitarChordHeight = getGuitarChordHeight( imgHeight );
+ unsigned int rowHeight = guitarChordHeight / nbOfFrets;
+ return std::make_pair( ( ( fretNb * rowHeight ) + tBorder ), rowHeight );
+}
+
+void
+NoteSymbols::drawMuteSymbol ( QPainter* p,
+ unsigned int position ) const
+{
+ QRect v = p->viewport();
+
+ posPair x_pos = getX ( v.width(), position, m_nbOfStrings );
+ unsigned int y_pos = getTopBorder( v.height() ) / 2;
+ double columnWidth = x_pos.second;
+ unsigned int width = static_cast<unsigned int>( columnWidth * 0.7 );
+ unsigned int height = static_cast<unsigned int>( columnWidth * 0.7 );
+
+ //std::cout << "NoteSymbols::drawMuteSymbol - drawing Mute symbol at string #" << position
+ //<< std::endl;
+
+ p->drawLine ( x_pos.first - ( width / 2 ),
+ y_pos - ( height / 2 ),
+ ( x_pos.first + ( width / 2 ) ),
+ y_pos + ( height / 2 ) );
+
+ p->drawLine( x_pos.first + ( width / 2 ),
+ y_pos - ( height / 2 ),
+ ( x_pos.first - ( width / 2 ) ),
+ y_pos + ( height / 2 ) );
+}
+
+void
+NoteSymbols::drawOpenSymbol ( QPainter* p,
+ unsigned int position ) const
+{
+ QRect v = p->viewport();
+ posPair x_pos = getX ( v.width(), position, m_nbOfStrings );
+ unsigned int y_pos = getTopBorder( v.height() ) / 2;
+ double columnWidth = x_pos.second;
+ unsigned int radius = static_cast<unsigned int>( columnWidth * 0.7 );
+
+ //std::cout << "NoteSymbols::drawOpenSymbol - drawing Open symbol at string #" << position
+ //<< std::endl;
+
+ p->setBrush( QBrush(p->brush().color(), Qt::NoBrush) );
+ p->drawEllipse( x_pos.first - ( radius / 2 ),
+ y_pos - ( radius / 2 ),
+ radius,
+ radius );
+}
+
+void
+NoteSymbols::drawNoteSymbol ( QPainter* p,
+ unsigned int stringNb,
+ int fretNb,
+ bool transient ) const
+{
+// NOTATION_DEBUG << "NoteSymbols::drawNoteSymbol - string: " << stringNb << ", fret:" << fretNb << endl;
+
+ QRect v = p->viewport();
+ posPair x_pos = getX ( v.width(), stringNb, m_nbOfStrings );
+ posPair y_pos = getY ( v.height(), fretNb, m_nbOfFrets );
+ double columnWidth = x_pos.second;
+ unsigned int radius;
+
+ if (transient) {
+ radius = static_cast<unsigned int>( columnWidth /* * 0.9 */ );
+ p->setBrush( QBrush(p->brush().color(), Qt::NoBrush) );
+ } else {
+ radius = static_cast<unsigned int>( columnWidth * 0.7 );
+ p->setBrush( QBrush(p->brush().color(), Qt::SolidPattern) );
+ }
+
+ int x = x_pos.first - ( radius / 2 ),
+ y = y_pos.first + ( (y_pos.second - radius) / 2) - y_pos.second + TOP_GUITAR_CHORD_MARGIN;
+
+// y = y_pos.first - (radius / 2) - y_pos.second + TOP_GUITAR_CHORD_MARGIN;
+
+// RG_DEBUG << "NoteSymbols::drawNoteSymbol : rect = " << QRect(x,y, radius, radius) << endl;
+
+ p->drawEllipse( x,
+ y,
+ radius,
+ radius );
+
+// p->save();
+// p->setPen(Qt::red);
+// p->drawRect( x, y, radius, radius );
+// p->restore();
+}
+
+void
+NoteSymbols::drawBarreSymbol ( QPainter* p,
+ int fretNb,
+ unsigned int start,
+ unsigned int end ) const
+{
+
+ //std::cout << "NoteSymbols::drawBarreSymbol - start: " << start << ", end:" << end << std::endl;
+
+ drawNoteSymbol ( p, start, fretNb );
+
+ if ( ( end - start ) >= 1 ) {
+ QRect v = p->viewport();
+ posPair startXPos = getX ( v.width(), start, m_nbOfStrings );
+ posPair endXPos = getX ( v.width(), end, m_nbOfStrings );
+ posPair y_pos = getY ( v.height(), fretNb, m_nbOfFrets );
+ double columnWidth = startXPos.second;
+ unsigned int thickness = static_cast<unsigned int>( columnWidth * 0.7 );
+
+ p->drawRect( startXPos.first,
+ y_pos.first + ( y_pos.second / 4 ) + TOP_GUITAR_CHORD_MARGIN,
+ endXPos.first - startXPos.first,
+ thickness );
+ }
+
+ drawNoteSymbol ( p, end, fretNb );
+}
+
+void
+NoteSymbols::drawFretNumber ( QPainter* p,
+ unsigned int fret_num ) const
+{
+ if ( fret_num > 1 ) {
+ QRect v = p->viewport();
+ unsigned int imgWidth = v.width();
+ unsigned int imgHeight = v.height();
+
+ p->save();
+ QFont font;
+ font.setPixelSize(getFontPixelSize(v.width(), v.height()));
+ p->setFont(font);
+
+ QString tmp;
+ tmp.setNum( fret_num );
+
+ // Use NoteSymbols to grab X and Y for first fret
+ posPair y_pos = getY( imgHeight, 0, m_nbOfFrets );
+
+ p->drawText( getLeftBorder( imgWidth ) / 4,
+ y_pos.first + ( y_pos.second / 2 ),
+ tmp );
+
+ p->restore();
+ }
+}
+
+void
+NoteSymbols::drawFrets ( QPainter* p ) const
+{
+ /*
+ std::cout << "NoteSymbols::drawFretHorizontalLines" << std::endl
+ << " scale: " << scale << std::endl
+ << " frets: " << fretsDisplayed << std::endl
+ << " max string: " << maxStringNum << std::endl;
+ */
+
+ QRect v = p->viewport();
+ unsigned int imgWidth = v.width();
+ unsigned int imgHeight = v.height();
+ //unsigned int endXPos = getGuitarChordWidth(imgWidth) + getLeftBorder(imgWidth);
+ posPair endXPos = getX ( imgWidth, m_nbOfStrings - 1, m_nbOfStrings );
+
+ unsigned int yGuitarChord = getGuitarChordHeight( imgHeight );
+ unsigned int rowHeight = yGuitarChord / m_nbOfFrets;
+
+ QPen pen(p->pen());
+ pen.setWidth(imgHeight >= 100 ? FRET_PEN_WIDTH : FRET_PEN_WIDTH / 2);
+ p->save();
+ p->setPen(pen);
+ unsigned int y_pos = (getY ( imgHeight, 0, m_nbOfFrets )).first + TOP_GUITAR_CHORD_MARGIN;
+
+// NOTATION_DEBUG << "NoteSymbols::drawFrets : " << m_nbOfFrets << endl;
+
+ // Horizontal lines
+ for ( unsigned int i = 0; i <= m_nbOfFrets; ++i ) {
+
+ /* This code borrowed from KGuitar 0.5 */
+ p->drawLine( getLeftBorder( imgWidth ),
+ y_pos,
+ endXPos.first,
+ y_pos);
+// NOTATION_DEBUG << "NoteSymbols::drawFrets : " << QPoint(getLeftBorder(imgWidth), y_pos)
+// << " to " << QPoint(endXPos.first, y_pos) << endl;
+
+
+ y_pos += rowHeight;
+ }
+
+ p->restore();
+
+}
+
+void
+NoteSymbols::drawStrings ( QPainter* p ) const
+{
+ // Vertical lines
+ QRect v = p->viewport();
+ int imgHeight = v.height();
+ int imgWidth = v.width();
+
+ unsigned int startPos = getTopBorder( imgHeight ) + TOP_GUITAR_CHORD_MARGIN;
+ unsigned int endPos = (getY ( imgHeight, m_nbOfFrets, m_nbOfFrets )).first + TOP_GUITAR_CHORD_MARGIN;
+
+ unsigned int guitarChordWidth = getGuitarChordWidth( imgWidth );
+ unsigned int columnWidth = guitarChordWidth / m_nbOfStrings;
+
+ unsigned int x_pos = (getX ( imgWidth, 0, m_nbOfStrings )).first;
+
+ QPen pen(p->pen());
+ pen.setWidth(imgWidth >= 100 ? STRING_PEN_WIDTH : STRING_PEN_WIDTH / 2);
+ p->save();
+ p->setPen(pen);
+
+ for ( unsigned int i = 0; i < m_nbOfStrings; ++i ) {
+
+ /* This code borrowed from KGuitar 0.5 */
+ p->drawLine( x_pos,
+ startPos,
+ x_pos,
+ endPos );
+
+ x_pos += columnWidth;
+ }
+
+ p->restore();
+
+}
+
+QRect NoteSymbols::getTransientNoteSymbolRect(QSize guitarChordSize,
+ unsigned int stringNb,
+ int fretNb) const
+{
+ posPair x_pos = getX ( guitarChordSize.width(), stringNb, m_nbOfStrings );
+ posPair y_pos = getY ( guitarChordSize.height(), fretNb, m_nbOfFrets );
+ double columnWidth = x_pos.second;
+ unsigned int radius = static_cast<unsigned int>( columnWidth /* * 0.9 */ );
+
+ int x = x_pos.first - ( radius / 2 ),
+ y = y_pos.first + ( (y_pos.second - radius) / 2) - y_pos.second + TOP_GUITAR_CHORD_MARGIN;
+
+ return QRect(x, y, radius, radius);
+}
+
+unsigned int
+NoteSymbols::getTopBorder ( unsigned int imgHeight ) const
+{
+ return static_cast<unsigned int>( TOP_BORDER_PERCENTAGE * imgHeight );
+}
+
+unsigned int
+NoteSymbols::getBottomBorder ( unsigned int imgHeight ) const
+{
+ return static_cast<unsigned int>( imgHeight * BOTTOM_BORDER_PERCENTAGE );
+}
+
+unsigned int
+NoteSymbols::getLeftBorder ( unsigned int imgWidth ) const
+{
+ unsigned int left = static_cast<unsigned int>( imgWidth * LEFT_BORDER_PERCENTAGE );
+ if ( left < 15 ) {
+ left = 15;
+ }
+ return left;
+}
+
+unsigned int
+NoteSymbols::getRightBorder ( unsigned int imgWidth ) const
+{
+ return static_cast<unsigned int>( imgWidth * RIGHT_BORDER_PERCENTAGE );
+}
+
+unsigned int
+NoteSymbols::getGuitarChordWidth ( int imgWidth ) const
+{
+ return static_cast<unsigned int>( imgWidth * GUITAR_CHORD_WIDTH_PERCENTAGE );
+}
+
+unsigned int
+NoteSymbols::getGuitarChordHeight ( int imgHeight ) const
+{
+ return static_cast<unsigned int>( imgHeight * GUITAR_CHORD_HEIGHT_PERCENTAGE );
+}
+
+unsigned int
+NoteSymbols::getFontPixelSize ( int imgWidth, int imgHeight ) const
+{
+ return std::max(8, imgHeight / 10);
+}
+
+std::pair<bool, unsigned int>
+NoteSymbols::getStringNumber ( int imgWidth,
+ unsigned int x_pos,
+ unsigned int maxStringNum ) const
+{
+ /*
+ std::cout << "NoteSymbols::getNumberOfStrings - input values" << std::endl
+ << " X position: " << x_pos << std::endl
+ << " string #: " << maxStringNum << std::endl
+ << " image width: " << imgWidth << std::endl;
+ */
+ bool valueOk = false;
+
+ posPair xPairPos;
+ unsigned int min = 0;
+ unsigned int max = 0;
+ unsigned int result = 0;
+
+ for ( unsigned int i = 0; i < maxStringNum; ++i ) {
+ xPairPos = getX ( imgWidth, i, maxStringNum );
+
+ // If the counter equals zero then we are at the first
+ // string to the left
+ if ( i == 0 ) {
+ // Add 10 pixel buffer to range comparison
+ min = xPairPos.first - 10;
+ } else {
+ min = xPairPos.first - xPairPos.second / 2;
+ }
+
+ // If the counter equals the maxString number -1 then we are at the last
+ // string to the right
+ if ( i == ( maxStringNum - 1 ) ) {
+ // Add 10 pixel buffer to range comparison
+ max = xPairPos.first + 10;
+ } else {
+ max = xPairPos.first + xPairPos.second / 2;
+ }
+
+ if ( ( x_pos >= min ) && ( x_pos <= max ) ) {
+ result = i;
+ valueOk = true;
+ break;
+ }
+ }
+
+ //std::cout << "NoteSymbols::getNumberOfStrings - string: #" << result << std::endl;
+ return std::make_pair( valueOk, result );
+}
+
+std::pair<bool, unsigned int>
+NoteSymbols::getFretNumber ( int imgHeight,
+ unsigned int y_pos,
+ unsigned int maxFretNum ) const
+{
+ /*
+ std::cout << "NoteSymbols::getNumberOfFrets - input values" << std::endl
+ << " Y position: " << y_pos << std::endl
+ << " max frets: " << maxFretNum << std::endl
+ << " image height: " << imgHeight << std::endl;
+ */
+
+ bool valueOk = false;
+ unsigned int tBorder = getTopBorder( imgHeight );
+ unsigned int result = 0;
+
+ if ( y_pos < tBorder ) {
+ // User pressing above the guitar chord to mark line muted or opened
+ valueOk = true;
+ } else {
+ typedef std::pair<unsigned int, unsigned int> RangePair;
+
+ posPair min_pos;
+ posPair max_pos;
+
+ for ( unsigned int i = 0; i < maxFretNum; ++i ) {
+ min_pos = getY ( imgHeight, i, maxFretNum );
+ max_pos = getY ( imgHeight, i + 1, maxFretNum );
+
+ if ( ( y_pos >= min_pos.first ) && y_pos <= max_pos.first - 1 ) {
+ result = i + 1;
+ valueOk = true;
+ break;
+ }
+ }
+ }
+ // std::cout << " fret #: " << result << std::endl;
+ return std::make_pair( valueOk, result );
+}
+
+void
+NoteSymbols::drawFingeringPixmap(const Guitar::Fingering& fingering, const Guitar::NoteSymbols& noteSymbols, QPainter *p)
+{
+ unsigned int startFret = fingering.getStartFret();
+
+ noteSymbols.drawFretNumber(p, startFret);
+ noteSymbols.drawFrets(p);
+ noteSymbols.drawStrings(p);
+
+ unsigned int stringNb = 0;
+
+ for (Fingering::const_iterator pos = fingering.begin();
+ pos != fingering.end();
+ ++pos, ++stringNb) {
+
+ switch (*pos) {
+ case Fingering::OPEN:
+ noteSymbols.drawOpenSymbol(p, stringNb);
+ break;
+
+ case Fingering::MUTED:
+ noteSymbols.drawMuteSymbol(p, stringNb);
+ break;
+
+ default:
+ noteSymbols.drawNoteSymbol(p, stringNb, *pos - (startFret - 1), false);
+ break;
+ }
+ }
+
+}
+
+
+float const NoteSymbols::LEFT_BORDER_PERCENTAGE = 0.2;
+float const NoteSymbols::RIGHT_BORDER_PERCENTAGE = 0.1;
+float const NoteSymbols::GUITAR_CHORD_WIDTH_PERCENTAGE = 0.8;
+float const NoteSymbols::TOP_BORDER_PERCENTAGE = 0.1;
+float const NoteSymbols::BOTTOM_BORDER_PERCENTAGE = 0.1;
+float const NoteSymbols::GUITAR_CHORD_HEIGHT_PERCENTAGE = 0.8;
+int const NoteSymbols::TOP_GUITAR_CHORD_MARGIN = 5;
+int const NoteSymbols::FRET_PEN_WIDTH = 2;
+int const NoteSymbols::STRING_PEN_WIDTH = 2;
+
+} /* namespace Guitar */
+
+}
+
diff --git a/src/gui/editors/guitar/NoteSymbols.h b/src/gui/editors/guitar/NoteSymbols.h
new file mode 100644
index 0000000..f90fefb
--- /dev/null
+++ b/src/gui/editors/guitar/NoteSymbols.h
@@ -0,0 +1,192 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
+
+/*
+ Rosegarden
+ A MIDI and audio sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ This file contains code from
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#ifndef _RG_SYMBOLS_H_
+#define _RG_SYMBOLS_H_
+
+#include <qbrush.h>
+#include <qpainter.h>
+
+namespace Rosegarden
+{
+
+/**
+ *----------------------------------------
+ * Finding X position on guitar chord pixmap
+ *----------------------------------------
+ *
+ * Originally x = position * scale + FC::BORDER + FC::CIRCBORD + FC::FRETTEXT
+ *
+ * The last three can be condense into on term called XBorder
+ * XBorder = FC::BORDER + FC::CIRCBORD + FC::FRETTEXT
+ * = 5 + 2 + 10 (see fingers.h)
+ * = 17
+ *
+ * The drawable guitar chord space on the x-axis:
+ * XGuitarChord = pixmap width - XBorder
+ * = width - 17
+ *
+ * The guitar chord x-axis is broken up into colums which represent the drawable
+ * space for a guitar chord component (e.g. note, barre)
+ * Column Width = XGuitarChord / number of strings
+ *
+ * Therefore a new x can be calculated from the position and the column width
+ * x = (position * Column Width) + XBorder
+ *
+ *-------------------------------------------
+ * Finding Y position on guitar chord pixmap
+ *-------------------------------------------
+ *
+ * Originally y = (FC::BORDER * scale) + (2 * FC::SPACER) + (fret * scale) + FC::CIRCBORD
+ *
+ * As with the x-axis the equation can be separated into the position plus the border. In
+ * this case YBorder
+ * YBorder = (FC::BORDER*scale) + (2*FC::SPACER) + FC::CIRCBORD
+ * = 17 (If we want to use the same border as the x-axis)
+ *
+ * The drawable guitar chord space on the y-axis:
+ * YGuitarChord = pixmap height - YBorder
+ *
+ * The guitar chord y-axis is broken up into rows which represent the drawable
+ * space for a guitar chord component (e.g. note, barre)
+ * Row Height = YGuitarChord / number of frets
+ *
+ * Therefore a new y can be calculated from the fret position and the row height
+ * y = fret * Row Height
+ **/
+
+namespace Guitar
+{
+
+class Fingering;
+
+
+class NoteSymbols
+{
+private:
+ typedef std::pair<unsigned int, unsigned int> posPair;
+
+ static float const LEFT_BORDER_PERCENTAGE;
+ static float const RIGHT_BORDER_PERCENTAGE;
+ static float const GUITAR_CHORD_WIDTH_PERCENTAGE;
+ static float const TOP_BORDER_PERCENTAGE;
+ static float const BOTTOM_BORDER_PERCENTAGE;
+ static float const GUITAR_CHORD_HEIGHT_PERCENTAGE;
+ static int const TOP_GUITAR_CHORD_MARGIN;
+ static int const FRET_PEN_WIDTH;
+ static int const STRING_PEN_WIDTH;
+
+public:
+
+ NoteSymbols(unsigned int nbOfStrings, unsigned int nbOfFrets) :
+ m_nbOfStrings(nbOfStrings),
+ m_nbOfFrets(nbOfFrets) {};
+
+ //! Display a mute symbol in the QPainter object
+ void
+ drawMuteSymbol ( QPainter* p,
+ unsigned int position ) const;
+
+ /* This code borrowed from KGuitar 0.5 */
+ //! Display a open symbol in the QPainter object (KGuitar)
+ void drawOpenSymbol ( QPainter* p,
+ unsigned int position ) const;
+
+ /* This code borrowed from KGuitar 0.5 */
+ //! Display a note symbol in the QPainter object (KGuitar)
+ void drawNoteSymbol ( QPainter* p,
+ unsigned int stringNb,
+ int fretNb,
+ bool transient = false ) const;
+
+ /* This code borrowed from KGuitar 0.5 */
+ /**
+ * Display a bar symbol in the QPainter object (KGuitar)
+ * The code from the KGuitar project was modified to display a bar. This feature was not
+ * available in that project
+ */
+ void drawBarreSymbol ( QPainter* p,
+ int fretNb,
+ unsigned int start,
+ unsigned int end ) const;
+
+ void drawFretNumber ( QPainter* p,
+ unsigned int fret_num ) const;
+
+ void drawFrets ( QPainter* p ) const;
+
+ void drawStrings ( QPainter* p ) const;
+
+ unsigned int getTopBorder ( unsigned int imgHeight ) const;
+
+ unsigned int getBottomBorder ( unsigned int imgHeight ) const;
+
+ unsigned int getLeftBorder ( unsigned int imgWidth ) const;
+
+ unsigned int getRightBorder ( unsigned int imgWidth ) const;
+
+ unsigned int getGuitarChordWidth ( int imgWidth ) const;
+
+ unsigned int getGuitarChordHeight ( int imgHeight ) const;
+
+ unsigned int getFontPixelSize ( int imgWidth, int imgHeight ) const;
+
+ std::pair<bool, unsigned int>
+ getStringNumber ( int imgWidth,
+ unsigned int x_pos,
+ unsigned int string_num ) const;
+
+ std::pair<bool, unsigned int>
+ getFretNumber ( int imgHeight,
+ unsigned int y_pos,
+ unsigned int maxFretNum ) const;
+
+ QRect getTransientNoteSymbolRect(QSize guitarChordSize,
+ unsigned int stringNb,
+ int fretNb) const;
+
+ static void drawFingeringPixmap(const Fingering& fingering, const NoteSymbols& noteSymbols, QPainter *p);
+
+private:
+
+ posPair
+ getX ( int imgWidth, unsigned int stringNb, unsigned int nbOfStrings ) const;
+
+ posPair
+ getY ( int imgHeight, unsigned int fretNb, unsigned int nbOfFrets ) const;
+
+
+ unsigned int m_nbOfStrings;
+ unsigned int m_nbOfFrets;
+
+};
+
+} /* namespace Guitar */
+
+}
+
+#endif /* SYMBOLS_H_ */
+
diff --git a/src/gui/editors/matrix/MatrixCanvasView.cpp b/src/gui/editors/matrix/MatrixCanvasView.cpp
new file mode 100644
index 0000000..c92b4aa
--- /dev/null
+++ b/src/gui/editors/matrix/MatrixCanvasView.cpp
@@ -0,0 +1,302 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
+
+/*
+ Rosegarden
+ A MIDI and audio sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "MatrixCanvasView.h"
+
+#include "base/SnapGrid.h"
+#include "gui/general/MidiPitchLabel.h"
+#include "gui/general/RosegardenCanvasView.h"
+#include "MatrixElement.h"
+#include "MatrixStaff.h"
+#include "QCanvasMatrixRectangle.h"
+#include "QCanvasMatrixDiamond.h"
+#include <qcanvas.h>
+#include <qpoint.h>
+#include <qwidget.h>
+#include "misc/Debug.h"
+
+
+
+namespace Rosegarden
+{
+
+MatrixCanvasView::MatrixCanvasView(MatrixStaff& staff,
+ SnapGrid *snapGrid,
+ bool drumMode,
+ QCanvas *viewing, QWidget *parent,
+ const char *name, WFlags f)
+ : RosegardenCanvasView(viewing, parent, name, f),
+ m_staff(staff),
+ m_snapGrid(snapGrid),
+ m_drumMode(drumMode),
+ m_previousEvTime(0),
+ m_previousEvPitch(0),
+ m_mouseWasPressed(false),
+ m_ignoreClick(false),
+ m_smoothModifier(Qt::ShiftButton),
+ m_lastSnap(SnapGrid::SnapToBeat),
+ m_isSnapTemporary(false)
+{
+ viewport()->setMouseTracking(true);
+}
+
+MatrixCanvasView::~MatrixCanvasView()
+{}
+
+void MatrixCanvasView::contentsMousePressEvent(QMouseEvent* e)
+{
+ QPoint p = inverseMapPoint(e->pos());
+
+ updateGridSnap(e);
+
+ MATRIX_DEBUG << "MatrixCanvasView::contentsMousePressEvent: snap time is " << m_snapGrid->getSnapTime(double(p.x())) << endl;
+
+ timeT evTime;
+
+ if (m_drumMode) {
+ evTime = m_snapGrid->snapX(p.x(), SnapGrid::SnapEither);
+ MATRIX_DEBUG << "MatrixCanvasView: drum mode: snapEither " << p.x() << " -> " << evTime << endl;
+ } else {
+ evTime = m_snapGrid->snapX(p.x(), SnapGrid::SnapLeft);
+ MATRIX_DEBUG << "MatrixCanvasView: normal mode: snapLeft " << p.x() << " -> " << evTime << endl;
+ }
+
+ int evPitch = m_staff.getHeightAtCanvasCoords(p.x(), p.y());
+
+ timeT emTime = m_staff.getSegment().getEndMarkerTime();
+ if (evTime > emTime)
+ evTime = emTime;
+ timeT esTime = m_staff.getSegment().getStartTime();
+ if (evTime < esTime)
+ evTime = esTime;
+
+// std::cerr << "MatrixCanvasView::contentsMousePressEvent() at pitch "
+// << evPitch << ", time " << evTime << std::endl;
+
+ QCanvasItemList itemList = canvas()->collisions(p);
+ QCanvasItemList::Iterator it;
+ MatrixElement* mel = 0;
+ QCanvasItem* activeItem = 0;
+
+ for (it = itemList.begin(); it != itemList.end(); ++it) {
+
+ QCanvasItem *item = *it;
+
+ QCanvasMatrixRectangle *mRect = 0;
+
+ if (item->active()) {
+ activeItem = item;
+ break;
+ }
+
+ if ((mRect = dynamic_cast<QCanvasMatrixRectangle*>(item))) {
+
+// std::cerr << "MatrixCanvasView: looking at element with rect " << mRect->rect().x() << "," << mRect->rect().y() << " (" << mRect->rect().width() << "x" << mRect->rect().height() << ")" << std::endl;
+
+// std::cerr << "MatrixCanvasView: point is " << p.x() << "," << p.y()<< std::endl;
+
+ QRect rect = mRect->rect();
+ if (dynamic_cast<QCanvasMatrixDiamond*>(mRect)) {
+ rect = QRect(rect.x() - rect.height()/2,
+ rect.y(),
+ rect.width(),
+ rect.height());
+ }
+
+// std::cerr << "MatrixCanvasView: adjusted rect " << rect.x() << "," << rect.y() << " (" << rect.width() << "x" << rect.height() << ")" << std::endl;
+
+ // QCanvas::collisions() can be a bit optimistic and report
+ // items which are close to the point but not actually under it.
+ // So a little sanity check helps.
+ if (!rect.contains(p, true)) continue;
+
+ mel = &(mRect->getMatrixElement());
+// std::cerr << "MatrixCanvasView::contentsMousePressEvent: collision with an existing matrix element" << std::endl;
+ break;
+ }
+ }
+
+ if (activeItem) { // active item takes precedence over notation elements
+ emit activeItemPressed(e, activeItem);
+ m_mouseWasPressed = true;
+ return ;
+ }
+
+ emit mousePressed(evTime, evPitch, e, mel);
+ m_mouseWasPressed = true;
+
+ // Ignore click if it was above the staff and not
+ // on an active item
+ //
+ if (!m_staff.containsCanvasCoords(p.x(), p.y()) && !activeItem)
+ m_ignoreClick = true;
+}
+
+void MatrixCanvasView::contentsMouseMoveEvent(QMouseEvent* e)
+{
+ QPoint p = inverseMapPoint(e->pos());
+ /*
+ if (m_snapGrid->getSnapTime(double(p.x())))
+ m_lastSnap = m_snapGrid->getSnapTime(double(p.x()));
+ */
+ updateGridSnap(e);
+
+ if (m_ignoreClick)
+ return ;
+
+ timeT evTime;
+ if (m_drumMode) {
+ evTime = m_snapGrid->snapX(p.x(), SnapGrid::SnapEither);
+ } else {
+ evTime = m_snapGrid->snapX(p.x(), SnapGrid::SnapLeft);
+ }
+
+ int evPitch = m_staff.getHeightAtCanvasCoords(p.x(), p.y());
+
+ timeT emTime = m_staff.getSegment().getEndMarkerTime();
+ if (evTime > emTime)
+ evTime = emTime;
+
+ timeT stTime = m_staff.getSegment().getStartTime();
+ if (evTime < stTime)
+ evTime = stTime;
+
+ if (evTime != m_previousEvTime) {
+ emit hoveredOverAbsoluteTimeChanged(evTime);
+ m_previousEvTime = evTime;
+ }
+
+ QCanvasItemList itemList = canvas()->collisions(p);
+ MatrixElement* mel = 0;
+
+ for (QCanvasItemList::iterator it = itemList.begin();
+ it != itemList.end(); ++it) {
+
+ QCanvasItem *item = *it;
+ QCanvasMatrixRectangle *mRect = 0;
+
+ if ((mRect = dynamic_cast<QCanvasMatrixRectangle*>(item))) {
+ if (!mRect->rect().contains(p, true))
+ continue;
+ mel = &(mRect->getMatrixElement());
+ MATRIX_DEBUG << "have element" << endl;
+ break;
+ }
+ }
+
+ if (!m_mouseWasPressed && // if mouse pressed, leave this to the tool
+ (evPitch != m_previousEvPitch || mel)) {
+ MidiPitchLabel label(evPitch);
+ if (mel) {
+ emit hoveredOverNoteChanged(evPitch, true,
+ mel->event()->getAbsoluteTime());
+ } else {
+ emit hoveredOverNoteChanged(evPitch, false, 0);
+ }
+ m_previousEvPitch = evPitch;
+ }
+
+// if (m_mouseWasPressed)
+ emit mouseMoved(evTime, evPitch, e);
+
+}
+
+void MatrixCanvasView::contentsMouseDoubleClickEvent (QMouseEvent* e)
+{
+ QPoint p = inverseMapPoint(e->pos());
+
+ if (!m_staff.containsCanvasCoords(p.x(), p.y())) {
+ m_ignoreClick = true;
+ return ;
+ }
+
+ contentsMousePressEvent(e);
+}
+
+void MatrixCanvasView::contentsMouseReleaseEvent(QMouseEvent* e)
+{
+ QPoint p = inverseMapPoint(e->pos());
+
+ if (m_ignoreClick) {
+ m_ignoreClick = false;
+ return ;
+ }
+
+ timeT evTime;
+ if (m_drumMode) {
+ evTime = m_snapGrid->snapX(p.x(), SnapGrid::SnapEither);
+ } else {
+ evTime = m_snapGrid->snapX(p.x(), SnapGrid::SnapLeft);
+ }
+
+ int evPitch = m_staff.getHeightAtCanvasCoords(p.x(), p.y());
+
+ timeT emTime = m_staff.getSegment().getEndMarkerTime();
+ if (evTime > emTime)
+ evTime = emTime;
+
+ emit mouseReleased(evTime, evPitch, e);
+ m_mouseWasPressed = false;
+}
+
+void MatrixCanvasView::slotExternalWheelEvent(QWheelEvent* e)
+{
+ wheelEvent(e);
+}
+
+void MatrixCanvasView::updateGridSnap(QMouseEvent *e)
+{
+ Qt::ButtonState bs = e->state();
+
+ // MATRIX_DEBUG << "MatrixCanvasView::updateGridSnap : bs = "
+ // << bs << " - sm = " << getSmoothModifier() << ", is temporary " << m_isSnapTemporary << ", saved is " << m_lastSnap << endl;
+
+ if (bs & getSmoothModifier()) {
+
+ if (!m_isSnapTemporary) {
+ m_lastSnap = m_snapGrid->getSnapSetting();
+ }
+ m_snapGrid->setSnapTime(SnapGrid::NoSnap);
+ m_isSnapTemporary = true;
+
+ } else if (m_isSnapTemporary) {
+
+ m_snapGrid->setSnapTime(m_lastSnap);
+ m_isSnapTemporary = false;
+ }
+}
+
+void MatrixCanvasView::enterEvent(QEvent *e)
+{
+ emit mouseEntered();
+}
+
+void MatrixCanvasView::leaveEvent(QEvent *e)
+{
+ emit mouseLeft();
+}
+
+}
+#include "MatrixCanvasView.moc"
diff --git a/src/gui/editors/matrix/MatrixCanvasView.h b/src/gui/editors/matrix/MatrixCanvasView.h
new file mode 100644
index 0000000..2ec4c7e
--- /dev/null
+++ b/src/gui/editors/matrix/MatrixCanvasView.h
@@ -0,0 +1,162 @@
+
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
+
+/*
+ Rosegarden
+ A MIDI and audio sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "MatrixElement.h"
+#include "misc/Debug.h"
+
+#include "base/Event.h"
+#include "base/NotationTypes.h"
+#include "base/ViewElement.h"
+#include "gui/general/GUIPalette.h"
+#include "QCanvasMatrixDiamond.h"
+#include "QCanvasMatrixRectangle.h"
+#include <qbrush.h>
+#include <qcanvas.h>
+#include <qcolor.h>
+
+
+namespace Rosegarden
+{
+
+MatrixElement::MatrixElement(Event *event, bool drum) :
+ ViewElement(event),
+ m_canvasRect(drum ?
+ new QCanvasMatrixDiamond(*this, 0) :
+ new QCanvasMatrixRectangle(*this, 0)),
+ m_overlapRectangles(NULL)
+{
+ // MATRIX_DEBUG << "new MatrixElement "
+ // << this << " wrapping " << event << endl;
+}
+
+MatrixElement::~MatrixElement()
+{
+ // MATRIX_DEBUG << "MatrixElement " << this << "::~MatrixElement() wrapping "
+ // << event() << endl;
+
+ m_canvasRect->hide();
+ delete m_canvasRect;
+
+ removeOverlapRectangles();
+}
+
+void MatrixElement::setCanvas(QCanvas* c)
+{
+ if (!m_canvasRect->canvas()) {
+
+ m_canvasRect->setCanvas(c);
+
+ // We set this by velocity now (matrixstaff.cpp)
+ //
+ //m_canvasRect->setBrush(RosegardenGUIColours::MatrixElementBlock);
+
+ m_canvasRect->setPen(GUIPalette::getColour(GUIPalette::MatrixElementBorder));
+ m_canvasRect->show();
+ }
+}
+
+bool MatrixElement::isNote() const
+{
+ return event()->isa(Note::EventType);
+}
+
+void MatrixElement::drawOverlapRectangles()
+{
+ if (m_overlapRectangles) removeOverlapRectangles();
+
+ QRect elRect = m_canvasRect->rect();
+ QCanvasItemList
+ itemList = m_canvasRect->canvas()->collisions(elRect);
+ QCanvasItemList::Iterator it;
+ MatrixElement* mel = 0;
+
+
+ for (it = itemList.begin(); it != itemList.end(); ++it) {
+
+ QCanvasMatrixRectangle *mRect = 0;
+ if ((mRect = dynamic_cast<QCanvasMatrixRectangle*>(*it))) {
+
+ // Element does'nt collide with itself
+ if (mRect == m_canvasRect) continue;
+
+ QRect rect = mRect->rect() & elRect;
+ if (!rect.isEmpty()) {
+ if (!m_overlapRectangles) {
+ m_overlapRectangles = new OverlapRectangles();
+ }
+
+ QCanvasRectangle *
+ overlap = new QCanvasRectangle(rect, m_canvasRect->canvas());
+ overlap->setBrush(GUIPalette::getColour(GUIPalette::MatrixOverlapBlock));
+ overlap->setZ(getCanvasZ() + 1);
+ overlap->show();
+ m_overlapRectangles->push_back(overlap);
+ }
+ }
+ }
+}
+
+void MatrixElement::redrawOverlaps(QRect rect)
+{
+ QCanvasItemList
+ itemList = m_canvasRect->canvas()->collisions(rect);
+ QCanvasItemList::Iterator it;
+ MatrixElement* mel = 0;
+
+ for (it = itemList.begin(); it != itemList.end(); ++it) {
+ QCanvasMatrixRectangle *mRect = 0;
+ if ((mRect = dynamic_cast<QCanvasMatrixRectangle*>(*it))) {
+ mRect->getMatrixElement().drawOverlapRectangles();
+ }
+ }
+}
+
+void MatrixElement::removeOverlapRectangles()
+{
+ if (!m_overlapRectangles) return;
+
+ OverlapRectangles::iterator it;
+ for (it = m_overlapRectangles->begin(); it != m_overlapRectangles->end(); ++it) {
+ (*it)->hide();
+ delete *it;
+ }
+
+ delete m_overlapRectangles;
+ m_overlapRectangles = NULL;
+}
+
+bool MatrixElement::getVisibleRectangle(QRect &rectangle)
+{
+ if (m_canvasRect && m_canvasRect->isVisible()) {
+ rectangle = m_canvasRect->rect();
+ return true;
+ }
+ return false;
+}
+
+
+}
diff --git a/src/gui/editors/matrix/MatrixElement.h b/src/gui/editors/matrix/MatrixElement.h
new file mode 100644
index 0000000..d330991
--- /dev/null
+++ b/src/gui/editors/matrix/MatrixElement.h
@@ -0,0 +1,138 @@
+
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
+
+/*
+ Rosegarden
+ A MIDI and audio sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_MATRIXELEMENT_H_
+#define _RG_MATRIXELEMENT_H_
+
+#include "base/ViewElement.h"
+#include <qbrush.h>
+#include <qcanvas.h>
+#include "QCanvasMatrixRectangle.h"
+
+class QColor;
+
+
+namespace Rosegarden
+{
+
+class Event;
+
+class MatrixElement : public ViewElement
+{
+
+ typedef std::vector <QCanvasRectangle *> OverlapRectangles;
+
+
+public:
+ MatrixElement(Event *event, bool drum);
+
+ virtual ~MatrixElement();
+
+ void setCanvas(QCanvas* c);
+
+ /**
+ * Returns the actual x coordinate of the element on the canvas
+ */
+ double getCanvasX() const { return m_canvasRect->x(); }
+
+ /**
+ * Returns the actual y coordinate of the element on the canvas
+ */
+ double getCanvasY() const { return m_canvasRect->y(); }
+
+ double getCanvasZ() const { return m_canvasRect->z(); }
+
+ /**
+ * Sets the x coordinate of the element on the canvas
+ */
+ void setCanvasX(double x) { m_canvasRect->setX(x); }
+
+ /**
+ * Sets the y coordinate of the element on the canvas
+ */
+ void setCanvasY(double y) { m_canvasRect->setY(y); }
+
+ void setCanvasZ(double z) { m_canvasRect->setZ(z); }
+
+ /**
+ * Sets the width of the rectangle on the canvas
+ */
+ void setWidth(int w) { m_canvasRect->setSize(w, m_canvasRect->height()); }
+ int getWidth() { return m_canvasRect->width(); }
+
+ /**
+ * Sets the height of the rectangle on the canvas
+ */
+ void setHeight(int h) { m_canvasRect->setSize(m_canvasRect->width(), h); }
+ int getHeight() { return m_canvasRect->height(); }
+
+ /// Returns true if the wrapped event is a note
+ bool isNote() const;
+
+ /*
+ * Set the colour of the element
+ */
+ void setColour(const QColor &colour)
+ { m_canvasRect->setBrush(QBrush(colour)); }
+
+ /**
+ * Draws overlap rectangles (if any)
+ * (should not be called in drum mode)
+ */
+ void drawOverlapRectangles();
+
+ /**
+ * Removes overlap rectangles if any
+ */
+ void removeOverlapRectangles();
+
+ /**
+ * If element rectangle is currently visible gets its size and returns true.
+ * Returns false if element rectangle is undefined or not visible.
+ */
+ bool getVisibleRectangle(QRect &rectangle);
+
+ /**
+ * Redraw overlap rectangles of all matrix elements colliding with rect
+ */
+ void redrawOverlaps(QRect rect);
+
+protected:
+
+ //--------------- Data members ---------------------------------
+
+ QCanvasMatrixRectangle *m_canvasRect;
+
+ OverlapRectangles *m_overlapRectangles;
+
+};
+
+
+typedef ViewElementList MatrixElementList;
+
+
+}
+
+#endif
diff --git a/src/gui/editors/matrix/MatrixEraser.cpp b/src/gui/editors/matrix/MatrixEraser.cpp
new file mode 100644
index 0000000..6c2373e
--- /dev/null
+++ b/src/gui/editors/matrix/MatrixEraser.cpp
@@ -0,0 +1,110 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
+
+/*
+ Rosegarden
+ A MIDI and audio sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "MatrixEraser.h"
+#include "misc/Debug.h"
+
+#include <klocale.h>
+#include <kstddirs.h>
+#include "base/ViewElement.h"
+#include "commands/matrix/MatrixEraseCommand.h"
+#include "gui/general/EditTool.h"
+#include "MatrixStaff.h"
+#include "MatrixTool.h"
+#include "MatrixView.h"
+#include <kaction.h>
+#include <kglobal.h>
+#include <qiconset.h>
+#include <qstring.h>
+
+
+namespace Rosegarden
+{
+
+MatrixEraser::MatrixEraser(MatrixView* parent)
+ : MatrixTool("MatrixEraser", parent),
+ m_currentStaff(0)
+{
+ QString pixmapDir = KGlobal::dirs()->findResource("appdata", "pixmaps/");
+ QCanvasPixmap pixmap(pixmapDir + "/toolbar/select.xpm");
+ QIconSet icon = QIconSet(pixmap);
+
+ new KAction(i18n("Switch to Select Tool"), icon, 0, this,
+ SLOT(slotSelectSelected()), actionCollection(),
+ "select");
+
+ new KAction(i18n("Switch to Draw Tool"), "pencil", 0, this,
+ SLOT(slotDrawSelected()), actionCollection(),
+ "draw");
+
+ new KAction(i18n("Switch to Move Tool"), "move", 0, this,
+ SLOT(slotMoveSelected()), actionCollection(),
+ "move");
+
+ pixmap.load(pixmapDir + "/toolbar/resize.xpm");
+ icon = QIconSet(pixmap);
+ new KAction(i18n("Switch to Resize Tool"), icon, 0, this,
+ SLOT(slotResizeSelected()), actionCollection(),
+ "resize");
+
+ createMenu("matrixeraser.rc");
+}
+
+void MatrixEraser::handleLeftButtonPress(timeT,
+ int,
+ int staffNo,
+ QMouseEvent*,
+ ViewElement* el)
+{
+ MATRIX_DEBUG << "MatrixEraser::handleLeftButtonPress : el = "
+ << el << endl;
+
+ if (!el)
+ return ; // nothing to erase
+
+ m_currentStaff = m_mParentView->getStaff(staffNo);
+
+ MatrixEraseCommand* command =
+ new MatrixEraseCommand(m_currentStaff->getSegment(), el->event());
+
+ m_mParentView->addCommandToHistory(command);
+
+ m_mParentView->update();
+}
+
+void MatrixEraser::ready()
+{
+ m_mParentView->setCanvasCursor(Qt::pointingHandCursor);
+ setBasicContextHelp();
+}
+
+void MatrixEraser::setBasicContextHelp()
+{
+ setContextHelp(i18n("Click on a note to delete it"));
+}
+
+const QString MatrixEraser::ToolName = "eraser";
+
+}
diff --git a/src/gui/editors/matrix/MatrixEraser.h b/src/gui/editors/matrix/MatrixEraser.h
new file mode 100644
index 0000000..4e3d65f
--- /dev/null
+++ b/src/gui/editors/matrix/MatrixEraser.h
@@ -0,0 +1,69 @@
+
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
+
+/*
+ Rosegarden
+ A MIDI and audio sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_MATRIXERASER_H_
+#define _RG_MATRIXERASER_H_
+
+#include "MatrixTool.h"
+#include <qstring.h>
+
+class QMouseEvent;
+
+
+namespace Rosegarden
+{
+
+class ViewElement;
+class MatrixView;
+class MatrixStaff;
+
+
+class MatrixEraser : public MatrixTool
+{
+ friend class MatrixToolBox;
+
+public:
+
+ virtual void handleLeftButtonPress(timeT,
+ int height,
+ int staffNo,
+ QMouseEvent *event,
+ ViewElement*);
+
+ static const QString ToolName;
+
+ virtual void ready();
+
+protected:
+ MatrixEraser(MatrixView*);
+
+ void setBasicContextHelp();
+
+ MatrixStaff* m_currentStaff;
+};
+
+}
+
+#endif
diff --git a/src/gui/editors/matrix/MatrixHLayout.cpp b/src/gui/editors/matrix/MatrixHLayout.cpp
new file mode 100644
index 0000000..99b89c2
--- /dev/null
+++ b/src/gui/editors/matrix/MatrixHLayout.cpp
@@ -0,0 +1,220 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
+
+/*
+ Rosegarden
+ A MIDI and audio sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "MatrixHLayout.h"
+#include "MatrixElement.h"
+#include "misc/Debug.h"
+
+#include "base/Composition.h"
+#include "base/LayoutEngine.h"
+#include "base/NotationTypes.h"
+#include "base/Profiler.h"
+#include "base/Segment.h"
+#include "base/Staff.h"
+#include "MatrixStaff.h"
+
+#include <cmath>
+
+
+namespace Rosegarden
+{
+
+MatrixHLayout::MatrixHLayout(Composition *c) :
+ HorizontalLayoutEngine(c),
+ m_totalWidth(0.0),
+ m_firstBar(0)
+{}
+
+MatrixHLayout::~MatrixHLayout()
+{}
+
+void MatrixHLayout::reset()
+{}
+
+void MatrixHLayout::resetStaff(Staff&, timeT, timeT)
+{}
+
+void MatrixHLayout::scanStaff(Staff &staffBase,
+ timeT startTime, timeT endTime)
+{
+ Profiler profiler("MatrixHLayout::scanStaff", true);
+
+ // The Matrix layout is not currently designed to be able to lay
+ // out more than one staff, because we have no requirement to show
+ // more than one at once in the Matrix view. To make it work for
+ // multiple staffs should be straightforward; we just need to bear
+ // in mind that they might start and end at different times (hence
+ // the total width and bar list can't just be calculated from the
+ // last staff scanned as they are now).
+
+ MatrixStaff &staff = static_cast<MatrixStaff &>(staffBase);
+ bool isFullScan = (startTime == endTime);
+
+ MatrixElementList *notes = staff.getViewElementList();
+ MatrixElementList::iterator startItr = notes->begin();
+ MatrixElementList::iterator endItr = notes->end();
+
+ if (!isFullScan) {
+ startItr = notes->findNearestTime(startTime);
+ if (startItr == notes->end())
+ startItr = notes->begin();
+ endItr = notes->findTime(endTime);
+ }
+
+ if (endItr == notes->end() && startItr == notes->begin()) {
+ isFullScan = true;
+ }
+
+ // Do this in two parts: bar lines separately from elements.
+ // (We don't need to do all that stuff notationhlayout has to do,
+ // scanning the notes bar-by-bar; we can just place the bar lines
+ // in the theoretically-correct places and do the same with the
+ // notes quite independently.)
+
+ Segment &segment = staff.getSegment();
+ Composition *composition = segment.getComposition();
+ m_firstBar = composition->getBarNumber(segment.getStartTime());
+ timeT from = composition->getBarStart(m_firstBar),
+ to = composition->getBarEndForTime(segment.getEndMarkerTime());
+
+ double startPosition = from;
+
+ // 1. Bar lines and time signatures. We only re-make these on
+ // full scans.
+
+ if (isFullScan || m_barData.size() == 0) {
+
+ m_barData.clear();
+ int barNo = m_firstBar;
+
+ MATRIX_DEBUG << "MatrixHLayout::scanStaff() : start time = " << startTime << ", first bar = " << m_firstBar << ", end time = " << endTime << ", end marker time = " << segment.getEndMarkerTime() << ", from = " << from << ", to = " << to << endl;
+
+ // hack for partial bars
+ //
+ timeT adjTo = to;
+
+ if (composition->getBarStartForTime(segment.getEndMarkerTime())
+ != segment.getEndMarkerTime())
+ adjTo++;
+
+ while (from < adjTo) {
+
+ bool isNew = false;
+ TimeSignature timeSig =
+ composition->getTimeSignatureInBar(barNo, isNew);
+
+ if (isNew || barNo == m_firstBar) {
+ m_barData.push_back(BarData((from - startPosition) *
+ staff.getTimeScaleFactor(),
+ TimeSigData(true, timeSig)));
+ } else {
+ m_barData.push_back(BarData((from - startPosition) *
+ staff.getTimeScaleFactor(),
+ TimeSigData(false, timeSig)));
+ }
+
+ from = composition->getBarEndForTime(from);
+ ++barNo;
+ }
+
+ m_barData.push_back(BarData(to * staff.getTimeScaleFactor(),
+ TimeSigData(false, TimeSignature())));
+ }
+
+ // 2. Elements
+
+ m_totalWidth = 0.0;
+ MatrixElementList::iterator i = startItr;
+
+ while (i != endItr) {
+
+ (*i)->setLayoutX(((*i)->getViewAbsoluteTime() - startPosition)
+ * staff.getTimeScaleFactor());
+
+ double width = (*i)->getViewDuration() * staff.getTimeScaleFactor();
+
+ // Make sure that very small elements can still be seen
+ //
+ if (width < 3) width = 3;
+ else width += 1; // fiddle factor
+
+ static_cast<MatrixElement*>((*i))->setWidth(lrint(width));
+
+ if (isFullScan) {
+ m_totalWidth = (*i)->getLayoutX() + width;
+ } else {
+ m_totalWidth = std::max(m_totalWidth, (*i)->getLayoutX() + width);
+ }
+
+ ++i;
+ }
+}
+
+double MatrixHLayout::getTotalWidth() const
+{
+ return m_totalWidth;
+}
+
+int MatrixHLayout::getFirstVisibleBar() const
+{
+ return m_firstBar;
+}
+
+int MatrixHLayout::getLastVisibleBar() const
+{
+ int barNo = m_firstBar + m_barData.size() - 2;
+ if (barNo < m_firstBar + 1)
+ barNo = m_firstBar + 1;
+
+ return barNo;
+}
+
+double MatrixHLayout::getBarPosition(int barNo) const
+{
+ if (barNo < getFirstVisibleBar()) {
+ return getBarPosition(getFirstVisibleBar());
+ }
+
+ if (barNo > getLastVisibleBar()) {
+ return getBarPosition(getLastVisibleBar());
+ }
+
+ return m_barData[barNo - m_firstBar].first;
+}
+
+bool MatrixHLayout::getTimeSignaturePosition(Staff &,
+ int barNo,
+ TimeSignature &timeSig,
+ double &timeSigX)
+{
+ timeSig = m_barData[barNo - m_firstBar].second.second;
+ timeSigX = m_barData[barNo - m_firstBar].first;
+ return m_barData[barNo - m_firstBar].second.first;
+}
+
+void MatrixHLayout::finishLayout(timeT, timeT)
+{}
+
+}
diff --git a/src/gui/editors/matrix/MatrixHLayout.h b/src/gui/editors/matrix/MatrixHLayout.h
new file mode 100644
index 0000000..76f1b31
--- /dev/null
+++ b/src/gui/editors/matrix/MatrixHLayout.h
@@ -0,0 +1,150 @@
+
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
+
+/*
+ Rosegarden
+ A MIDI and audio sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_MATRIXHLAYOUT_H_
+#define _RG_MATRIXHLAYOUT_H_
+
+#include "base/FastVector.h"
+#include "base/LayoutEngine.h"
+#include <utility>
+#include "base/Event.h"
+
+#include "gui/general/HZoomable.h"
+
+
+
+namespace Rosegarden
+{
+
+class TimeSignature;
+class Staff;
+class Composition;
+
+
+class MatrixHLayout : public HorizontalLayoutEngine
+{
+public:
+ MatrixHLayout(Composition *c);
+ virtual ~MatrixHLayout();
+
+ /**
+ * Resets internal data stores for all staffs
+ */
+ virtual void reset();
+
+ /**
+ * Resets internal data stores for a specific staff
+ */
+ virtual void resetStaff(Staff &staff,
+ timeT = 0,
+ timeT = 0);
+
+ /**
+ * Returns the total length of all elements once layout is done.
+ * This is the x-coord of the end of the last element on the
+ * longest staff
+ */
+ virtual double getTotalWidth() const;
+
+ /**
+ * Returns the number of the first visible bar line
+ */
+ virtual int getFirstVisibleBar() const;
+
+ /**
+ * Returns the number of the first visible bar line
+ */
+ virtual int getLastVisibleBar() const;
+
+ /**
+ * Returns the x-coordinate of the given bar number
+ */
+ virtual double getBarPosition(int barNo) const;
+
+ /**
+ * Precomputes layout data for a single staff, updating any
+ * internal data stores associated with that staff and updating
+ * any layout-related properties in the events on the staff's
+ * segment.
+ */
+ virtual void scanStaff(Staff&,
+ timeT = 0,
+ timeT = 0);
+
+ /**
+ * Computes any layout data that may depend on the results of
+ * scanning more than one staff. This may mean doing most of
+ * the layout (likely for horizontal layout) or nothing at all
+ * (likely for vertical layout).
+ */
+ virtual void finishLayout(timeT = 0,
+ timeT = 0);
+
+ /**
+ * Returns true if there is a new time signature in the given bar,
+ * setting timeSignature appropriately and setting timeSigX to its
+ * x-coord
+ */
+ virtual bool getTimeSignaturePosition(Staff &staff,
+ int barNo,
+ TimeSignature &timeSig,
+ double &timeSigX);
+
+protected:
+
+ //--------------- Data members ---------------------------------
+
+ // pair of has-time-sig and time-sig
+ typedef std::pair<bool, TimeSignature> TimeSigData;
+ // pair of layout-x and time-signature if there is one
+ typedef std::pair<double, TimeSigData> BarData;
+ typedef FastVector<BarData> BarDataList;
+ BarDataList m_barData;
+ double m_totalWidth;
+ int m_firstBar;
+};
+
+/**
+ * "zoomable" version of the above, used in the MatrixView
+ * to properly scale Tempo and Chord rulers.
+ *
+ */
+class ZoomableMatrixHLayoutRulerScale : public RulerScale, public HZoomable {
+public:
+ ZoomableMatrixHLayoutRulerScale(MatrixHLayout& layout) : RulerScale(layout.getComposition()), m_referenceHLayout(layout) {};
+
+ virtual double getBarPosition(int n) const { return m_referenceHLayout.getBarPosition(n) * getHScaleFactor(); }
+ virtual double getXForTime(timeT time) const { return m_referenceHLayout.getXForTime(time) * getHScaleFactor(); }
+ virtual timeT getTimeForX(double x) const { return m_referenceHLayout.getTimeForX(x / getHScaleFactor()); }
+ virtual double getBarWidth(int n) const { return m_referenceHLayout.getBarWidth(n) * getHScaleFactor(); }
+ virtual int getLastVisibleBar() const { return m_referenceHLayout.getLastVisibleBar(); }
+
+protected:
+ MatrixHLayout& m_referenceHLayout;
+};
+
+}
+
+#endif
diff --git a/src/gui/editors/matrix/MatrixMover.cpp b/src/gui/editors/matrix/MatrixMover.cpp
new file mode 100644
index 0000000..d725f16
--- /dev/null
+++ b/src/gui/editors/matrix/MatrixMover.cpp
@@ -0,0 +1,481 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
+
+/*
+ Rosegarden
+ A MIDI and audio sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "MatrixMover.h"
+
+#include "base/BaseProperties.h"
+#include <klocale.h>
+#include <kstddirs.h>
+#include "base/Event.h"
+#include "base/Segment.h"
+#include "base/Selection.h"
+#include "base/SnapGrid.h"
+#include "base/ViewElement.h"
+#include "commands/matrix/MatrixModifyCommand.h"
+#include "commands/matrix/MatrixInsertionCommand.h"
+#include "commands/notation/NormalizeRestsCommand.h"
+#include "gui/general/EditTool.h"
+#include "gui/general/RosegardenCanvasView.h"
+#include "MatrixElement.h"
+#include "MatrixStaff.h"
+#include "MatrixTool.h"
+#include "MatrixView.h"
+#include "MatrixVLayout.h"
+#include <kaction.h>
+#include <kglobal.h>
+#include <qiconset.h>
+#include <qpoint.h>
+#include <qstring.h>
+#include "misc/Debug.h"
+
+
+namespace Rosegarden
+{
+
+MatrixMover::MatrixMover(MatrixView* parent) :
+ MatrixTool("MatrixMover", parent),
+ m_currentElement(0),
+ m_currentStaff(0),
+ m_lastPlayedPitch(-1)
+{
+ QString pixmapDir = KGlobal::dirs()->findResource("appdata", "pixmaps/");
+ QCanvasPixmap pixmap(pixmapDir + "/toolbar/select.xpm");
+ QIconSet icon = QIconSet(pixmap);
+
+ new KAction(i18n("Switch to Select Tool"), icon, 0, this,
+ SLOT(slotSelectSelected()), actionCollection(),
+ "select");
+
+ new KAction(i18n("Switch to Draw Tool"), "pencil", 0, this,
+ SLOT(slotDrawSelected()), actionCollection(),
+ "draw");
+
+ new KAction(i18n("Switch to Erase Tool"), "eraser", 0, this,
+ SLOT(slotEraseSelected()), actionCollection(),
+ "erase");
+
+ pixmap.load(pixmapDir + "/toolbar/resize.xpm");
+ icon = QIconSet(pixmap);
+ new KAction(i18n("Switch to Resize Tool"), icon, 0, this,
+ SLOT(slotResizeSelected()), actionCollection(),
+ "resize");
+
+ createMenu("matrixmover.rc");
+}
+
+void MatrixMover::handleEventRemoved(Event *event)
+{
+ if (m_currentElement && m_currentElement->event() == event) {
+ m_currentElement = 0;
+ }
+}
+
+void MatrixMover::handleLeftButtonPress(timeT time,
+ int pitch,
+ int staffNo,
+ QMouseEvent* e,
+ ViewElement* el)
+{
+ MATRIX_DEBUG << "MatrixMover::handleLeftButtonPress() : time = " << time << ", el = " << el << endl;
+ if (!el) return;
+
+ m_quickCopy = (e->state() & Qt::ControlButton);
+
+ if (!m_duplicateElements.empty()) {
+ for (size_t i = 0; i < m_duplicateElements.size(); ++i) {
+ delete m_duplicateElements[i]->event();
+ delete m_duplicateElements[i];
+ }
+ m_duplicateElements.clear();
+ }
+
+ m_currentElement = dynamic_cast<MatrixElement*>(el);
+ m_currentStaff = m_mParentView->getStaff(staffNo);
+
+ if (m_currentElement) {
+
+ // Add this element and allow movement
+ //
+ EventSelection* selection = m_mParentView->getCurrentSelection();
+
+ if (selection) {
+ EventSelection *newSelection;
+
+ if ((e->state() & Qt::ShiftButton) ||
+ selection->contains(m_currentElement->event()))
+ newSelection = new EventSelection(*selection);
+ else
+ newSelection = new EventSelection(m_currentStaff->getSegment());
+
+ // if the selection already contains the event, remove it from the
+ // selection if shift is pressed
+ if (selection->contains(m_currentElement->event())){
+ if (e->state() & Qt::ShiftButton)
+ newSelection->removeEvent(m_currentElement->event());
+ } else {
+ newSelection->addEvent(m_currentElement->event());
+ }
+ m_mParentView->setCurrentSelection(newSelection, true, true);
+ m_mParentView->canvas()->update();
+ selection = newSelection;
+ } else {
+ m_mParentView->setSingleSelectedEvent(m_currentStaff->getSegment(),
+ m_currentElement->event(),
+ true);
+ m_mParentView->canvas()->update();
+ }
+
+ long velocity = m_mParentView->getCurrentVelocity();
+ m_currentElement->event()->get<Int>(BaseProperties::VELOCITY, velocity);
+ m_mParentView->playNote(m_currentStaff->getSegment(), pitch, velocity);
+ m_lastPlayedPitch = pitch;
+
+ if (m_quickCopy && selection) {
+ for (EventSelection::eventcontainer::iterator i =
+ selection->getSegmentEvents().begin();
+ i != selection->getSegmentEvents().end(); ++i) {
+
+ MatrixElement *element = m_currentStaff->getElement(*i);
+ if (!element) continue;
+
+ MatrixElement *duplicate = new MatrixElement
+ (new Event(**i), m_mParentView->isDrumMode());
+ duplicate->setLayoutY(element->getLayoutY());
+ duplicate->setLayoutX(element->getLayoutX());
+ duplicate->setWidth(element->getWidth());
+ duplicate->setHeight(element->getHeight());
+ duplicate->setCanvasZ(-1);
+ m_currentStaff->positionElement(duplicate);
+ m_duplicateElements.push_back(duplicate);
+ }
+ }
+ }
+
+ m_clickX = m_mParentView->inverseMapPoint(e->pos()).x();
+}
+
+timeT
+MatrixMover::getDragTime(QMouseEvent *e, timeT candidate)
+{
+ int x = m_mParentView->inverseMapPoint(e->pos()).x();
+ int xdiff = x - m_clickX;
+
+ const SnapGrid &grid = getSnapGrid();
+ const RulerScale &scale = *grid.getRulerScale();
+
+ timeT eventTime = m_currentElement->getViewAbsoluteTime();
+ int eventX = scale.getXForTime(eventTime);
+ timeT preSnapTarget = scale.getTimeForX(eventX + xdiff);
+
+ candidate = grid.snapTime(preSnapTarget, SnapGrid::SnapEither);
+
+ if (xdiff == 0 ||
+ (abs(eventTime - preSnapTarget) < abs(candidate - preSnapTarget))) {
+ candidate = eventTime;
+ }
+
+ return candidate;
+}
+
+int MatrixMover::handleMouseMove(timeT newTime,
+ int newPitch,
+ QMouseEvent *e)
+{
+ MATRIX_DEBUG << "MatrixMover::handleMouseMove() time = "
+ << newTime << endl;
+
+ if (e) {
+ setBasicContextHelp(e->state() & Qt::ControlButton);
+ }
+
+ if (!m_currentElement || !m_currentStaff)
+ return RosegardenCanvasView::NoFollow;
+
+ if (getSnapGrid().getSnapSetting() != SnapGrid::NoSnap) {
+ setContextHelp(i18n("Hold Shift to avoid snapping to beat grid"));
+ } else {
+ clearContextHelp();
+ }
+
+ if (e) newTime = getDragTime(e, newTime);
+
+ emit hoveredOverNoteChanged(newPitch, true, newTime);
+
+ using BaseProperties::PITCH;
+ int diffPitch = 0;
+ if (m_currentElement->event()->has(PITCH)) {
+ diffPitch = newPitch - m_currentElement->event()->get<Int>(PITCH);
+ }
+
+ int diffY =
+ int(((m_currentStaff->getLayoutYForHeight(newPitch) -
+ m_currentStaff->getElementHeight() / 2) -
+ m_currentElement->getLayoutY()));
+
+ EventSelection* selection = m_mParentView->getCurrentSelection();
+ EventSelection::eventcontainer::iterator it =
+ selection->getSegmentEvents().begin();
+
+ MatrixElement *element = 0;
+ int maxY = m_currentStaff->getCanvasYForHeight(0);
+
+ for (; it != selection->getSegmentEvents().end(); it++) {
+ element = m_currentStaff->getElement(*it);
+
+ if (element) {
+
+ timeT diffTime = element->getViewAbsoluteTime() -
+ m_currentElement->getViewAbsoluteTime();
+
+ int newX = getSnapGrid().getRulerScale()->
+ getXForTime(newTime + diffTime);
+
+ if (newX < 0) newX = 0;
+
+ int newY = int(element->getLayoutY() + diffY);
+
+ if (newY < 0) newY = 0;
+ if (newY > maxY) newY = maxY;
+
+ element->setLayoutX(newX);
+ element->setLayoutY(newY);
+
+ m_currentStaff->positionElement(element);
+ }
+ }
+
+ if (newPitch != m_lastPlayedPitch) {
+ long velocity = m_mParentView->getCurrentVelocity();
+ m_currentElement->event()->get<Int>(BaseProperties::VELOCITY, velocity);
+ m_mParentView->playNote(m_currentStaff->getSegment(), newPitch, velocity);
+ m_lastPlayedPitch = newPitch;
+ }
+
+ m_mParentView->canvas()->update();
+ return RosegardenCanvasView::FollowHorizontal |
+ RosegardenCanvasView::FollowVertical;
+}
+
+void MatrixMover::handleMouseRelease(timeT newTime,
+ int newPitch,
+ QMouseEvent *e)
+{
+ MATRIX_DEBUG << "MatrixMover::handleMouseRelease() - newPitch = "
+ << newPitch << endl;
+
+ if (!m_currentElement || !m_currentStaff)
+ return;
+
+ if (newPitch > MatrixVLayout::maxMIDIPitch)
+ newPitch = MatrixVLayout::maxMIDIPitch;
+ if (newPitch < 0)
+ newPitch = 0;
+
+ if (e) newTime = getDragTime(e, newTime);
+
+ using BaseProperties::PITCH;
+ timeT diffTime = newTime - m_currentElement->getViewAbsoluteTime();
+ int diffPitch = 0;
+ if (m_currentElement->event()->has(PITCH)) {
+ diffPitch = newPitch - m_currentElement->event()->get<Int>(PITCH);
+ }
+
+ EventSelection *selection = m_mParentView->getCurrentSelection();
+
+ if ((diffTime == 0 && diffPitch == 0) || selection->getAddedEvents() == 0) {
+ for (size_t i = 0; i < m_duplicateElements.size(); ++i) {
+ delete m_duplicateElements[i]->event();
+ delete m_duplicateElements[i];
+ }
+ m_duplicateElements.clear();
+ m_mParentView->canvas()->update();
+ m_currentElement = 0;
+ return;
+ }
+
+ if (newPitch != m_lastPlayedPitch) {
+ long velocity = m_mParentView->getCurrentVelocity();
+ m_currentElement->event()->get<Int>(BaseProperties::VELOCITY, velocity);
+ m_mParentView->playNote(m_currentStaff->getSegment(), newPitch, velocity);
+ m_lastPlayedPitch = newPitch;
+ }
+
+ QString commandLabel;
+ if (m_quickCopy) {
+ if (selection->getAddedEvents() < 2) {
+ commandLabel = i18n("Copy and Move Event");
+ } else {
+ commandLabel = i18n("Copy and Move Events");
+ }
+ } else {
+ if (selection->getAddedEvents() < 2) {
+ commandLabel = i18n("Move Event");
+ } else {
+ commandLabel = i18n("Move Events");
+ }
+ }
+
+ KMacroCommand *macro = new KMacroCommand(commandLabel);
+
+ EventSelection::eventcontainer::iterator it =
+ selection->getSegmentEvents().begin();
+
+ Segment &segment = m_currentStaff->getSegment();
+
+ EventSelection *newSelection = new EventSelection(segment);
+
+ timeT normalizeStart = selection->getStartTime();
+ timeT normalizeEnd = selection->getEndTime();
+
+ if (m_quickCopy) {
+ for (size_t i = 0; i < m_duplicateElements.size(); ++i) {
+ timeT time = m_duplicateElements[i]->getViewAbsoluteTime();
+ timeT endTime = time + m_duplicateElements[i]->getViewDuration();
+ if (time < normalizeStart) normalizeStart = time;
+ if (endTime > normalizeEnd) normalizeEnd = endTime;
+ macro->addCommand(new MatrixInsertionCommand
+ (segment, time, endTime,
+ m_duplicateElements[i]->event()));
+ delete m_duplicateElements[i]->event();
+ delete m_duplicateElements[i];
+ }
+ m_duplicateElements.clear();
+ m_quickCopy = false;
+ }
+
+ for (; it != selection->getSegmentEvents().end(); it++) {
+
+ timeT newTime = (*it)->getAbsoluteTime() + diffTime;
+
+ int newPitch = 60;
+ if ((*it)->has(PITCH)) {
+ newPitch = (*it)->get<Int>(PITCH) + diffPitch;
+ }
+
+ Event *newEvent = 0;
+
+ if (newTime < segment.getStartTime()) {
+ newTime = segment.getStartTime();
+ }
+
+ if (newTime + (*it)->getDuration() >= segment.getEndMarkerTime()) {
+ timeT limit = getSnapGrid().snapTime
+ (segment.getEndMarkerTime() - 1, SnapGrid::SnapLeft);
+ if (newTime > limit) newTime = limit;
+ timeT newDuration = std::min
+ ((*it)->getDuration(), segment.getEndMarkerTime() - newTime);
+ newEvent = new Event(**it, newTime, newDuration);
+ } else {
+ newEvent = new Event(**it, newTime);
+ }
+
+ newEvent->set<Int>(BaseProperties::PITCH, newPitch);
+
+ macro->addCommand(new MatrixModifyCommand(segment,
+ (*it),
+ newEvent,
+ true,
+ false));
+ newSelection->addEvent(newEvent);
+ }
+
+ normalizeStart = std::min(normalizeStart, newSelection->getStartTime());
+ normalizeEnd = std::max(normalizeEnd, newSelection->getEndTime());
+
+ macro->addCommand(new NormalizeRestsCommand(segment,
+ normalizeStart,
+ normalizeEnd));
+
+ m_mParentView->setCurrentSelection(0, false, false);
+ m_mParentView->addCommandToHistory(macro);
+ m_mParentView->setCurrentSelection(newSelection, false, false);
+
+ m_mParentView->canvas()->update();
+ m_currentElement = 0;
+
+ setBasicContextHelp();
+}
+
+void MatrixMover::ready()
+{
+ connect(m_parentView->getCanvasView(), SIGNAL(contentsMoving (int, int)),
+ this, SLOT(slotMatrixScrolled(int, int)));
+ connect(this, SIGNAL(hoveredOverNoteChanged(int, bool, timeT)),
+ m_mParentView, SLOT(slotHoveredOverNoteChanged(int, bool, timeT)));
+ m_mParentView->setCanvasCursor(Qt::sizeAllCursor);
+ setBasicContextHelp();
+}
+
+void MatrixMover::stow()
+{
+ disconnect(m_parentView->getCanvasView(), SIGNAL(contentsMoving (int, int)),
+ this, SLOT(slotMatrixScrolled(int, int)));
+ disconnect(this, SIGNAL(hoveredOverNoteChanged(int, bool, timeT)),
+ m_mParentView, SLOT(slotHoveredOverNoteChanged(int, bool, timeT)));
+}
+
+void MatrixMover::slotMatrixScrolled(int newX, int newY)
+{
+ if (!m_currentElement)
+ return ;
+
+ QPoint newP1(newX, newY), oldP1(m_parentView->getCanvasView()->contentsX(),
+ m_parentView->getCanvasView()->contentsY());
+
+ QPoint offset = newP1 - oldP1;
+
+ offset = m_mParentView->inverseMapPoint(offset);
+
+ QPoint p(m_currentElement->getCanvasX(), m_currentElement->getCanvasY());
+ p += offset;
+
+ timeT newTime = getSnapGrid().snapX(p.x());
+ int newPitch = m_currentStaff->getHeightAtCanvasCoords(p.x(), p.y());
+
+ handleMouseMove(newTime, newPitch, 0);
+}
+
+void MatrixMover::setBasicContextHelp(bool ctrlPressed)
+{
+ EventSelection *selection = m_mParentView->getCurrentSelection();
+ if (!selection || selection->getAddedEvents() < 2) {
+ if (!ctrlPressed) {
+ setContextHelp(i18n("Click and drag to move a note; hold Ctrl as well to copy it"));
+ } else {
+ setContextHelp(i18n("Click and drag to copy a note"));
+ }
+ } else {
+ if (!ctrlPressed) {
+ setContextHelp(i18n("Click and drag to move selected notes; hold Ctrl as well to copy"));
+ } else {
+ setContextHelp(i18n("Click and drag to copy selected notes"));
+ }
+ }
+}
+
+const QString MatrixMover::ToolName = "mover";
+
+}
+#include "MatrixMover.moc"
diff --git a/src/gui/editors/matrix/MatrixMover.h b/src/gui/editors/matrix/MatrixMover.h
new file mode 100644
index 0000000..ac95c5f
--- /dev/null
+++ b/src/gui/editors/matrix/MatrixMover.h
@@ -0,0 +1,112 @@
+
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
+
+/*
+ Rosegarden
+ A MIDI and audio sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_MATRIXMOVER_H_
+#define _RG_MATRIXMOVER_H_
+
+#include "MatrixTool.h"
+#include <qstring.h>
+#include "base/Event.h"
+
+
+class QMouseEvent;
+
+
+namespace Rosegarden
+{
+
+class ViewElement;
+class MatrixView;
+class MatrixStaff;
+class MatrixElement;
+class Event;
+
+
+class MatrixMover : public MatrixTool
+{
+ Q_OBJECT
+
+ friend class MatrixToolBox;
+
+public:
+ virtual void handleLeftButtonPress(timeT,
+ int height,
+ int staffNo,
+ QMouseEvent *event,
+ ViewElement*);
+
+ /**
+ * Set the duration of the element
+ */
+ virtual int handleMouseMove(timeT,
+ int height,
+ QMouseEvent*);
+
+ /**
+ * Actually insert the new element
+ */
+ virtual void handleMouseRelease(timeT,
+ int height,
+ QMouseEvent*);
+
+ static const QString ToolName;
+
+ /**
+ * Respond to an event being deleted -- it may be the one the tool
+ * is remembering as the current event.
+ */
+ virtual void handleEventRemoved(Event *event);
+
+ virtual void ready();
+ virtual void stow();
+
+signals:
+ void hoveredOverNoteChanged(int evPitch, bool haveEvent, timeT evTime);
+
+protected slots:
+ void slotMatrixScrolled(int x, int y);
+
+protected:
+ MatrixMover(MatrixView*);
+
+ void setBasicContextHelp(bool ctrlPressed = false);
+
+ timeT getDragTime(QMouseEvent *e, timeT candidate);
+
+ MatrixElement* m_currentElement;
+ MatrixStaff* m_currentStaff;
+
+ std::vector<MatrixElement *> m_duplicateElements;
+ bool m_quickCopy;
+
+ int m_lastPlayedPitch;
+ int m_clickX;
+};
+
+
+
+}
+
+#endif
diff --git a/src/gui/editors/matrix/MatrixPainter.cpp b/src/gui/editors/matrix/MatrixPainter.cpp
new file mode 100644
index 0000000..be63bd7
--- /dev/null
+++ b/src/gui/editors/matrix/MatrixPainter.cpp
@@ -0,0 +1,370 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
+
+/*
+ Rosegarden
+ A MIDI and audio sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "MatrixPainter.h"
+
+#include "base/BaseProperties.h"
+#include <klocale.h>
+#include <kstddirs.h>
+#include "base/Event.h"
+#include "base/NotationTypes.h"
+#include "base/SegmentMatrixHelper.h"
+#include "base/SnapGrid.h"
+#include "base/ViewElement.h"
+#include "commands/matrix/MatrixInsertionCommand.h"
+#include "commands/matrix/MatrixEraseCommand.h"
+#include "commands/matrix/MatrixPercussionInsertionCommand.h"
+#include "gui/general/EditTool.h"
+#include "gui/general/RosegardenCanvasView.h"
+#include "MatrixElement.h"
+#include "MatrixStaff.h"
+#include "MatrixTool.h"
+#include "MatrixView.h"
+#include <kaction.h>
+#include <kglobal.h>
+#include <qiconset.h>
+#include <qpoint.h>
+#include <qstring.h>
+#include "misc/Debug.h"
+
+
+namespace Rosegarden
+{
+
+MatrixPainter::MatrixPainter(MatrixView* parent)
+ : MatrixTool("MatrixPainter", parent),
+ m_currentElement(0),
+ m_currentStaff(0)
+{
+ QString pixmapDir = KGlobal::dirs()->findResource("appdata", "pixmaps/");
+ QCanvasPixmap pixmap(pixmapDir + "/toolbar/select.xpm");
+ QIconSet icon = QIconSet(pixmap);
+
+ new KAction(i18n("Switch to Select Tool"), icon, 0, this,
+ SLOT(slotSelectSelected()), actionCollection(),
+ "select");
+
+ new KAction(i18n("Switch to Erase Tool"), "eraser", 0, this,
+ SLOT(slotEraseSelected()), actionCollection(),
+ "erase");
+
+ new KAction(i18n("Switch to Move Tool"), "move", 0, this,
+ SLOT(slotMoveSelected()), actionCollection(),
+ "move");
+
+ pixmap.load(pixmapDir + "/toolbar/resize.xpm");
+ icon = QIconSet(pixmap);
+ new KAction(i18n("Switch to Resize Tool"), icon, 0, this,
+ SLOT(slotResizeSelected()), actionCollection(),
+ "resize");
+
+ createMenu("matrixpainter.rc");
+}
+
+MatrixPainter::MatrixPainter(QString name, MatrixView* parent)
+ : MatrixTool(name, parent),
+ m_currentElement(0),
+ m_currentStaff(0)
+{}
+
+void MatrixPainter::handleEventRemoved(Event *event)
+{
+ if (m_currentElement && m_currentElement->event() == event) {
+ m_currentElement = 0;
+ }
+}
+
+void MatrixPainter::handleLeftButtonPress(timeT time,
+ int pitch,
+ int staffNo,
+ QMouseEvent *e,
+ ViewElement *element)
+{
+ MATRIX_DEBUG << "MatrixPainter::handleLeftButtonPress : pitch = "
+ << pitch << ", time : " << time << endl;
+
+ QPoint p = m_mParentView->inverseMapPoint(e->pos());
+
+ m_currentStaff = m_mParentView->getStaff(staffNo);
+
+ // Don't create an overlapping event on the same note on the same channel
+ if (dynamic_cast<MatrixElement*>(element)) {
+ std::cerr << "MatrixPainter::handleLeftButtonPress : overlap with an other matrix element" << std::endl;
+ // In percussion matrix, we delete the existing event rather
+ // than just ignoring it -- this is reasonable as the event
+ // has no meaningful duration, so we can just toggle it on and
+ // off with repeated clicks
+ if (m_mParentView->isDrumMode()) {
+ if (element->event()) {
+ MatrixEraseCommand *command =
+ new MatrixEraseCommand(m_currentStaff->getSegment(),
+ element->event());
+ m_mParentView->addCommandToHistory(command);
+ }
+ }
+ m_currentElement = 0;
+ return ;
+ }
+
+ // This is needed for the event duration rounding
+ SnapGrid grid(getSnapGrid());
+
+ Event *ev = new Event(Note::EventType, time,
+ grid.getSnapTime(double(p.x())));
+ ev->set<Int>(BaseProperties::PITCH, pitch);
+ ev->set<Int>(BaseProperties::VELOCITY, m_mParentView->getCurrentVelocity());
+
+ m_currentElement = new MatrixElement(ev, m_mParentView->isDrumMode());
+
+ int y = m_currentStaff->getLayoutYForHeight(pitch) -
+ m_currentStaff->getElementHeight() / 2;
+
+ m_currentElement->setLayoutY(y);
+ m_currentElement->setLayoutX(grid.getRulerScale()->getXForTime(time));
+ m_currentElement->setHeight(m_currentStaff->getElementHeight());
+
+ int width = grid.getRulerScale()->getXForTime(time + ev->getDuration())
+ - m_currentElement->getLayoutX() + 1;
+
+ m_currentElement->setWidth(width);
+
+ m_currentStaff->positionElement(m_currentElement);
+ m_mParentView->update();
+
+ // preview
+ m_mParentView->playNote(ev);
+}
+
+int MatrixPainter::handleMouseMove(timeT time,
+ int pitch,
+ QMouseEvent *e)
+{
+ // sanity check
+ if (!m_currentElement)
+ return RosegardenCanvasView::NoFollow;
+
+ if (getSnapGrid().getSnapSetting() != SnapGrid::NoSnap) {
+ setContextHelp(i18n("Hold Shift to avoid snapping to beat grid"));
+ } else {
+ clearContextHelp();
+ }
+
+ // We don't want to use the time passed in, because it's snapped
+ // to the left and we want a more particular policy
+
+ if (e) {
+ QPoint p = m_mParentView->inverseMapPoint(e->pos());
+ time = getSnapGrid().snapX(p.x(), SnapGrid::SnapEither);
+ if (time >= m_currentElement->getViewAbsoluteTime()) {
+ time = getSnapGrid().snapX(p.x(), SnapGrid::SnapRight);
+ } else {
+ time = getSnapGrid().snapX(p.x(), SnapGrid::SnapLeft);
+ }
+ }
+
+ MATRIX_DEBUG << "MatrixPainter::handleMouseMove : pitch = "
+ << pitch << ", time : " << time << endl;
+
+ using BaseProperties::PITCH;
+
+ if (time == m_currentElement->getViewAbsoluteTime()) {
+ time =
+ m_currentElement->getViewAbsoluteTime() +
+ m_currentElement->getViewDuration();
+ }
+
+ int width = getSnapGrid().getRulerScale()->getXForTime(time)
+ - getSnapGrid().getRulerScale()->getXForTime
+ (m_currentElement->getViewAbsoluteTime()) + 1;
+
+ m_currentElement->setWidth(width);
+// std::cerr << "current element width "<< width << std::endl;
+
+ if (m_currentElement->event()->has(PITCH) &&
+ pitch != m_currentElement->event()->get<Int>(PITCH)) {
+
+ m_currentElement->event()->set<Int>(PITCH, pitch);
+
+ int y = m_currentStaff->getLayoutYForHeight(pitch) -
+ m_currentStaff->getElementHeight() / 2;
+
+ m_currentElement->setLayoutY(y);
+
+ m_currentStaff->positionElement(m_currentElement);
+
+ // preview
+ m_mParentView->playNote(m_currentElement->event());
+ }
+
+ m_mParentView->update();
+
+ return RosegardenCanvasView::FollowHorizontal |
+ RosegardenCanvasView::FollowVertical;
+}
+
+void MatrixPainter::handleMouseRelease(timeT endTime,
+ int,
+ QMouseEvent *e)
+{
+ // This can happen in case of screen/window capture -
+ // we only get a mouse release, the window snapshot tool
+ // got the mouse down
+ if (!m_currentElement)
+ return ;
+
+ // We don't want to use the time passed in, because it's snapped
+ // to the left and we want a more particular policy
+
+ if (e) {
+ QPoint p = m_mParentView->inverseMapPoint(e->pos());
+ endTime = getSnapGrid().snapX(p.x(), SnapGrid::SnapEither);
+ if (endTime >= m_currentElement->getViewAbsoluteTime()) {
+ endTime = getSnapGrid().snapX(p.x(), SnapGrid::SnapRight);
+ } else {
+ endTime = getSnapGrid().snapX(p.x(), SnapGrid::SnapLeft);
+ }
+ }
+
+ timeT time = m_currentElement->getViewAbsoluteTime();
+ timeT segmentEndTime = m_currentStaff->getSegment().getEndMarkerTime();
+
+ if (m_mParentView->isDrumMode()) {
+
+ if (time > segmentEndTime)
+ time = segmentEndTime;
+
+ MatrixPercussionInsertionCommand *command =
+ new MatrixPercussionInsertionCommand(m_currentStaff->getSegment(),
+ time,
+ m_currentElement->event());
+ m_mParentView->addCommandToHistory(command);
+
+ Event* ev = m_currentElement->event();
+ delete m_currentElement;
+ delete ev;
+
+ ev = command->getLastInsertedEvent();
+ if (ev)
+ m_mParentView->setSingleSelectedEvent(m_currentStaff->getSegment(),
+ ev);
+ } else {
+
+ // Insert element if it has a non null duration,
+ // discard it otherwise
+ //
+ if (time > endTime)
+ std::swap(time, endTime);
+
+ if (endTime == time)
+ endTime = time + m_currentElement->getViewDuration();
+
+ if (time < segmentEndTime) {
+
+ if (endTime > segmentEndTime)
+ endTime = segmentEndTime;
+
+ SegmentMatrixHelper helper(m_currentStaff->getSegment());
+ MATRIX_DEBUG << "MatrixPainter::handleMouseRelease() : helper.insertNote()" << endl;
+
+ MatrixInsertionCommand* command =
+ new MatrixInsertionCommand(m_currentStaff->getSegment(),
+ time,
+ endTime,
+ m_currentElement->event());
+
+ m_mParentView->addCommandToHistory(command);
+
+ Event* ev = m_currentElement->event();
+ delete m_currentElement;
+ delete ev;
+
+ ev = command->getLastInsertedEvent();
+ if (ev)
+ m_mParentView->setSingleSelectedEvent(m_currentStaff->getSegment(),
+ ev);
+ } else {
+
+ Event* ev = m_currentElement->event();
+ delete m_currentElement;
+ delete ev;
+ }
+ }
+
+ m_mParentView->update();
+ m_currentElement = 0;
+
+ setBasicContextHelp();
+}
+
+void MatrixPainter::ready()
+{
+ connect(m_parentView->getCanvasView(), SIGNAL(contentsMoving (int, int)),
+ this, SLOT(slotMatrixScrolled(int, int)));
+
+ m_mParentView->setCanvasCursor(Qt::crossCursor);
+
+ setBasicContextHelp();
+}
+
+void MatrixPainter::stow()
+{
+ disconnect(m_parentView->getCanvasView(), SIGNAL(contentsMoving (int, int)),
+ this, SLOT(slotMatrixScrolled(int, int)));
+}
+
+void MatrixPainter::slotMatrixScrolled(int newX, int newY)
+{
+ if (!m_currentElement)
+ return ;
+
+ QPoint newP1(newX, newY), oldP1(m_parentView->getCanvasView()->contentsX(),
+ m_parentView->getCanvasView()->contentsY());
+
+ QPoint offset = newP1 - oldP1;
+
+ offset = m_mParentView->inverseMapPoint(offset);
+
+ QPoint p(m_currentElement->getCanvasX() + m_currentElement->getWidth(), m_currentElement->getCanvasY());
+ p += offset;
+
+ timeT newTime = getSnapGrid().snapX(p.x());
+ int newPitch = m_currentStaff->getHeightAtCanvasCoords(p.x(), p.y());
+
+ handleMouseMove(newTime, newPitch, 0);
+}
+
+void MatrixPainter::setBasicContextHelp()
+{
+ if (getSnapGrid().getSnapSetting() != SnapGrid::NoSnap) {
+ setContextHelp(i18n("Click and drag to draw a note; Shift to avoid snapping to grid"));
+ } else {
+ setContextHelp(i18n("Click and drag to draw a note"));
+ }
+}
+
+const QString MatrixPainter::ToolName = "painter";
+
+}
+#include "MatrixPainter.moc"
diff --git a/src/gui/editors/matrix/MatrixPainter.h b/src/gui/editors/matrix/MatrixPainter.h
new file mode 100644
index 0000000..570243a
--- /dev/null
+++ b/src/gui/editors/matrix/MatrixPainter.h
@@ -0,0 +1,105 @@
+
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
+
+/*
+ Rosegarden
+ A MIDI and audio sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_MATRIXPAINTER_H_
+#define _RG_MATRIXPAINTER_H_
+
+#include "MatrixTool.h"
+#include <qstring.h>
+#include "base/Event.h"
+
+
+class QMouseEvent;
+
+
+namespace Rosegarden
+{
+
+class ViewElement;
+class MatrixView;
+class MatrixStaff;
+class MatrixElement;
+class Event;
+
+
+class MatrixPainter : public MatrixTool
+{
+ Q_OBJECT
+
+ friend class MatrixToolBox;
+
+public:
+
+ virtual void handleLeftButtonPress(timeT,
+ int height,
+ int staffNo,
+ QMouseEvent *event,
+ ViewElement*);
+
+ /**
+ * Set the duration of the element
+ */
+ virtual int handleMouseMove(timeT,
+ int height,
+ QMouseEvent*);
+
+ /**
+ * Actually insert the new element
+ */
+ virtual void handleMouseRelease(timeT,
+ int height,
+ QMouseEvent*);
+
+ static const QString ToolName;
+
+ /**
+ * Respond to an event being deleted -- it may be the one the tool
+ * is remembering as the current event.
+ */
+ virtual void handleEventRemoved(Event *event);
+
+ virtual void ready();
+ virtual void stow();
+
+protected slots:
+
+ void slotMatrixScrolled(int x, int y);
+
+protected:
+ MatrixPainter(MatrixView*);
+ MatrixPainter(QString name, MatrixView*);
+
+ void setBasicContextHelp();
+
+ MatrixElement* m_currentElement;
+ MatrixStaff* m_currentStaff;
+};
+
+
+
+
+}
+
+#endif
diff --git a/src/gui/editors/matrix/MatrixParameterBox.cpp b/src/gui/editors/matrix/MatrixParameterBox.cpp
new file mode 100644
index 0000000..c330b94
--- /dev/null
+++ b/src/gui/editors/matrix/MatrixParameterBox.cpp
@@ -0,0 +1,99 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
+
+/*
+ Rosegarden
+ A MIDI and audio sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "MatrixParameterBox.h"
+
+#include "base/Instrument.h"
+#include "base/BasicQuantizer.h"
+#include "base/Selection.h"
+#include "document/RosegardenGUIDoc.h"
+#include "gui/editors/parameters/InstrumentParameterBox.h"
+#include <kcombobox.h>
+#include <qfont.h>
+#include <qfontmetrics.h>
+#include <qframe.h>
+#include <qlayout.h>
+#include <qwidget.h>
+
+
+namespace Rosegarden
+{
+
+MatrixParameterBox::MatrixParameterBox(RosegardenGUIDoc *doc,
+ QWidget *parent, const char* name):
+ QFrame(parent, name),
+ m_quantizations(BasicQuantizer::getStandardQuantizations()),
+ m_doc(doc)
+{
+ setFrameStyle(NoFrame);
+ initBox();
+}
+
+MatrixParameterBox::~MatrixParameterBox()
+{}
+
+void
+MatrixParameterBox::initBox()
+{
+ QFont boldFont;
+ boldFont.setPointSize(int(boldFont.pointSize() * 9.5 / 10.0 + 0.5));
+ boldFont.setBold(true);
+
+ QFont plainFont;
+ plainFont.setPointSize(plainFont.pointSize() * 9 / 10);
+ QFont font = plainFont;
+
+ QFontMetrics fontMetrics(font);
+ // magic numbers: 13 is the height of the menu pixmaps, 10 is just 10
+ //int comboHeight = std::max(fontMetrics.height(), 13) + 10;
+
+ QGridLayout *gridLayout = new QGridLayout(this, 20, 3, 8, 1);
+
+ m_instrumentParameterBox = new InstrumentParameterBox(m_doc, this);
+ gridLayout->addMultiCellWidget(m_instrumentParameterBox, 0, 7, 0, 2);
+
+}
+
+void
+MatrixParameterBox::setSelection(EventSelection *selection)
+{
+ if (!selection)
+ return ;
+
+ EventSelection::eventcontainer::iterator
+ it = selection->getSegmentEvents().begin();
+
+for (; it != selection->getSegmentEvents().end(); it++) {}
+
+}
+
+void
+MatrixParameterBox::useInstrument(Instrument *instrument)
+{
+ m_instrumentParameterBox->useInstrument(instrument);
+}
+
+}
+#include "MatrixParameterBox.moc"
diff --git a/src/gui/editors/matrix/MatrixParameterBox.h b/src/gui/editors/matrix/MatrixParameterBox.h
new file mode 100644
index 0000000..d8d4a4d
--- /dev/null
+++ b/src/gui/editors/matrix/MatrixParameterBox.h
@@ -0,0 +1,76 @@
+
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
+
+/*
+ Rosegarden
+ A MIDI and audio sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_MATRIXPARAMETERBOX_H_
+#define _RG_MATRIXPARAMETERBOX_H_
+
+#include <qframe.h>
+#include <vector>
+#include "base/Event.h"
+
+
+class QWidget;
+class KComboBox;
+
+
+namespace Rosegarden
+{
+
+class RosegardenGUIDoc;
+class InstrumentParameterBox;
+class Instrument;
+class EventSelection;
+
+
+class MatrixParameterBox : public QFrame
+{
+ Q_OBJECT
+
+public:
+ MatrixParameterBox(RosegardenGUIDoc *doc=0, QWidget *parent=0, const char* name=0);
+ ~MatrixParameterBox();
+
+ void initBox();
+ void setSelection(EventSelection *);
+ void useInstrument(Instrument *instrument);
+
+protected:
+
+ KComboBox *m_quantizeCombo;
+ KComboBox *m_snapGridCombo;
+ InstrumentParameterBox *m_instrumentParameterBox;
+
+ std::vector<timeT> m_quantizations;
+ std::vector<timeT> m_snapValues;
+
+ RosegardenGUIDoc *m_doc;
+
+};
+
+
+
+}
+
+#endif
diff --git a/src/gui/editors/matrix/MatrixResizer.cpp b/src/gui/editors/matrix/MatrixResizer.cpp
new file mode 100644
index 0000000..2fab5e8
--- /dev/null
+++ b/src/gui/editors/matrix/MatrixResizer.cpp
@@ -0,0 +1,333 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
+
+/*
+ Rosegarden
+ A MIDI and audio sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "MatrixResizer.h"
+
+#include <klocale.h>
+#include <kstddirs.h>
+#include "base/Event.h"
+#include "base/Segment.h"
+#include "base/Selection.h"
+#include "base/SnapGrid.h"
+#include "base/ViewElement.h"
+#include "commands/matrix/MatrixModifyCommand.h"
+#include "commands/notation/NormalizeRestsCommand.h"
+#include "gui/general/EditTool.h"
+#include "gui/general/RosegardenCanvasView.h"
+#include "MatrixElement.h"
+#include "MatrixStaff.h"
+#include "MatrixTool.h"
+#include "MatrixView.h"
+#include <kaction.h>
+#include <kglobal.h>
+#include <qiconset.h>
+#include <qpoint.h>
+#include <qstring.h>
+#include "misc/Debug.h"
+
+
+namespace Rosegarden
+{
+
+MatrixResizer::MatrixResizer(MatrixView* parent)
+ : MatrixTool("MatrixResizer", parent),
+ m_currentElement(0),
+ m_currentStaff(0)
+{
+ QString pixmapDir = KGlobal::dirs()->findResource("appdata", "pixmaps/");
+ QCanvasPixmap pixmap(pixmapDir + "/toolbar/select.xpm");
+ QIconSet icon = QIconSet(pixmap);
+
+ new KAction(i18n("Switch to Select Tool"), icon, 0, this,
+ SLOT(slotSelectSelected()), actionCollection(),
+ "select");
+
+ new KAction(i18n("Switch to Draw Tool"), "pencil", 0, this,
+ SLOT(slotDrawSelected()), actionCollection(),
+ "draw");
+
+ new KAction(i18n("Switch to Erase Tool"), "eraser", 0, this,
+ SLOT(slotEraseSelected()), actionCollection(),
+ "erase");
+
+ new KAction(i18n("Switch to Move Tool"), "move", 0, this,
+ SLOT(slotMoveSelected()), actionCollection(),
+ "move");
+
+ createMenu("matrixresizer.rc");
+}
+
+void MatrixResizer::handleEventRemoved(Event *event)
+{
+ if (m_currentElement && m_currentElement->event() == event) {
+ m_currentElement = 0;
+ }
+}
+
+void MatrixResizer::handleLeftButtonPress(timeT,
+ int,
+ int staffNo,
+ QMouseEvent* e,
+ ViewElement* el)
+{
+ MATRIX_DEBUG << "MatrixResizer::handleLeftButtonPress() : el = "
+ << el << endl;
+
+ if (!el)
+ return ; // nothing to erase
+
+ m_currentElement = dynamic_cast<MatrixElement*>(el);
+ m_currentStaff = m_mParentView->getStaff(staffNo);
+
+ if (m_currentElement) {
+
+ // Add this element and allow movement
+ //
+ EventSelection* selection = m_mParentView->getCurrentSelection();
+
+ if (selection) {
+ EventSelection *newSelection;
+
+ if ((e->state() & Qt::ShiftButton) ||
+ selection->contains(m_currentElement->event()))
+ newSelection = new EventSelection(*selection);
+ else
+ newSelection = new EventSelection(m_currentStaff->getSegment());
+
+ newSelection->addEvent(m_currentElement->event());
+ m_mParentView->setCurrentSelection(newSelection, true, true);
+ m_mParentView->canvas()->update();
+ } else {
+ m_mParentView->setSingleSelectedEvent(m_currentStaff->getSegment(),
+ m_currentElement->event(),
+ true);
+ m_mParentView->canvas()->update();
+ }
+ }
+}
+
+int MatrixResizer::handleMouseMove(timeT newTime,
+ int,
+ QMouseEvent *e)
+{
+ setBasicContextHelp();
+
+ if (!m_currentElement || !m_currentStaff)
+ return RosegardenCanvasView::NoFollow;
+
+ if (getSnapGrid().getSnapSetting() != SnapGrid::NoSnap) {
+ setContextHelp(i18n("Hold Shift to avoid snapping to beat grid"));
+ } else {
+ clearContextHelp();
+ }
+
+ // For the resizer we normally don't want to use the official
+ // time, because it's snapped to the left and we want to snap in
+ // the closest direction instead
+
+ if (e) {
+ QPoint p = m_mParentView->inverseMapPoint(e->pos());
+ newTime = getSnapGrid().snapX(p.x(), SnapGrid::SnapEither);
+ }
+
+ timeT newDuration = newTime - m_currentElement->getViewAbsoluteTime();
+
+ if (newDuration == 0) {
+ newDuration += getSnapGrid().getSnapTime
+ (m_currentElement->getViewAbsoluteTime());
+ }
+
+ int width = getSnapGrid().getRulerScale()->getXForTime
+ (m_currentElement->getViewAbsoluteTime() + newDuration)
+ - m_currentElement->getLayoutX() + 1;
+
+ int initialWidth = m_currentElement->getWidth();
+
+ int diffWidth = initialWidth - width;
+
+ EventSelection* selection = m_mParentView->getCurrentSelection();
+ EventSelection::eventcontainer::iterator it =
+ selection->getSegmentEvents().begin();
+
+ MatrixElement *element = 0;
+ for (; it != selection->getSegmentEvents().end(); it++) {
+ element = m_currentStaff->getElement(*it);
+
+ if (element) {
+ int newWidth = element->getWidth() - diffWidth;
+
+ MATRIX_DEBUG << "MatrixResizer::handleMouseMove - "
+ << "new width = " << newWidth << endl;
+
+ element->setWidth(newWidth);
+ m_currentStaff->positionElement(element);
+ }
+ }
+
+ m_mParentView->canvas()->update();
+ return RosegardenCanvasView::FollowHorizontal;
+}
+
+void MatrixResizer::handleMouseRelease(timeT newTime,
+ int,
+ QMouseEvent *e)
+{
+ if (!m_currentElement || !m_currentStaff)
+ return ;
+
+ // For the resizer we don't want to use the time passed in,
+ // because it's snapped to the left and we want to snap in the
+ // closest direction instead
+
+ if (e) {
+ QPoint p = m_mParentView->inverseMapPoint(e->pos());
+ newTime = getSnapGrid().snapX(p.x(), SnapGrid::SnapEither);
+ }
+
+ timeT diffDuration =
+ newTime - m_currentElement->getViewAbsoluteTime() -
+ m_currentElement->getViewDuration();
+
+ EventSelection *selection = m_mParentView->getCurrentSelection();
+
+ if (selection->getAddedEvents() == 0)
+ return ;
+ else {
+ QString commandLabel = i18n("Resize Event");
+
+ if (selection->getAddedEvents() > 1)
+ commandLabel = i18n("Resize Events");
+
+ KMacroCommand *macro = new KMacroCommand(commandLabel);
+
+ EventSelection::eventcontainer::iterator it =
+ selection->getSegmentEvents().begin();
+
+ Segment &segment = m_currentStaff->getSegment();
+
+ EventSelection *newSelection = new EventSelection(segment);
+
+ timeT normalizeStart = selection->getStartTime();
+ timeT normalizeEnd = selection->getEndTime();
+
+ for (; it != selection->getSegmentEvents().end(); it++) {
+ timeT eventTime = (*it)->getAbsoluteTime();
+ timeT eventDuration = (*it)->getDuration() + diffDuration;
+
+
+ MATRIX_DEBUG << "MatrixResizer::handleMouseRelease - "
+ << "Time = " << eventTime
+ << ", Duration = " << eventDuration << endl;
+
+
+ if (eventDuration < 0) {
+ eventTime += eventDuration;
+ eventDuration = -eventDuration;
+ }
+
+ if (eventDuration == 0) {
+ eventDuration += getSnapGrid().getSnapTime(eventTime);
+ }
+
+ if (eventTime + eventDuration >= segment.getEndMarkerTime()) {
+ eventDuration = std::min(eventDuration,
+ segment.getEndMarkerTime() - eventTime);
+ }
+
+ Event *newEvent =
+ new Event(**it,
+ eventTime,
+ eventDuration);
+
+ macro->addCommand(new MatrixModifyCommand(segment,
+ *it,
+ newEvent,
+ false,
+ false));
+
+ newSelection->addEvent(newEvent);
+ }
+
+ normalizeStart = std::min(normalizeStart, newSelection->getStartTime());
+ normalizeEnd = std::max(normalizeEnd, newSelection->getEndTime());
+
+ macro->addCommand(new NormalizeRestsCommand(segment,
+ normalizeStart,
+ normalizeEnd));
+
+ m_mParentView->setCurrentSelection(0, false, false);
+ m_mParentView->addCommandToHistory(macro);
+ m_mParentView->setCurrentSelection(newSelection, false, false);
+ }
+
+ m_mParentView->update();
+ m_currentElement = 0;
+ setBasicContextHelp();
+}
+
+void MatrixResizer::ready()
+{
+ connect(m_parentView->getCanvasView(), SIGNAL(contentsMoving (int, int)),
+ this, SLOT(slotMatrixScrolled(int, int)));
+ m_mParentView->setCanvasCursor(Qt::sizeHorCursor);
+ setBasicContextHelp();
+}
+
+void MatrixResizer::stow()
+{
+ disconnect(m_parentView->getCanvasView(), SIGNAL(contentsMoving (int, int)),
+ this, SLOT(slotMatrixScrolled(int, int)));
+}
+
+void MatrixResizer::slotMatrixScrolled(int newX, int newY)
+{
+ QPoint newP1(newX, newY), oldP1(m_parentView->getCanvasView()->contentsX(),
+ m_parentView->getCanvasView()->contentsY());
+
+ QPoint p(newX, newY);
+
+ if (newP1.x() > oldP1.x()) {
+ p.setX(newX + m_parentView->getCanvasView()->visibleWidth());
+ }
+
+ p = m_mParentView->inverseMapPoint(p);
+ int newTime = getSnapGrid().snapX(p.x());
+ handleMouseMove(newTime, 0, 0);
+}
+
+void MatrixResizer::setBasicContextHelp()
+{
+ EventSelection *selection = m_mParentView->getCurrentSelection();
+ if (selection && selection->getAddedEvents() > 1) {
+ setContextHelp(i18n("Click and drag to resize selected notes"));
+ } else {
+ setContextHelp(i18n("Click and drag to resize a note"));
+ }
+}
+
+const QString MatrixResizer::ToolName = "resizer";
+
+}
+#include "MatrixResizer.moc"
diff --git a/src/gui/editors/matrix/MatrixResizer.h b/src/gui/editors/matrix/MatrixResizer.h
new file mode 100644
index 0000000..e623cac
--- /dev/null
+++ b/src/gui/editors/matrix/MatrixResizer.h
@@ -0,0 +1,102 @@
+
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
+
+/*
+ Rosegarden
+ A MIDI and audio sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_MATRIXRESIZER_H_
+#define _RG_MATRIXRESIZER_H_
+
+#include "MatrixTool.h"
+#include <qstring.h>
+#include "base/Event.h"
+
+
+class QMouseEvent;
+
+
+namespace Rosegarden
+{
+
+class ViewElement;
+class MatrixView;
+class MatrixStaff;
+class MatrixElement;
+class Event;
+
+
+class MatrixResizer : public MatrixTool
+{
+ Q_OBJECT
+
+ friend class MatrixToolBox;
+
+public:
+ virtual void handleLeftButtonPress(timeT,
+ int height,
+ int staffNo,
+ QMouseEvent *event,
+ ViewElement*);
+
+ /**
+ * Set the duration of the element
+ */
+ virtual int handleMouseMove(timeT,
+ int height,
+ QMouseEvent*);
+
+ /**
+ * Actually insert the new element
+ */
+ virtual void handleMouseRelease(timeT,
+ int height,
+ QMouseEvent*);
+
+ static const QString ToolName;
+
+ /**
+ * Respond to an event being deleted -- it may be the one the tool
+ * is remembering as the current event.
+ */
+ virtual void handleEventRemoved(Event *event);
+
+ virtual void ready();
+ virtual void stow();
+
+protected slots:
+
+ void slotMatrixScrolled(int x, int y);
+
+protected:
+ MatrixResizer(MatrixView*);
+
+ void setBasicContextHelp();
+
+ MatrixElement* m_currentElement;
+ MatrixStaff* m_currentStaff;
+};
+
+
+
+}
+
+#endif
diff --git a/src/gui/editors/matrix/MatrixSelector.cpp b/src/gui/editors/matrix/MatrixSelector.cpp
new file mode 100644
index 0000000..fbb9689
--- /dev/null
+++ b/src/gui/editors/matrix/MatrixSelector.cpp
@@ -0,0 +1,629 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
+
+/*
+ Rosegarden
+ A MIDI and audio sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "MatrixSelector.h"
+
+#include "base/BaseProperties.h"
+#include <klocale.h>
+#include <kstddirs.h>
+#include "base/Event.h"
+#include "base/NotationTypes.h"
+#include "base/Selection.h"
+#include "base/ViewElement.h"
+#include "commands/edit/EventEditCommand.h"
+#include "gui/dialogs/EventEditDialog.h"
+#include "gui/dialogs/SimpleEventEditDialog.h"
+#include "gui/general/EditTool.h"
+#include "gui/general/EditToolBox.h"
+#include "gui/general/GUIPalette.h"
+#include "gui/general/RosegardenCanvasView.h"
+#include "MatrixElement.h"
+#include "MatrixMover.h"
+#include "MatrixPainter.h"
+#include "MatrixResizer.h"
+#include "MatrixStaff.h"
+#include "MatrixTool.h"
+#include "MatrixView.h"
+#include <kaction.h>
+#include <kglobal.h>
+#include <kapplication.h>
+#include <kconfig.h>
+#include <qdialog.h>
+#include <qiconset.h>
+#include <qpoint.h>
+#include <qstring.h>
+#include "misc/Debug.h"
+
+
+namespace Rosegarden
+{
+
+MatrixSelector::MatrixSelector(MatrixView* view)
+ : MatrixTool("MatrixSelector", view),
+ m_selectionRect(0),
+ m_updateRect(false),
+ m_currentStaff(0),
+ m_clickedElement(0),
+ m_dispatchTool(0),
+ m_justSelectedBar(false),
+ m_matrixView(view),
+ m_selectionToMerge(0)
+{
+ connect(m_parentView, SIGNAL(usedSelection()),
+ this, SLOT(slotHideSelection()));
+
+ new KAction(i18n("Switch to Draw Tool"), "pencil", 0, this,
+ SLOT(slotDrawSelected()), actionCollection(),
+ "draw");
+
+ new KAction(i18n("Switch to Erase Tool"), "eraser", 0, this,
+ SLOT(slotEraseSelected()), actionCollection(),
+ "erase");
+
+ new KAction(i18n("Switch to Move Tool"), "move", 0, this,
+ SLOT(slotMoveSelected()), actionCollection(),
+ "move");
+
+ QString pixmapDir = KGlobal::dirs()->findResource("appdata", "pixmaps/");
+ QCanvasPixmap pixmap(pixmapDir + "/toolbar/resize.xpm");
+ QIconSet icon = QIconSet(pixmap);
+
+ new KAction(i18n("Switch to Resize Tool"), icon, 0, this,
+ SLOT(slotResizeSelected()), actionCollection(),
+ "resize");
+
+ createMenu("matrixselector.rc");
+}
+
+void MatrixSelector::handleEventRemoved(Event *event)
+{
+ if (m_dispatchTool)
+ m_dispatchTool->handleEventRemoved(event);
+ if (m_clickedElement && m_clickedElement->event() == event) {
+ m_clickedElement = 0;
+ }
+}
+
+void MatrixSelector::slotClickTimeout()
+{
+ m_justSelectedBar = false;
+}
+
+void MatrixSelector::handleLeftButtonPress(timeT time,
+ int height,
+ int staffNo,
+ QMouseEvent* e,
+ ViewElement *element)
+{
+ MATRIX_DEBUG << "MatrixSelector::handleMousePress" << endl;
+
+ if (m_justSelectedBar) {
+ handleMouseTripleClick(time, height, staffNo, e, element);
+ m_justSelectedBar = false;
+ return ;
+ }
+
+ QPoint p = m_mParentView->inverseMapPoint(e->pos());
+
+ m_currentStaff = m_mParentView->getStaff(staffNo);
+
+ // Do the merge selection thing
+ //
+ delete m_selectionToMerge; // you can safely delete 0, you know?
+ const EventSelection *selectionToMerge = 0;
+ if (e->state() & Qt::ShiftButton)
+ selectionToMerge = m_mParentView->getCurrentSelection();
+
+ m_selectionToMerge =
+ (selectionToMerge ? new EventSelection(*selectionToMerge) : 0);
+
+ // Now the rest of the element stuff
+ //
+ m_clickedElement = dynamic_cast<MatrixElement*>(element);
+
+ if (m_clickedElement) {
+ int x = int(m_clickedElement->getLayoutX());
+ int width = m_clickedElement->getWidth();
+ int resizeStart = int(double(width) * 0.85) + x;
+
+ // max size of 10
+ if ((x + width ) - resizeStart > 10)
+ resizeStart = x + width - 10;
+
+ if (p.x() > resizeStart) {
+ m_dispatchTool = m_parentView->
+ getToolBox()->getTool(MatrixResizer::ToolName);
+ } else {
+ m_dispatchTool = m_parentView->
+ getToolBox()->getTool(MatrixMover::ToolName);
+ }
+
+ m_dispatchTool->ready();
+
+ m_dispatchTool->handleLeftButtonPress(time,
+ height,
+ staffNo,
+ e,
+ element);
+ return ;
+
+ } else if (e->state() & Qt::ControlButton) {
+
+ handleMidButtonPress(time, height, staffNo, e, element);
+ return;
+
+ } else {
+
+ // Workaround for #930420 Positional error in sweep-selection box
+ // boundary
+ int zoomValue = (int)m_matrixView->m_hZoomSlider->getCurrentSize();
+ MatrixStaff *staff = m_mParentView->getStaff(staffNo);
+ int pitch = m_currentStaff->getHeightAtCanvasCoords(p.x(), p.y());
+ int pitchCentreHeight = staff->getTotalHeight() -
+ pitch * staff->getLineSpacing() - 2; // 2 or ?
+ int pitchLineHeight = pitchCentreHeight + staff->getLineSpacing() / 2;
+ int drawHeight = p.y();
+ if (drawHeight <= pitchLineHeight + 1 &&
+ drawHeight >= pitchLineHeight - 1) {
+ if (drawHeight == pitchLineHeight)
+ drawHeight += 2;
+ else
+ drawHeight += 2 * (drawHeight - pitchLineHeight);
+ }
+ MATRIX_DEBUG << "#### MatrixSelector::handleLeftButtonPress() : zoom "
+ << zoomValue
+ << " pitch " << pitch
+ << " pitchCentreHeight " << pitchCentreHeight
+ << " pitchLineHeight " << pitchLineHeight
+ << " lineSpacing " << staff->getLineSpacing()
+ << " drawHeight " << drawHeight << endl;
+ m_selectionRect->setX(int(p.x() / 4)*4); // more workaround for #930420
+ m_selectionRect->setY(drawHeight);
+ m_selectionRect->setSize(0, 0);
+
+ m_selectionRect->show();
+ m_updateRect = true;
+
+ // Clear existing selection if we're not merging
+ //
+ if (!m_selectionToMerge) {
+ m_mParentView->setCurrentSelection(0, false, true);
+ m_mParentView->canvas()->update();
+ }
+ }
+
+ //m_parentView->setCursorPosition(p.x());
+}
+
+void MatrixSelector::handleMidButtonPress(timeT time,
+ int height,
+ int staffNo,
+ QMouseEvent* e,
+ ViewElement *element)
+{
+ m_clickedElement = 0; // should be used for left-button clicks only
+
+ // Don't allow overlapping elements on the same channel
+ if (dynamic_cast<MatrixElement*>(element))
+ return ;
+
+ m_dispatchTool = m_parentView->
+ getToolBox()->getTool(MatrixPainter::ToolName);
+
+ m_dispatchTool->ready();
+
+ m_dispatchTool->handleLeftButtonPress(time, height, staffNo, e, element);
+}
+
+void MatrixSelector::handleMouseDoubleClick(timeT ,
+ int ,
+ int staffNo,
+ QMouseEvent *ev,
+ ViewElement *element)
+{
+ /*
+ if (m_dispatchTool)
+ {
+ m_dispatchTool->handleMouseDoubleClick(time, height, staffNo, e, element);
+ }
+ */
+
+ m_clickedElement = dynamic_cast<MatrixElement*>(element);
+
+ MatrixStaff *staff = m_mParentView->getStaff(staffNo);
+ if (!staff)
+ return ;
+
+ if (m_clickedElement) {
+
+ if (m_clickedElement->event()->isa(Note::EventType) &&
+ m_clickedElement->event()->has(BaseProperties::TRIGGER_SEGMENT_ID)) {
+
+ int id = m_clickedElement->event()->get
+ <Int>
+ (BaseProperties::TRIGGER_SEGMENT_ID);
+ emit editTriggerSegment(id);
+ return ;
+ }
+
+ if (ev->state() & ShiftButton) { // advanced edit
+
+ EventEditDialog dialog(m_mParentView, *m_clickedElement->event(), true);
+
+ if (dialog.exec() == QDialog::Accepted &&
+ dialog.isModified()) {
+
+ EventEditCommand *command = new EventEditCommand
+ (staff->getSegment(),
+ m_clickedElement->event(),
+ dialog.getEvent());
+
+ m_mParentView->addCommandToHistory(command);
+ }
+ } else {
+
+ SimpleEventEditDialog dialog(m_mParentView, m_mParentView->getDocument(),
+ *m_clickedElement->event(), false);
+
+ if (dialog.exec() == QDialog::Accepted &&
+ dialog.isModified()) {
+
+ EventEditCommand *command = new EventEditCommand
+ (staff->getSegment(),
+ m_clickedElement->event(),
+ dialog.getEvent());
+
+ m_mParentView->addCommandToHistory(command);
+ }
+ }
+
+ } /*
+
+ #988167: Matrix:Multiclick select methods don't work in matrix editor
+ Postponing this, as it falls foul of world-matrix transformation
+ etiquette and other such niceties
+
+ else {
+
+ QRect rect = staff->getBarExtents(ev->x(), ev->y());
+
+ m_selectionRect->setX(rect.x() + 2);
+ m_selectionRect->setY(rect.y());
+ m_selectionRect->setSize(rect.width() - 4, rect.height());
+
+ m_selectionRect->show();
+ m_updateRect = false;
+
+ m_justSelectedBar = true;
+ QTimer::singleShot(QApplication::doubleClickInterval(), this,
+ SLOT(slotClickTimeout()));
+ } */
+}
+
+void MatrixSelector::handleMouseTripleClick(timeT t,
+ int height,
+ int staffNo,
+ QMouseEvent *ev,
+ ViewElement *element)
+{
+ if (!m_justSelectedBar)
+ return ;
+ m_justSelectedBar = false;
+
+ MatrixStaff *staff = m_mParentView->getStaff(staffNo);
+ if (!staff)
+ return ;
+
+ if (m_clickedElement) {
+
+ // should be safe, as we've already set m_justSelectedBar false
+ handleLeftButtonPress(t, height, staffNo, ev, element);
+ return ;
+
+ } else {
+
+ m_selectionRect->setX(staff->getX());
+ m_selectionRect->setY(staff->getY());
+ m_selectionRect->setSize(int(staff->getTotalWidth()) - 1,
+ staff->getTotalHeight() - 1);
+
+ m_selectionRect->show();
+ m_updateRect = false;
+ }
+}
+
+int MatrixSelector::handleMouseMove(timeT time, int height,
+ QMouseEvent *e)
+{
+ QPoint p = m_mParentView->inverseMapPoint(e->pos());
+
+ if (m_dispatchTool) {
+ return m_dispatchTool->handleMouseMove(time, height, e);
+ }
+
+
+ if (!m_updateRect) {
+ setContextHelpFor(e->pos(),
+ getSnapGrid().getSnapSetting() == SnapGrid::NoSnap);
+ return RosegardenCanvasView::NoFollow;
+ } else {
+ clearContextHelp();
+ }
+
+ int w = int(p.x() - m_selectionRect->x());
+ int h = int(p.y() - m_selectionRect->y());
+
+ // Qt rectangle dimensions appear to be 1-based
+ if (w > 0)
+ ++w;
+ else
+ --w;
+ if (h > 0)
+ ++h;
+ else
+ --h;
+
+ // Workaround for #930420 Positional error in sweep-selection box boundary
+ int wFix = (w > 0) ? 3 : 0;
+ int hFix = (h > 0) ? 3 : 0;
+ int xFix = (w < 0) ? 3 : 0;
+ m_selectionRect->setSize(w - wFix, h - hFix);
+ m_selectionRect->setX(m_selectionRect->x() + xFix);
+ setViewCurrentSelection();
+ m_selectionRect->setSize(w, h);
+ m_selectionRect->setX(m_selectionRect->x() - xFix);
+ m_mParentView->canvas()->update();
+
+ return RosegardenCanvasView::FollowHorizontal | RosegardenCanvasView::FollowVertical;
+}
+
+void MatrixSelector::handleMouseRelease(timeT time, int height, QMouseEvent *e)
+{
+ MATRIX_DEBUG << "MatrixSelector::handleMouseRelease" << endl;
+
+ if (m_dispatchTool) {
+ m_dispatchTool->handleMouseRelease(time, height, e);
+
+ m_dispatchTool->stow();
+ ready();
+
+ // don't delete the tool as it's still part of the toolbox
+ m_dispatchTool = 0;
+
+ return ;
+ }
+
+ m_updateRect = false;
+
+ if (m_clickedElement) {
+ m_mParentView->setSingleSelectedEvent(m_currentStaff->getSegment(),
+ m_clickedElement->event(),
+ false, true);
+ m_mParentView->canvas()->update();
+ m_clickedElement = 0;
+
+ } else if (m_selectionRect) {
+ setViewCurrentSelection();
+ m_selectionRect->hide();
+ m_mParentView->canvas()->update();
+ }
+
+ // Tell anyone who's interested that the selection has changed
+ emit gotSelection();
+
+ setContextHelpFor(e->pos());
+}
+
+void MatrixSelector::ready()
+{
+ if (m_mParentView) {
+ m_selectionRect = new QCanvasRectangle(m_mParentView->canvas());
+ m_selectionRect->hide();
+ m_selectionRect->setPen(QPen(GUIPalette::getColour(GUIPalette::SelectionRectangle), 2));
+
+ m_mParentView->setCanvasCursor(Qt::arrowCursor);
+ //m_mParentView->setPositionTracking(false);
+ }
+
+ connect(m_parentView->getCanvasView(), SIGNAL(contentsMoving (int, int)),
+ this, SLOT(slotMatrixScrolled(int, int)));
+
+ setContextHelp(i18n("Click and drag to select; middle-click and drag to draw new note"));
+}
+
+void MatrixSelector::stow()
+{
+ if (m_selectionRect) {
+ delete m_selectionRect;
+ m_selectionRect = 0;
+ m_mParentView->canvas()->update();
+ }
+
+ disconnect(m_parentView->getCanvasView(), SIGNAL(contentsMoving (int, int)),
+ this, SLOT(slotMatrixScrolled(int, int)));
+
+}
+
+void MatrixSelector::slotHideSelection()
+{
+ if (!m_selectionRect)
+ return ;
+ m_selectionRect->hide();
+ m_selectionRect->setSize(0, 0);
+ m_mParentView->canvas()->update();
+}
+
+void MatrixSelector::slotMatrixScrolled(int newX, int newY)
+{
+ if (m_updateRect) {
+ int offsetX = newX - m_parentView->getCanvasView()->contentsX();
+ int offsetY = newY - m_parentView->getCanvasView()->contentsY();
+
+ int w = int(m_selectionRect->width() + offsetX);
+ int h = int(m_selectionRect->height() + offsetY);
+
+ // Qt rectangle dimensions appear to be 1-based
+ if (w > 0)
+ ++w;
+ else
+ --w;
+ if (h > 0)
+ ++h;
+ else
+ --h;
+
+ m_selectionRect->setSize(w, h);
+ setViewCurrentSelection();
+ m_mParentView->canvas()->update();
+ }
+}
+
+void MatrixSelector::setViewCurrentSelection()
+{
+ EventSelection* selection = getSelection();
+
+ if (m_selectionToMerge && selection &&
+ m_selectionToMerge->getSegment() == selection->getSegment()) {
+
+ selection->addFromSelection(m_selectionToMerge);
+ m_mParentView->setCurrentSelection(selection, true, true);
+
+ } else if (!m_selectionToMerge) {
+
+ m_mParentView->setCurrentSelection(selection, true, true);
+
+ }
+
+}
+
+EventSelection* MatrixSelector::getSelection()
+{
+ if (!m_selectionRect->visible()) return 0;
+
+ Segment& originalSegment = m_currentStaff->getSegment();
+ EventSelection* selection = new EventSelection(originalSegment);
+
+ // get the selections
+ //
+ QCanvasItemList l = m_selectionRect->collisions(true);
+
+ if (l.count())
+ {
+ for (QCanvasItemList::Iterator it=l.begin(); it!=l.end(); ++it)
+ {
+ QCanvasItem *item = *it;
+ QCanvasMatrixRectangle *matrixRect = 0;
+
+ if ((matrixRect = dynamic_cast<QCanvasMatrixRectangle*>(item)))
+ {
+ MatrixElement *mE = &matrixRect->getMatrixElement();
+ selection->addEvent(mE->event());
+ }
+ }
+ }
+
+ if (selection->getAddedEvents() > 0) {
+ return selection;
+ } else {
+ delete selection;
+ return 0;
+ }
+}
+
+void MatrixSelector::setContextHelpFor(QPoint p, bool ctrlPressed)
+{
+ kapp->config()->setGroup(GeneralOptionsConfigGroup);
+ if (!kapp->config()->readBoolEntry("toolcontexthelp", true)) return;
+
+ p = m_mParentView->inverseMapPoint(p);
+
+ // same logic as in MatrixCanvasView::contentsMousePressEvent
+
+ QCanvasItemList itemList = m_mParentView->canvas()->collisions(p);
+ QCanvasItemList::Iterator it;
+ MatrixElement* mel = 0;
+ QCanvasItem* activeItem = 0;
+
+ for (it = itemList.begin(); it != itemList.end(); ++it) {
+
+ QCanvasItem *item = *it;
+ QCanvasMatrixRectangle *mRect = 0;
+
+ if (item->active()) {
+ break;
+ }
+
+ if ((mRect = dynamic_cast<QCanvasMatrixRectangle*>(item))) {
+ if (! mRect->rect().contains(p, true)) continue;
+ mel = &(mRect->getMatrixElement());
+ break;
+ }
+ }
+
+ if (!mel) {
+ setContextHelp(i18n("Click and drag to select; middle-click and drag to draw new note"));
+
+ } else {
+
+ // same logic as in handleMouseButtonPress
+
+ int x = int(mel->getLayoutX());
+ int width = mel->getWidth();
+ int resizeStart = int(double(width) * 0.85) + x;
+
+ // max size of 10
+ if ((x + width ) - resizeStart > 10)
+ resizeStart = x + width - 10;
+
+ EventSelection *s = m_mParentView->getCurrentSelection();
+
+ if (p.x() > resizeStart) {
+ if (s && s->getAddedEvents() > 1) {
+ setContextHelp(i18n("Click and drag to resize selected notes"));
+ } else {
+ setContextHelp(i18n("Click and drag to resize note"));
+ }
+ } else {
+ if (s && s->getAddedEvents() > 1) {
+ if (!ctrlPressed) {
+ setContextHelp(i18n("Click and drag to move selected notes; hold Ctrl as well to copy"));
+ } else {
+ setContextHelp(i18n("Click and drag to copy selected notes"));
+ }
+ } else {
+ if (!ctrlPressed) {
+ setContextHelp(i18n("Click and drag to move note; hold Ctrl as well to copy"));
+ } else {
+ setContextHelp(i18n("Click and drag to copy note"));
+ }
+ }
+ }
+ }
+}
+
+const QString MatrixSelector::ToolName = "selector";
+
+}
+#include "MatrixSelector.moc"
diff --git a/src/gui/editors/matrix/MatrixSelector.h b/src/gui/editors/matrix/MatrixSelector.h
new file mode 100644
index 0000000..a1d1ca4
--- /dev/null
+++ b/src/gui/editors/matrix/MatrixSelector.h
@@ -0,0 +1,177 @@
+
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
+
+/*
+ Rosegarden
+ A MIDI and audio sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_MATRIXSELECTOR_H_
+#define _RG_MATRIXSELECTOR_H_
+
+#include "MatrixTool.h"
+#include <qstring.h>
+#include "base/Event.h"
+
+
+class QMouseEvent;
+class QCanvasRectangle;
+
+
+namespace Rosegarden
+{
+
+class ViewElement;
+class MatrixView;
+class MatrixStaff;
+class MatrixElement;
+class EventSelection;
+class Event;
+class EditTool;
+
+
+class MatrixSelector : public MatrixTool
+{
+ Q_OBJECT
+
+ friend class MatrixToolBox;
+
+public:
+
+ virtual void handleLeftButtonPress(timeT time,
+ int height,
+ int staffNo,
+ QMouseEvent *event,
+ ViewElement *element);
+
+ virtual void handleMidButtonPress(timeT time,
+ int height,
+ int staffNo,
+ QMouseEvent *event,
+ ViewElement *element);
+
+ virtual int handleMouseMove(timeT time,
+ int height,
+ QMouseEvent *event);
+
+ virtual void handleMouseRelease(timeT,
+ int height,
+ QMouseEvent *event);
+
+ /**
+ * Double-click: edit an event or make a whole-bar selection
+ */
+ virtual void handleMouseDoubleClick(timeT time,
+ int height,
+ int staffNo,
+ QMouseEvent* event,
+ ViewElement *element);
+
+ /**
+ * Triple-click: maybe make a whole-staff selection
+ */
+ virtual void handleMouseTripleClick(timeT time,
+ int height,
+ int staffNo,
+ QMouseEvent* event,
+ ViewElement *element);
+
+
+ /**
+ * Create the selection rect
+ *
+ * We need this because MatrixView deletes all QCanvasItems
+ * along with it. This happens before the MatrixSelector is
+ * deleted, so we can't delete the selection rect in
+ * ~MatrixSelector because that leads to double deletion.
+ */
+ virtual void ready();
+
+ /**
+ * Delete the selection rect.
+ */
+ virtual void stow();
+
+ /**
+ * Returns the currently selected events
+ *
+ * The returned result is owned by the caller
+ */
+ EventSelection* getSelection();
+
+ /**
+ * Respond to an event being deleted -- it may be the one the tool
+ * is remembering as the current event.
+ */
+ virtual void handleEventRemoved(Event *event);
+
+ static const QString ToolName;
+
+public slots:
+ /**
+ * Hide the selection rectangle
+ *
+ * Should be called after a cut or a copy has been
+ * performed
+ */
+ void slotHideSelection();
+
+ void slotClickTimeout();
+
+protected slots:
+
+ void slotMatrixScrolled(int x, int y);
+
+signals:
+ void gotSelection(); // inform that we've got a new selection
+ void editTriggerSegment(int);
+
+protected:
+ MatrixSelector(MatrixView*);
+
+ void setContextHelpFor(QPoint p, bool ctrlPressed = false);
+
+ void setViewCurrentSelection();
+
+ //--------------- Data members ---------------------------------
+
+ QCanvasRectangle* m_selectionRect;
+ bool m_updateRect;
+
+ int m_clickedStaff;
+ MatrixStaff* m_currentStaff;
+
+ MatrixElement* m_clickedElement;
+
+ // tool to delegate to
+ EditTool* m_dispatchTool;
+
+ bool m_justSelectedBar;
+
+ MatrixView * m_matrixView;
+
+ EventSelection *m_selectionToMerge;
+};
+
+
+
+}
+
+#endif
diff --git a/src/gui/editors/matrix/MatrixStaff.cpp b/src/gui/editors/matrix/MatrixStaff.cpp
new file mode 100644
index 0000000..b6be79f
--- /dev/null
+++ b/src/gui/editors/matrix/MatrixStaff.cpp
@@ -0,0 +1,232 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
+
+/*
+ Rosegarden
+ A MIDI and audio sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "MatrixStaff.h"
+#include "misc/Debug.h"
+
+#include "base/BaseProperties.h"
+#include "base/Composition.h"
+#include "base/Event.h"
+#include "base/Instrument.h"
+#include "base/MidiProgram.h"
+#include "base/NotationTypes.h"
+#include "base/Segment.h"
+#include "base/Selection.h"
+#include "base/SnapGrid.h"
+#include "base/Staff.h"
+#include "base/Track.h"
+#include "base/ViewElement.h"
+#include "base/SegmentMatrixHelper.h"
+#include "document/RosegardenGUIDoc.h"
+#include "gui/general/GUIPalette.h"
+#include "gui/general/LinedStaff.h"
+#include "gui/rulers/DefaultVelocityColour.h"
+#include "MatrixElement.h"
+#include "MatrixView.h"
+#include "MatrixVLayout.h"
+#include <qcanvas.h>
+
+
+namespace Rosegarden
+{
+
+MatrixStaff::MatrixStaff(QCanvas *canvas,
+ Segment *segment,
+ SnapGrid *snapGrid,
+ int id,
+ int vResolution,
+ MatrixView *view) :
+ LinedStaff(canvas, segment, snapGrid, id, vResolution, 1),
+ m_scaleFactor(2.0 /
+ Note(Note::Shortest).getDuration()),
+ m_view(view)
+{}
+
+MatrixStaff::~MatrixStaff()
+{
+ // nothing
+}
+
+int MatrixStaff::getLineCount() const
+{
+ // MATRIX_DEBUG << "MatrixStaff::getLineCount: isDrumMode " << m_view->isDrumMode() << ", key mapping " << (getKeyMapping() ? getKeyMapping()->getName() : "<none>") << endl;
+
+ if (m_view->isDrumMode()) {
+ const MidiKeyMapping *km = getKeyMapping();
+ if (km)
+ return km->getPitchExtent() + 1;
+ }
+ return MatrixVLayout::maxMIDIPitch + 2;
+}
+
+int MatrixStaff::getLegerLineCount() const
+{
+ return 0;
+}
+
+int MatrixStaff::getBottomLineHeight() const
+{
+ if (m_view->isDrumMode()) {
+ const MidiKeyMapping *km = getKeyMapping();
+ if (km)
+ return km->getPitchForOffset(0);
+ }
+ return 0;
+}
+
+int MatrixStaff::getHeightPerLine() const
+{
+ return 1;
+}
+
+bool MatrixStaff::elementsInSpaces() const
+{
+ return true;
+}
+
+bool MatrixStaff::showBeatLines() const
+{
+ return true;
+}
+
+bool MatrixStaff::wrapEvent(Event* e)
+{
+ // Changed from "Note or Time signature" to just "Note" because
+ // there should be no time signature events in any ordinary
+ // segments, they're only in the composition's ref segment
+
+ return e->isa(Note::EventType) &&
+ Staff::wrapEvent(e);
+}
+
+void
+MatrixStaff::positionElements(timeT from, timeT to)
+{
+ MatrixElementList *mel = getViewElementList();
+
+ MatrixElementList::iterator beginAt = mel->findTime(from);
+ if (beginAt != mel->begin())
+ --beginAt;
+
+ MatrixElementList::iterator endAt = mel->findTime(to);
+
+ for (MatrixElementList::iterator i = beginAt; i != endAt; ++i) {
+ positionElement(*i);
+ }
+}
+
+void MatrixStaff::positionElement(ViewElement* vel)
+{
+ MatrixElement* el = dynamic_cast<MatrixElement*>(vel);
+
+ // Memorize initial rectangle position. May be some overlap rectangles
+ // belonging to other notes are here and should be refreshed after
+ // current element is moved.
+ QRect initialRect;
+ bool rectWasVisible;
+ if (! m_view->isDrumMode())
+ rectWasVisible = el->getVisibleRectangle(initialRect);
+
+ LinedStaffCoords coords = getCanvasCoordsForLayoutCoords
+ (el->getLayoutX(), int(el->getLayoutY()));
+
+ // Get velocity for colouring
+ //
+ using BaseProperties::VELOCITY;
+ long velocity = 127;
+ if (el->event()->has(VELOCITY))
+ el->event()->get
+ <Int>(VELOCITY, velocity);
+
+ el->setCanvas(m_canvas);
+
+ // Is the event currently selected? Colour accordingly.
+ //
+ EventSelection *selection = m_view->getCurrentSelection();
+
+ if (selection && selection->contains(el->event()))
+ el->setColour(GUIPalette::getColour(GUIPalette::SelectedElement));
+ else if (el->event()->has(BaseProperties::TRIGGER_SEGMENT_ID))
+ el->setColour(Qt::gray);
+ else
+ el->setColour(DefaultVelocityColour::getInstance()->getColour(velocity));
+
+ el->setCanvasX(coords.first);
+ el->setCanvasY((double)coords.second);
+
+ // Display overlaps
+ if (m_view->isDrumMode()) {
+ SegmentMatrixHelper helper(m_segment);
+ if (helper.isDrumColliding(el->event()))
+ el->setColour(GUIPalette::getColour(GUIPalette::MatrixOverlapBlock));
+ } else {
+ el->drawOverlapRectangles();
+
+ // Refresh other overlap rectangles
+ if (rectWasVisible) el->redrawOverlaps(initialRect);
+ }
+
+}
+
+MatrixElement*
+MatrixStaff::getElement(Event *event)
+{
+ ViewElementList::iterator i = findEvent(event);
+ if (i == m_viewElementList->end())
+ return 0;
+ return dynamic_cast<MatrixElement*>(*i);
+}
+
+void
+MatrixStaff::eventRemoved(const Segment *segment,
+ Event *event)
+{
+ LinedStaff::eventRemoved(segment, event);
+ m_view->handleEventRemoved(event);
+}
+
+ViewElement*
+MatrixStaff::makeViewElement(Event* e)
+{
+ return new MatrixElement(e, m_view->isDrumMode());
+}
+
+const MidiKeyMapping*
+MatrixStaff::getKeyMapping() const
+{
+ Composition *comp = getSegment().getComposition();
+ if (!comp)
+ return 0;
+ TrackId trackId = getSegment().getTrack();
+ Track *track = comp->getTrackById(trackId);
+ Instrument *instr = m_view->getDocument()->getStudio().
+ getInstrumentById(track->getInstrument());
+ if (!instr)
+ return 0;
+ return m_view->getKeyMapping();
+}
+
+
+}
diff --git a/src/gui/editors/matrix/MatrixStaff.h b/src/gui/editors/matrix/MatrixStaff.h
new file mode 100644
index 0000000..cd0a9dc
--- /dev/null
+++ b/src/gui/editors/matrix/MatrixStaff.h
@@ -0,0 +1,111 @@
+
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
+
+/*
+ Rosegarden
+ A MIDI and audio sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_MATRIXSTAFF_H_
+#define _RG_MATRIXSTAFF_H_
+
+#include "base/Staff.h"
+#include "gui/general/LinedStaff.h"
+#include "base/Event.h"
+
+
+class QCanvas;
+
+
+namespace Rosegarden
+{
+
+class ViewElement;
+class SnapGrid;
+class Segment;
+class MidiKeyMapping;
+class MatrixView;
+class MatrixElement;
+class Event;
+
+
+class MatrixStaff : public LinedStaff
+{
+public:
+ MatrixStaff(QCanvas *canvas,
+ Segment *segment,
+ SnapGrid *snapGrid,
+ int id,
+ int vResolution,
+ MatrixView *view);
+ virtual ~MatrixStaff();
+
+protected:
+ virtual int getLineCount() const;
+ virtual int getLegerLineCount() const;
+ virtual int getBottomLineHeight() const;
+ virtual int getHeightPerLine() const;
+ virtual bool elementsInSpaces() const;
+ virtual bool showBeatLines() const;
+
+ const MidiKeyMapping *getKeyMapping() const;
+
+ /**
+ * Override from Staff<T>
+ * Wrap only notes
+ */
+ virtual bool wrapEvent(Event*);
+
+ /**
+ * Override from Staff<T>
+ * Let tools know if their current element has gone
+ */
+ virtual void eventRemoved(const Segment *, Event *);
+
+ virtual ViewElement* makeViewElement(Event*);
+
+public:
+ LinedStaff::setResolution;
+
+// double getTimeScaleFactor() const { return m_scaleFactor * 2; } // TODO: GROSS HACK to enhance matrix resolution (see also in matrixview.cpp) - BREAKS MATRIX VIEW, see bug 1000595
+ double getTimeScaleFactor() const { return m_scaleFactor; }
+ void setTimeScaleFactor(double f) { m_scaleFactor = f; }
+
+ int getElementHeight() { return m_resolution; }
+
+ virtual void positionElements(timeT from,
+ timeT to);
+
+ virtual void positionElement(ViewElement*);
+
+ // Get an element for an Event
+ //
+ MatrixElement* getElement(Event *event);
+
+private:
+ double m_scaleFactor;
+
+ MatrixView *m_view;
+};
+
+
+}
+
+#endif
diff --git a/src/gui/editors/matrix/MatrixTool.cpp b/src/gui/editors/matrix/MatrixTool.cpp
new file mode 100644
index 0000000..b036559
--- /dev/null
+++ b/src/gui/editors/matrix/MatrixTool.cpp
@@ -0,0 +1,79 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
+
+/*
+ Rosegarden
+ A MIDI and audio sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "MatrixTool.h"
+
+#include "gui/general/EditTool.h"
+#include "MatrixView.h"
+#include <kaction.h>
+#include <qstring.h>
+
+
+namespace Rosegarden
+{
+
+MatrixTool::MatrixTool(const QString& menuName, MatrixView* parent)
+ : EditTool(menuName, parent),
+ m_mParentView(parent)
+{}
+
+void
+MatrixTool::slotSelectSelected()
+{
+ m_parentView->actionCollection()->action("select")->activate();
+}
+
+void
+MatrixTool::slotMoveSelected()
+{
+ m_parentView->actionCollection()->action("move")->activate();
+}
+
+void
+MatrixTool::slotEraseSelected()
+{
+ m_parentView->actionCollection()->action("erase")->activate();
+}
+
+void
+MatrixTool::slotResizeSelected()
+{
+ m_parentView->actionCollection()->action("resize")->activate();
+}
+
+void
+MatrixTool::slotDrawSelected()
+{
+ m_parentView->actionCollection()->action("draw")->activate();
+}
+
+const SnapGrid &
+MatrixTool::getSnapGrid() const
+{
+ return m_mParentView->getSnapGrid();
+}
+
+}
+#include "MatrixTool.moc"
diff --git a/src/gui/editors/matrix/MatrixTool.h b/src/gui/editors/matrix/MatrixTool.h
new file mode 100644
index 0000000..5127f57
--- /dev/null
+++ b/src/gui/editors/matrix/MatrixTool.h
@@ -0,0 +1,74 @@
+
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
+
+/*
+ Rosegarden
+ A MIDI and audio sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "MatrixToolBox.h"
+
+#include "gui/general/EditToolBox.h"
+#include "gui/general/EditTool.h"
+#include "MatrixView.h"
+#include "MatrixPainter.h"
+#include "MatrixEraser.h"
+#include "MatrixSelector.h"
+#include "MatrixMover.h"
+#include "MatrixResizer.h"
+
+#include <qstring.h>
+#include <kmessagebox.h>
+
+namespace Rosegarden
+{
+
+MatrixToolBox::MatrixToolBox(MatrixView* parent)
+ : EditToolBox(parent),
+ m_mParentView(parent)
+{}
+
+EditTool* MatrixToolBox::createTool(const QString& toolName)
+{
+ MatrixTool* tool = 0;
+
+ QString toolNamelc = toolName.lower();
+
+ if (toolNamelc == MatrixPainter::ToolName)
+
+ tool = new MatrixPainter(m_mParentView);
+
+ else if (toolNamelc == MatrixEraser::ToolName)
+
+ tool = new MatrixEraser(m_mParentView);
+
+ else if (toolNamelc == MatrixSelector::ToolName)
+
+ tool = new MatrixSelector(m_mParentView);
+
+ else if (toolNamelc == MatrixMover::ToolName)
+
+ tool = new MatrixMover(m_mParentView);
+
+ else if (toolNamelc == MatrixResizer::ToolName)
+
+ tool = new MatrixResizer(m_mParentView);
+
+ else {
+ KMessageBox::error(0, QString("MatrixToolBox::createTool : unrecognised toolname %1 (%2)")
+ .arg(toolName).arg(toolNamelc));
+ return 0;
+ }
+
+ m_tools.insert(toolName, tool);
+
+ return tool;
+
+}
+
+}
+#include "MatrixToolBox.moc"
diff --git a/src/gui/editors/matrix/MatrixToolBox.h b/src/gui/editors/matrix/MatrixToolBox.h
new file mode 100644
index 0000000..3bf0818
--- /dev/null
+++ b/src/gui/editors/matrix/MatrixToolBox.h
@@ -0,0 +1,60 @@
+
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
+
+/*
+ Rosegarden
+ A MIDI and audio sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "MatrixVLayout.h"
+#include "misc/Debug.h"
+
+#include "base/BaseProperties.h"
+#include "base/LayoutEngine.h"
+#include "base/Staff.h"
+#include "MatrixElement.h"
+#include "MatrixStaff.h"
+
+
+namespace Rosegarden
+{
+
+MatrixVLayout::MatrixVLayout()
+{}
+
+MatrixVLayout::~MatrixVLayout()
+{}
+
+void MatrixVLayout::reset()
+{}
+
+void MatrixVLayout::resetStaff(Staff&, timeT, timeT)
+{}
+
+void MatrixVLayout::scanStaff(Staff& staffBase,
+ timeT startTime, timeT endTime)
+{
+ MatrixStaff& staff = dynamic_cast<MatrixStaff&>(staffBase);
+
+ using BaseProperties::PITCH;
+
+ MatrixElementList *notes = staff.getViewElementList();
+
+ MatrixElementList::iterator from = notes->begin();
+ MatrixElementList::iterator to = notes->end();
+ MatrixElementList::iterator i;
+
+ if (startTime != endTime) {
+ from = notes->findNearestTime(startTime);
+ if (from == notes->end())
+ from = notes->begin();
+ to = notes->findTime(endTime);
+ }
+
+ MATRIX_DEBUG << "MatrixVLayout::scanStaff : id = "
+ << staff.getId() << endl;
+
+
+ for (i = from; i != to; ++i) {
+
+ MatrixElement *el = dynamic_cast<MatrixElement*>((*i));
+
+ if (!el->isNote())
+ continue; // notes only
+
+ long pitch = 60;
+ el->event()->get
+ <Int>(PITCH, pitch);
+
+ int y = staff.getLayoutYForHeight(pitch) - staff.getElementHeight() / 2;
+
+ el->setLayoutY(y);
+ el->setHeight(staff.getElementHeight());
+ }
+
+}
+
+void MatrixVLayout::finishLayout(timeT, timeT)
+{}
+
+const int MatrixVLayout::minMIDIPitch = 0;
+const int MatrixVLayout::maxMIDIPitch = 127;
+
+}
diff --git a/src/gui/editors/matrix/MatrixVLayout.h b/src/gui/editors/matrix/MatrixVLayout.h
new file mode 100644
index 0000000..a33e0d1
--- /dev/null
+++ b/src/gui/editors/matrix/MatrixVLayout.h
@@ -0,0 +1,91 @@
+
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
+
+/*
+ Rosegarden
+ A MIDI and audio sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "MatrixView.h"
+
+#include "base/BaseProperties.h"
+#include "misc/Debug.h"
+#include "misc/Strings.h"
+#include "base/AudioLevel.h"
+#include "base/Clipboard.h"
+#include "base/Composition.h"
+#include "base/Event.h"
+#include "base/Instrument.h"
+#include "base/LayoutEngine.h"
+#include "base/MidiProgram.h"
+#include "base/NotationTypes.h"
+#include "base/Profiler.h"
+#include "base/PropertyName.h"
+#include "base/BasicQuantizer.h"
+#include "base/LegatoQuantizer.h"
+#include "base/RealTime.h"
+#include "base/RulerScale.h"
+#include "base/Segment.h"
+#include "base/Selection.h"
+#include "base/SnapGrid.h"
+#include "base/Staff.h"
+#include "base/Studio.h"
+#include "base/Track.h"
+#include "commands/edit/ChangeVelocityCommand.h"
+#include "commands/edit/ClearTriggersCommand.h"
+#include "commands/edit/CollapseNotesCommand.h"
+#include "commands/edit/CopyCommand.h"
+#include "commands/edit/CutCommand.h"
+#include "commands/edit/EraseCommand.h"
+#include "commands/edit/EventQuantizeCommand.h"
+#include "commands/edit/EventUnquantizeCommand.h"
+#include "commands/edit/PasteEventsCommand.h"
+#include "commands/edit/SelectionPropertyCommand.h"
+#include "commands/edit/SetTriggerCommand.h"
+#include "commands/matrix/MatrixInsertionCommand.h"
+#include "document/RosegardenGUIDoc.h"
+#include "document/ConfigGroups.h"
+#include "gui/application/RosegardenGUIApp.h"
+#include "gui/dialogs/EventFilterDialog.h"
+#include "gui/dialogs/EventParameterDialog.h"
+#include "gui/dialogs/QuantizeDialog.h"
+#include "gui/dialogs/TriggerSegmentDialog.h"
+#include "gui/editors/guitar/Chord.h"
+#include "gui/editors/notation/NotationElement.h"
+#include "gui/editors/notation/NotationStrings.h"
+#include "gui/editors/notation/NotePixmapFactory.h"
+#include "gui/editors/parameters/InstrumentParameterBox.h"
+#include "gui/rulers/StandardRuler.h"
+#include "gui/general/ActiveItem.h"
+#include "gui/general/EditViewBase.h"
+#include "gui/general/EditView.h"
+#include "gui/general/GUIPalette.h"
+#include "gui/general/MidiPitchLabel.h"
+#include "gui/kdeext/KTmpStatusMsg.h"
+#include "gui/rulers/ChordNameRuler.h"
+#include "gui/rulers/LoopRuler.h"
+#include "gui/rulers/PercussionPitchRuler.h"
+#include "gui/rulers/PitchRuler.h"
+#include "gui/rulers/PropertyBox.h"
+#include "gui/rulers/PropertyViewRuler.h"
+#include "gui/rulers/TempoRuler.h"
+#include "gui/studio/StudioControl.h"
+#include "gui/widgets/QDeferScrollView.h"
+#include "MatrixCanvasView.h"
+#include "MatrixElement.h"
+#include "MatrixEraser.h"
+#include "MatrixHLayout.h"
+#include "MatrixMover.h"
+#include "MatrixPainter.h"
+#include "MatrixResizer.h"
+#include "MatrixSelector.h"
+#include "MatrixStaff.h"
+#include "MatrixToolBox.h"
+#include "MatrixVLayout.h"
+#include "PianoKeyboard.h"
+#include "sound/MappedEvent.h"
+#include "sound/SequencerDataBlock.h"
+#include <klocale.h>
+#include <kstddirs.h>
+#include <kaction.h>
+#include <kcombobox.h>
+#include <kconfig.h>
+#include <kdockwidget.h>
+#include <kglobal.h>
+#include <kmessagebox.h>
+#include <kstatusbar.h>
+#include <ktoolbar.h>
+#include <kxmlguiclient.h>
+#include <qcanvas.h>
+#include <qcursor.h>
+#include <qdialog.h>
+#include <qlayout.h>
+#include <qiconset.h>
+#include <qlabel.h>
+#include <qpixmap.h>
+#include <qpoint.h>
+#include <qscrollview.h>
+#include <qsize.h>
+#include <qslider.h>
+#include <qstring.h>
+#include <qwidget.h>
+#include <qwmatrix.h>
+
+
+namespace Rosegarden
+{
+
+static double xorigin = 0.0;
+
+
+MatrixView::MatrixView(RosegardenGUIDoc *doc,
+ std::vector<Segment *> segments,
+ QWidget *parent,
+ bool drumMode)
+ : EditView(doc, segments, 3, parent, "matrixview"),
+ m_hlayout(&doc->getComposition()),
+ m_referenceRuler(new ZoomableMatrixHLayoutRulerScale(m_hlayout)),
+ m_vlayout(),
+ m_snapGrid(new SnapGrid(&m_hlayout)),
+ m_lastEndMarkerTime(0),
+ m_hoveredOverAbsoluteTime(0),
+ m_hoveredOverNoteName(0),
+ m_selectionCounter(0),
+ m_insertModeLabel(0),
+ m_haveHoveredOverNote(false),
+ m_previousEvPitch(0),
+ m_dockLeft(0),
+ m_canvasView(0),
+ m_pianoView(0),
+ m_localMapping(0),
+ m_lastNote(0),
+ m_quantizations(BasicQuantizer::getStandardQuantizations()),
+ m_chordNameRuler(0),
+ m_tempoRuler(0),
+ m_playTracking(true),
+ m_dockVisible(true),
+ m_drumMode(drumMode),
+ m_mouseInCanvasView(false)
+{
+ RG_DEBUG << "MatrixView ctor: drumMode " << drumMode << "\n";
+
+ QString pixmapDir = KGlobal::dirs()->findResource("appdata", "pixmaps/toolbar");
+ QPixmap matrixPixmap(pixmapDir + "/matrix.xpm");
+
+ m_dockLeft = createDockWidget("params dock", matrixPixmap, 0L,
+ i18n("Instrument Parameters"));
+ m_dockLeft->manualDock(m_mainDockWidget, // dock target
+ KDockWidget::DockLeft, // dock site
+ 20); // relation target/this (in percent)
+
+ connect(m_dockLeft, SIGNAL(iMBeingClosed()),
+ this, SLOT(slotParametersClosed()));
+ connect(m_dockLeft, SIGNAL(hasUndocked()),
+ this, SLOT(slotParametersClosed()));
+ // Apparently, hasUndocked() is emitted when the dock widget's
+ // 'close' button on the dock handle is clicked.
+ connect(m_mainDockWidget, SIGNAL(docking(KDockWidget*, KDockWidget::DockPosition)),
+ this, SLOT(slotParametersDockedBack(KDockWidget*, KDockWidget::DockPosition)));
+
+ Composition &comp = doc->getComposition();
+
+ m_toolBox = new MatrixToolBox(this);
+
+ initStatusBar();
+
+ connect(m_toolBox, SIGNAL(showContextHelp(const QString &)),
+ this, SLOT(slotToolHelpChanged(const QString &)));
+
+ QCanvas *tCanvas = new QCanvas(this);
+
+ m_config->setGroup(MatrixViewConfigGroup);
+ if (m_config->readBoolEntry("backgroundtextures-1.6-plus", true)) {
+ QPixmap background;
+ QString pixmapDir =
+ KGlobal::dirs()->findResource("appdata", "pixmaps/");
+ // We now use a lined background for the non-percussion matrix,
+ // suggested and supplied by Alessandro Preziosi
+ QString backgroundPixmap = isDrumMode() ? "bg-paper-white.xpm" : "bg-matrix-lines.xpm";
+ if (background.load(QString("%1/misc/%2").
+ arg(pixmapDir, backgroundPixmap))) {
+ tCanvas->setBackgroundPixmap(background);
+ }
+ }
+
+ MATRIX_DEBUG << "MatrixView : creating staff\n";
+
+ Track *track =
+ comp.getTrackById(segments[0]->getTrack());
+
+ Instrument *instr = getDocument()->getStudio().
+ getInstrumentById(track->getInstrument());
+
+ int resolution = 8;
+
+ if (isDrumMode() && instr && instr->getKeyMapping()) {
+ resolution = 11;
+ }
+
+ for (unsigned int i = 0; i < segments.size(); ++i) {
+ m_staffs.push_back(new MatrixStaff(tCanvas,
+ segments[i],
+ m_snapGrid,
+ i,
+ resolution,
+ this));
+ // staff has one too many rows to avoid a half-row at the top:
+ m_staffs[i]->setY( -resolution / 2);
+ //!!! if (isDrumMode()) m_staffs[i]->setX(resolution);
+ if (i == 0)
+ m_staffs[i]->setCurrent(true);
+ }
+
+ MATRIX_DEBUG << "MatrixView : creating canvas view\n";
+
+ const MidiKeyMapping *mapping = 0;
+
+ if (instr) {
+ mapping = instr->getKeyMapping();
+ if (mapping) {
+ RG_DEBUG << "MatrixView: Instrument has key mapping: "
+ << mapping->getName() << endl;
+ m_localMapping = new MidiKeyMapping(*mapping);
+ extendKeyMapping();
+ } else {
+ RG_DEBUG << "MatrixView: Instrument has no key mapping\n";
+ }
+ }
+
+ m_pianoView = new QDeferScrollView(getCentralWidget());
+
+ QWidget* vport = m_pianoView->viewport();
+
+ if (isDrumMode() && mapping &&
+ !m_localMapping->getMap().empty()) {
+ m_pitchRuler = new PercussionPitchRuler(vport,
+ m_localMapping,
+ resolution); // line spacing
+ } else {
+ m_pitchRuler = new PianoKeyboard(vport);
+ }
+
+ m_pianoView->setVScrollBarMode(QScrollView::AlwaysOff);
+ m_pianoView->setHScrollBarMode(QScrollView::AlwaysOff);
+ m_pianoView->addChild(m_pitchRuler);
+ m_pianoView->setFixedWidth(m_pianoView->contentsWidth());
+
+ m_grid->addWidget(m_pianoView, CANVASVIEW_ROW, 1);
+
+ m_parameterBox = new InstrumentParameterBox(getDocument(), m_dockLeft);
+ m_dockLeft->setWidget(m_parameterBox);
+
+ RosegardenGUIApp *app = RosegardenGUIApp::self();
+ connect(app,
+ SIGNAL(pluginSelected(InstrumentId, int, int)),
+ m_parameterBox,
+ SLOT(slotPluginSelected(InstrumentId, int, int)));
+ connect(app,
+ SIGNAL(pluginBypassed(InstrumentId, int, bool)),
+ m_parameterBox,
+ SLOT(slotPluginBypassed(InstrumentId, int, bool)));
+ connect(app,
+ SIGNAL(instrumentParametersChanged(InstrumentId)),
+ m_parameterBox,
+ SLOT(slotInstrumentParametersChanged(InstrumentId)));
+ connect(m_parameterBox,
+ SIGNAL(instrumentParametersChanged(InstrumentId)),
+ app,
+ SIGNAL(instrumentParametersChanged(InstrumentId)));
+ connect(m_parameterBox,
+ SIGNAL(selectPlugin(QWidget *, InstrumentId, int)),
+ app,
+ SLOT(slotShowPluginDialog(QWidget *, InstrumentId, int)));
+ connect(m_parameterBox,
+ SIGNAL(showPluginGUI(InstrumentId, int)),
+ app,
+ SLOT(slotShowPluginGUI(InstrumentId, int)));
+ connect(parent, // RosegardenGUIView
+ SIGNAL(checkTrackAssignments()),
+ this,
+ SLOT(slotCheckTrackAssignments()));
+
+ // Assign the instrument
+ //
+ m_parameterBox->useInstrument(instr);
+
+ if (m_drumMode) {
+ connect(m_parameterBox,
+ SIGNAL(instrumentPercussionSetChanged(Instrument *)),
+ this,
+ SLOT(slotPercussionSetChanged(Instrument *)));
+ }
+
+ // Set the snap grid from the stored size in the segment
+ //
+ int snapGridSize = m_staffs[0]->getSegment().getSnapGridSize();
+
+ MATRIX_DEBUG << "MatrixView : Snap Grid Size = " << snapGridSize << endl;
+
+ if (snapGridSize != -1) {
+ m_snapGrid->setSnapTime(snapGridSize);
+ } else {
+ m_config->setGroup(MatrixViewConfigGroup);
+ snapGridSize = m_config->readNumEntry
+ ("Snap Grid Size", SnapGrid::SnapToBeat);
+ m_snapGrid->setSnapTime(snapGridSize);
+ m_staffs[0]->getSegment().setSnapGridSize(snapGridSize);
+ }
+
+ m_canvasView = new MatrixCanvasView(*m_staffs[0],
+ m_snapGrid,
+ m_drumMode,
+ tCanvas,
+ getCentralWidget());
+ setCanvasView(m_canvasView);
+
+ // do this after we have a canvas
+ setupActions();
+ setupAddControlRulerMenu();
+
+ stateChanged("parametersbox_closed", KXMLGUIClient::StateReverse);
+
+ // tool bars
+ initActionsToolbar();
+ initZoomToolbar();
+
+ // Connect vertical scrollbars between matrix and piano
+ //
+ connect(m_canvasView->verticalScrollBar(), SIGNAL(valueChanged(int)),
+ this, SLOT(slotVerticalScrollPianoKeyboard(int)));
+
+ connect(m_canvasView->verticalScrollBar(), SIGNAL(sliderMoved(int)),
+ this, SLOT(slotVerticalScrollPianoKeyboard(int)));
+
+ connect(m_canvasView, SIGNAL(zoomIn()), this, SLOT(slotZoomIn()));
+ connect(m_canvasView, SIGNAL(zoomOut()), this, SLOT(slotZoomOut()));
+
+ connect(m_pianoView, SIGNAL(gotWheelEvent(QWheelEvent*)),
+ m_canvasView, SLOT(slotExternalWheelEvent(QWheelEvent*)));
+
+ // ensure the piano keyb keeps the right margins when the user toggles
+ // the canvas view rulers
+ //
+ connect(m_canvasView, SIGNAL(bottomWidgetHeightChanged(int)),
+ this, SLOT(slotCanvasBottomWidgetHeightChanged(int)));
+
+ connect(m_canvasView, SIGNAL(mouseEntered()),
+ this, SLOT(slotMouseEnteredCanvasView()));
+
+ connect(m_canvasView, SIGNAL(mouseLeft()),
+ this, SLOT(slotMouseLeftCanvasView()));
+
+ /*
+ QObject::connect
+ (getCanvasView(), SIGNAL(activeItemPressed(QMouseEvent*, QCanvasItem*)),
+ this, SLOT (activeItemPressed(QMouseEvent*, QCanvasItem*)));
+ */
+
+ QObject::connect
+ (getCanvasView(),
+ SIGNAL(mousePressed(timeT,
+ int, QMouseEvent*, MatrixElement*)),
+ this,
+ SLOT(slotMousePressed(timeT,
+ int, QMouseEvent*, MatrixElement*)));
+
+ QObject::connect
+ (getCanvasView(),
+ SIGNAL(mouseMoved(timeT, int, QMouseEvent*)),
+ this,
+ SLOT(slotMouseMoved(timeT, int, QMouseEvent*)));
+
+ QObject::connect
+ (getCanvasView(),
+ SIGNAL(mouseReleased(timeT, int, QMouseEvent*)),
+ this,
+ SLOT(slotMouseReleased(timeT, int, QMouseEvent*)));
+
+ QObject::connect
+ (getCanvasView(), SIGNAL(hoveredOverNoteChanged(int, bool, timeT)),
+ this, SLOT(slotHoveredOverNoteChanged(int, bool, timeT)));
+
+ QObject::connect
+ (m_pitchRuler, SIGNAL(hoveredOverKeyChanged(unsigned int)),
+ this, SLOT (slotHoveredOverKeyChanged(unsigned int)));
+
+ QObject::connect
+ (m_pitchRuler, SIGNAL(keyPressed(unsigned int, bool)),
+ this, SLOT (slotKeyPressed(unsigned int, bool)));
+
+ QObject::connect
+ (m_pitchRuler, SIGNAL(keySelected(unsigned int, bool)),
+ this, SLOT (slotKeySelected(unsigned int, bool)));
+
+ QObject::connect
+ (m_pitchRuler, SIGNAL(keyReleased(unsigned int, bool)),
+ this, SLOT (slotKeyReleased(unsigned int, bool)));
+
+ QObject::connect
+ (getCanvasView(), SIGNAL(hoveredOverAbsoluteTimeChanged(unsigned int)),
+ this, SLOT (slotHoveredOverAbsoluteTimeChanged(unsigned int)));
+
+ QObject::connect
+ (doc, SIGNAL(pointerPositionChanged(timeT)),
+ this, SLOT(slotSetPointerPosition(timeT)));
+
+ MATRIX_DEBUG << "MatrixView : applying layout\n";
+
+ bool layoutApplied = applyLayout();
+ if (!layoutApplied)
+ KMessageBox::sorry(0, i18n("Couldn't apply piano roll layout"));
+ else {
+ MATRIX_DEBUG << "MatrixView : rendering elements\n";
+ for (unsigned int i = 0; i < m_staffs.size(); ++i) {
+
+ m_staffs[i]->positionAllElements();
+ m_staffs[i]->getSegment().getRefreshStatus
+ (m_segmentsRefreshStatusIds[i]).setNeedsRefresh(false);
+ }
+ }
+
+ StandardRuler *topStandardRuler = new StandardRuler(getDocument(),
+ &m_hlayout, int(xorigin), 25,
+ false, getCentralWidget());
+ topStandardRuler->setSnapGrid(m_snapGrid);
+ setTopStandardRuler(topStandardRuler);
+
+ StandardRuler *bottomStandardRuler = new StandardRuler(getDocument(),
+ &m_hlayout, 0, 25,
+ true, getBottomWidget());
+ bottomStandardRuler->setSnapGrid(m_snapGrid);
+ setBottomStandardRuler(bottomStandardRuler);
+
+ topStandardRuler->connectRulerToDocPointer(doc);
+ bottomStandardRuler->connectRulerToDocPointer(doc);
+
+ // Disconnect the default connections for this signal from the
+ // top ruler, and connect our own instead
+
+ QObject::disconnect
+ (topStandardRuler->getLoopRuler(),
+ SIGNAL(setPointerPosition(timeT)), 0, 0);
+
+ QObject::connect
+ (topStandardRuler->getLoopRuler(),
+ SIGNAL(setPointerPosition(timeT)),
+ this, SLOT(slotSetInsertCursorPosition(timeT)));
+
+ QObject::connect
+ (topStandardRuler,
+ SIGNAL(dragPointerToPosition(timeT)),
+ this, SLOT(slotSetInsertCursorPosition(timeT)));
+
+ topStandardRuler->getLoopRuler()->setBackgroundColor
+ (GUIPalette::getColour(GUIPalette::InsertCursorRuler));
+
+ connect(topStandardRuler->getLoopRuler(), SIGNAL(startMouseMove(int)),
+ m_canvasView, SLOT(startAutoScroll(int)));
+ connect(topStandardRuler->getLoopRuler(), SIGNAL(stopMouseMove()),
+ m_canvasView, SLOT(stopAutoScroll()));
+
+ connect(bottomStandardRuler->getLoopRuler(), SIGNAL(startMouseMove(int)),
+ m_canvasView, SLOT(startAutoScroll(int)));
+ connect(bottomStandardRuler->getLoopRuler(), SIGNAL(stopMouseMove()),
+ m_canvasView, SLOT(stopAutoScroll()));
+ connect(m_bottomStandardRuler, SIGNAL(dragPointerToPosition(timeT)),
+ this, SLOT(slotSetPointerPosition(timeT)));
+
+ // Force height for the moment
+ //
+ m_pitchRuler->setFixedHeight(canvas()->height());
+
+
+ updateViewCaption();
+
+ // Add a velocity ruler
+ //
+ //!!! addPropertyViewRuler(BaseProperties::VELOCITY);
+
+ m_chordNameRuler = new ChordNameRuler
+ (m_referenceRuler, doc, segments, 0, 20, getCentralWidget());
+ m_chordNameRuler->setStudio(&getDocument()->getStudio());
+ addRuler(m_chordNameRuler);
+
+ m_tempoRuler = new TempoRuler
+ (m_referenceRuler, doc, this, 0, 24, false, getCentralWidget());
+ static_cast<TempoRuler *>(m_tempoRuler)->connectSignals();
+ addRuler(m_tempoRuler);
+
+ stateChanged("have_selection", KXMLGUIClient::StateReverse);
+ slotTestClipboard();
+
+ timeT start = doc->getComposition().getLoopStart();
+ timeT end = doc->getComposition().getLoopEnd();
+ m_topStandardRuler->getLoopRuler()->slotSetLoopMarker(start, end);
+ m_bottomStandardRuler->getLoopRuler()->slotSetLoopMarker(start, end);
+
+ setCurrentSelection(0, false);
+
+ // Change this if the matrix view ever has its own page
+ // in the config dialog.
+ setConfigDialogPageIndex(0);
+
+ // default zoom
+ m_config->setGroup(MatrixViewConfigGroup);
+ double zoom = m_config->readDoubleNumEntry("Zoom Level",
+ m_hZoomSlider->getCurrentSize());
+ m_hZoomSlider->setSize(zoom);
+ m_referenceRuler->setHScaleFactor(zoom);
+
+ // Scroll view to centre middle-C and warp to pointer position
+ //
+ m_canvasView->scrollBy(0, m_staffs[0]->getCanvasYForHeight(60) / 2);
+
+ slotSetPointerPosition(comp.getPosition());
+
+ // All toolbars should be created before this is called
+ setAutoSaveSettings("MatrixView", true);
+
+ readOptions();
+ setOutOfCtor();
+
+ // Property and Control Rulers
+ //
+ if (getCurrentSegment()->getViewFeatures())
+ slotShowVelocityControlRuler();
+ setupControllerTabs();
+
+ setRewFFwdToAutoRepeat();
+ slotCompositionStateUpdate();
+}
+
+MatrixView::~MatrixView()
+{
+ slotSaveOptions();
+
+ delete m_chordNameRuler;
+
+ for (unsigned int i = 0; i < m_staffs.size(); ++i) {
+ delete m_staffs[i]; // this will erase all "notes" canvas items
+ }
+
+ // This looks silly but the reason is that on destruction of the
+ // MatrixCanvasView, setCanvas() is called (this is in
+ // ~QCanvasView so we can't do anything about it). This calls
+ // QCanvasView::updateContentsSize(), which in turn updates the
+ // view's scrollbars, hence calling QScrollBar::setValue(), and
+ // sending the QSCrollbar::valueChanged() signal. But we have a
+ // slot connected to that signal
+ // (MatrixView::slotVerticalScrollPianoKeyboard), which scrolls
+ // the pianoView. However at this stage the pianoView has already
+ // been deleted, so a likely outcome is a crash.
+ //
+ // A solution is to zero out m_pianoView here, and to check if
+ // it's non null in slotVerticalScrollPianoKeyboard.
+ //
+ m_pianoView = 0;
+
+ delete m_snapGrid;
+
+ if (m_localMapping)
+ delete m_localMapping;
+}
+
+void MatrixView::slotSaveOptions()
+{
+ m_config->setGroup(MatrixViewConfigGroup);
+
+ m_config->writeEntry("Show Chord Name Ruler", getToggleAction("show_chords_ruler")->isChecked());
+ m_config->writeEntry("Show Tempo Ruler", getToggleAction("show_tempo_ruler")->isChecked());
+ m_config->writeEntry("Show Parameters", m_dockVisible);
+ //getToggleAction("m_dockLeft->isVisible());
+
+ m_config->sync();
+}
+
+void MatrixView::readOptions()
+{
+ EditView::readOptions();
+ m_config->setGroup(MatrixViewConfigGroup);
+
+ bool opt = false;
+
+ opt = m_config->readBoolEntry("Show Chord Name Ruler", false);
+ getToggleAction("show_chords_ruler")->setChecked(opt);
+ slotToggleChordsRuler();
+
+ opt = m_config->readBoolEntry("Show Tempo Ruler", true);
+ getToggleAction("show_tempo_ruler")->setChecked(opt);
+ slotToggleTempoRuler();
+
+ opt = m_config->readBoolEntry("Show Parameters", true);
+ if (!opt) {
+ m_dockLeft->undock();
+ m_dockLeft->hide();
+ stateChanged("parametersbox_closed", KXMLGUIClient::StateNoReverse);
+ m_dockVisible = false;
+ }
+
+}
+
+void MatrixView::setupActions()
+{
+ EditViewBase::setupActions("matrix.rc");
+ EditView::setupActions();
+
+ //
+ // Edition tools (eraser, selector...)
+ //
+ KRadioAction* toolAction = 0;
+
+ QString pixmapDir = KGlobal::dirs()->findResource("appdata", "pixmaps/");
+ QIconSet icon(QPixmap(pixmapDir + "/toolbar/select.xpm"));
+
+ toolAction = new KRadioAction(i18n("&Select and Edit"), icon, Key_F2,
+ this, SLOT(slotSelectSelected()),
+ actionCollection(), "select");
+ toolAction->setExclusiveGroup("tools");
+
+ toolAction = new KRadioAction(i18n("&Draw"), "pencil", Key_F3,
+ this, SLOT(slotPaintSelected()),
+ actionCollection(), "draw");
+ toolAction->setExclusiveGroup("tools");
+
+ toolAction = new KRadioAction(i18n("&Erase"), "eraser", Key_F4,
+ this, SLOT(slotEraseSelected()),
+ actionCollection(), "erase");
+ toolAction->setExclusiveGroup("tools");
+
+ toolAction = new KRadioAction(i18n("&Move"), "move", Key_F5,
+ this, SLOT(slotMoveSelected()),
+ actionCollection(), "move");
+ toolAction->setExclusiveGroup("tools");
+
+ QCanvasPixmap pixmap(pixmapDir + "/toolbar/resize.xpm");
+ icon = QIconSet(pixmap);
+ toolAction = new KRadioAction(i18n("Resi&ze"), icon, Key_F6,
+ this, SLOT(slotResizeSelected()),
+ actionCollection(), "resize");
+ toolAction->setExclusiveGroup("tools");
+
+ icon = QIconSet(NotePixmapFactory::toQPixmap(NotePixmapFactory::makeToolbarPixmap("chord")));
+ (new KToggleAction(i18n("C&hord Insert Mode"), icon, Key_H,
+ this, SLOT(slotUpdateInsertModeStatus()),
+ actionCollection(), "chord_mode"))->
+ setChecked(false);
+
+ pixmap.load(pixmapDir + "/toolbar/step_by_step.xpm");
+ icon = QIconSet(pixmap);
+ new KToggleAction(i18n("Ste&p Recording"), icon, 0, this,
+ SLOT(slotToggleStepByStep()), actionCollection(),
+ "toggle_step_by_step");
+
+ pixmap.load(pixmapDir + "/toolbar/quantize.png");
+ icon = QIconSet(pixmap);
+ new KAction(EventQuantizeCommand::getGlobalName(), icon, Key_Equal, this,
+ SLOT(slotTransformsQuantize()), actionCollection(),
+ "quantize");
+
+ new KAction(i18n("Repeat Last Quantize"), Key_Plus, this,
+ SLOT(slotTransformsRepeatQuantize()), actionCollection(),
+ "repeat_quantize");
+
+ new KAction(CollapseNotesCommand::getGlobalName(), Key_Equal + CTRL, this,
+ SLOT(slotTransformsCollapseNotes()), actionCollection(),
+ "collapse_notes");
+
+ new KAction(i18n("&Legato"), Key_Minus, this,
+ SLOT(slotTransformsLegato()), actionCollection(),
+ "legatoize");
+
+ new KAction(ChangeVelocityCommand::getGlobalName(10), 0,
+ Key_Up + SHIFT, this,
+ SLOT(slotVelocityUp()), actionCollection(),
+ "velocity_up");
+
+ new KAction(ChangeVelocityCommand::getGlobalName( -10), 0,
+ Key_Down + SHIFT, this,
+ SLOT(slotVelocityDown()), actionCollection(),
+ "velocity_down");
+
+ new KAction(i18n("Set to Current Velocity"), 0, this,
+ SLOT(slotSetVelocitiesToCurrent()), actionCollection(),
+ "set_to_current_velocity");
+
+ new KAction(i18n("Set Event &Velocities..."), 0, this,
+ SLOT(slotSetVelocities()), actionCollection(),
+ "set_velocities");
+
+ new KAction(i18n("Trigger Se&gment..."), 0, this,
+ SLOT(slotTriggerSegment()), actionCollection(),
+ "trigger_segment");
+
+ new KAction(i18n("Remove Triggers..."), 0, this,
+ SLOT(slotRemoveTriggers()), actionCollection(),
+ "remove_trigger");
+
+ new KAction(i18n("Select &All"), Key_A + CTRL, this,
+ SLOT(slotSelectAll()), actionCollection(),
+ "select_all");
+
+ new KAction(i18n("&Delete"), Key_Delete, this,
+ SLOT(slotEditDelete()), actionCollection(),
+ "delete");
+
+ new KAction(i18n("Cursor &Back"), 0, Key_Left, this,
+ SLOT(slotStepBackward()), actionCollection(),
+ "cursor_back");
+
+ new KAction(i18n("Cursor &Forward"), 0, Key_Right, this,
+ SLOT(slotStepForward()), actionCollection(),
+ "cursor_forward");
+
+ new KAction(i18n("Cursor Ba&ck Bar"), 0, Key_Left + CTRL, this,
+ SLOT(slotJumpBackward()), actionCollection(),
+ "cursor_back_bar");
+
+ new KAction(i18n("Cursor For&ward Bar"), 0, Key_Right + CTRL, this,
+ SLOT(slotJumpForward()), actionCollection(),
+ "cursor_forward_bar");
+
+ new KAction(i18n("Cursor Back and Se&lect"), SHIFT + Key_Left, this,
+ SLOT(slotExtendSelectionBackward()), actionCollection(),
+ "extend_selection_backward");
+
+ new KAction(i18n("Cursor Forward and &Select"), SHIFT + Key_Right, this,
+ SLOT(slotExtendSelectionForward()), actionCollection(),
+ "extend_selection_forward");
+
+ new KAction(i18n("Cursor Back Bar and Select"), SHIFT + CTRL + Key_Left, this,
+ SLOT(slotExtendSelectionBackwardBar()), actionCollection(),
+ "extend_selection_backward_bar");
+
+ new KAction(i18n("Cursor Forward Bar and Select"), SHIFT + CTRL + Key_Right, this,
+ SLOT(slotExtendSelectionForwardBar()), actionCollection(),
+ "extend_selection_forward_bar");
+
+ new KAction(i18n("Cursor to St&art"), 0,
+ /* #1025717: conflicting meanings for ctrl+a - dupe with Select All
+ Key_A + CTRL, */ this,
+ SLOT(slotJumpToStart()), actionCollection(),
+ "cursor_start");
+
+ new KAction(i18n("Cursor to &End"), 0, Key_E + CTRL, this,
+ SLOT(slotJumpToEnd()), actionCollection(),
+ "cursor_end");
+
+ icon = QIconSet(NotePixmapFactory::toQPixmap(NotePixmapFactory::makeToolbarPixmap
+ ("transport-cursor-to-pointer")));
+ new KAction(i18n("Cursor to &Playback Pointer"), icon, 0, this,
+ SLOT(slotJumpCursorToPlayback()), actionCollection(),
+ "cursor_to_playback_pointer");
+
+ icon = QIconSet(NotePixmapFactory::toQPixmap(NotePixmapFactory::makeToolbarPixmap
+ ("transport-play")));
+ KAction *play = new KAction(i18n("&Play"), icon, Key_Enter, this,
+ SIGNAL(play()), actionCollection(), "play");
+ // Alternative shortcut for Play
+ KShortcut playShortcut = play->shortcut();
+ playShortcut.append( KKey(Key_Return + CTRL) );
+ play->setShortcut(playShortcut);
+
+ icon = QIconSet(NotePixmapFactory::toQPixmap(NotePixmapFactory::makeToolbarPixmap
+ ("transport-stop")));
+ new KAction(i18n("&Stop"), icon, Key_Insert, this,
+ SIGNAL(stop()), actionCollection(), "stop");
+
+ icon = QIconSet(NotePixmapFactory::toQPixmap(NotePixmapFactory::makeToolbarPixmap
+ ("transport-rewind")));
+ new KAction(i18n("Re&wind"), icon, Key_End, this,
+ SIGNAL(rewindPlayback()), actionCollection(),
+ "playback_pointer_back_bar");
+
+ icon = QIconSet(NotePixmapFactory::toQPixmap(NotePixmapFactory::makeToolbarPixmap
+ ("transport-ffwd")));
+ new KAction(i18n("&Fast Forward"), icon, Key_PageDown, this,
+ SIGNAL(fastForwardPlayback()), actionCollection(),
+ "playback_pointer_forward_bar");
+
+ icon = QIconSet(NotePixmapFactory::toQPixmap(NotePixmapFactory::makeToolbarPixmap
+ ("transport-rewind-end")));
+ new KAction(i18n("Rewind to &Beginning"), icon, 0, this,
+ SIGNAL(rewindPlaybackToBeginning()), actionCollection(),
+ "playback_pointer_start");
+
+ icon = QIconSet(NotePixmapFactory::toQPixmap(NotePixmapFactory::makeToolbarPixmap
+ ("transport-ffwd-end")));
+ new KAction(i18n("Fast Forward to &End"), icon, 0, this,
+ SIGNAL(fastForwardPlaybackToEnd()), actionCollection(),
+ "playback_pointer_end");
+
+ icon = QIconSet(NotePixmapFactory::toQPixmap(NotePixmapFactory::makeToolbarPixmap
+ ("transport-pointer-to-cursor")));
+ new KAction(i18n("Playback Pointer to &Cursor"), icon, 0, this,
+ SLOT(slotJumpPlaybackToCursor()), actionCollection(),
+ "playback_pointer_to_cursor");
+
+ icon = QIconSet(NotePixmapFactory::toQPixmap(NotePixmapFactory::makeToolbarPixmap
+ ("transport-solo")));
+ new KToggleAction(i18n("&Solo"), icon, 0, this,
+ SLOT(slotToggleSolo()), actionCollection(),
+ "toggle_solo");
+
+ icon = QIconSet(NotePixmapFactory::toQPixmap(NotePixmapFactory::makeToolbarPixmap
+ ("transport-tracking")));
+ (new KToggleAction(i18n("Scro&ll to Follow Playback"), icon, Key_Pause, this,
+ SLOT(slotToggleTracking()), actionCollection(),
+ "toggle_tracking"))->setChecked(m_playTracking);
+
+ icon = QIconSet(NotePixmapFactory::toQPixmap(NotePixmapFactory::makeToolbarPixmap
+ ("transport-panic")));
+ new KAction(i18n("Panic"), icon, Key_P + CTRL + ALT, this,
+ SIGNAL(panic()), actionCollection(), "panic");
+
+ new KAction(i18n("Set Loop to Selection"), Key_Semicolon + CTRL, this,
+ SLOT(slotPreviewSelection()), actionCollection(),
+ "preview_selection");
+
+ new KAction(i18n("Clear L&oop"), Key_Colon + CTRL, this,
+ SLOT(slotClearLoop()), actionCollection(),
+ "clear_loop");
+
+ new KAction(i18n("Clear Selection"), Key_Escape, this,
+ SLOT(slotClearSelection()), actionCollection(),
+ "clear_selection");
+
+ // icon = QIconSet(QCanvasPixmap(pixmapDir + "/toolbar/eventfilter.xpm"));
+ new KAction(i18n("&Filter Selection"), "filter", Key_F + CTRL, this,
+ SLOT(slotFilterSelection()), actionCollection(),
+ "filter_selection");
+
+ timeT crotchetDuration = Note(Note::Crotchet).getDuration();
+ m_snapValues.push_back(SnapGrid::NoSnap);
+ m_snapValues.push_back(SnapGrid::SnapToUnit);
+ m_snapValues.push_back(crotchetDuration / 16);
+ m_snapValues.push_back(crotchetDuration / 12);
+ m_snapValues.push_back(crotchetDuration / 8);
+ m_snapValues.push_back(crotchetDuration / 6);
+ m_snapValues.push_back(crotchetDuration / 4);
+ m_snapValues.push_back(crotchetDuration / 3);
+ m_snapValues.push_back(crotchetDuration / 2);
+ m_snapValues.push_back(crotchetDuration);
+ m_snapValues.push_back((crotchetDuration * 3) / 2);
+ m_snapValues.push_back(crotchetDuration * 2);
+ m_snapValues.push_back(SnapGrid::SnapToBeat);
+ m_snapValues.push_back(SnapGrid::SnapToBar);
+
+ for (unsigned int i = 0; i < m_snapValues.size(); i++) {
+
+ timeT d = m_snapValues[i];
+
+ if (d == SnapGrid::NoSnap) {
+ new KAction(i18n("&No Snap"), 0, this,
+ SLOT(slotSetSnapFromAction()),
+ actionCollection(), "snap_none");
+ } else if (d == SnapGrid::SnapToUnit) {
+ } else if (d == SnapGrid::SnapToBeat) {
+ new KAction(i18n("Snap to Bea&t"), Key_1, this,
+ SLOT(slotSetSnapFromAction()),
+ actionCollection(), "snap_beat");
+ } else if (d == SnapGrid::SnapToBar) {
+ new KAction(i18n("Snap to &Bar"), Key_5, this,
+ SLOT(slotSetSnapFromAction()),
+ actionCollection(), "snap_bar");
+ } else {
+
+ timeT err = 0;
+ QString label = NotationStrings::makeNoteMenuLabel(d, true, err);
+ QPixmap pixmap = NotePixmapFactory::toQPixmap
+ (NotePixmapFactory::makeNoteMenuPixmap(d, err));
+
+ KShortcut cut = 0;
+ if (d == crotchetDuration / 16) cut = Key_0;
+ else if (d == crotchetDuration / 8) cut = Key_3;
+ else if (d == crotchetDuration / 4) cut = Key_6;
+ else if (d == crotchetDuration / 2) cut = Key_8;
+ else if (d == crotchetDuration) cut = Key_4;
+ else if (d == crotchetDuration * 2) cut = Key_2;
+
+ QString actionName = QString("snap_%1").arg(int((crotchetDuration * 4) / d));
+ if (d == (crotchetDuration * 3) / 2) actionName = "snap_3";
+ new KAction(i18n("Snap to %1").arg(label), pixmap, cut, this,
+ SLOT(slotSetSnapFromAction()), actionCollection(),
+ actionName);
+ }
+ }
+
+ //
+ // Settings menu
+ //
+ new KAction(i18n("Show Instrument Parameters"), 0, this,
+ SLOT(slotDockParametersBack()),
+ actionCollection(),
+ "show_inst_parameters");
+
+ new KToggleAction(i18n("Show Ch&ord Name Ruler"), 0, this,
+ SLOT(slotToggleChordsRuler()),
+ actionCollection(), "show_chords_ruler");
+
+ new KToggleAction(i18n("Show &Tempo Ruler"), 0, this,
+ SLOT(slotToggleTempoRuler()),
+ actionCollection(), "show_tempo_ruler");
+
+ createGUI(getRCFileName(), false);
+
+ if (getSegmentsOnlyRestsAndClefs())
+ actionCollection()->action("draw")->activate();
+ else
+ actionCollection()->action("select")->activate();
+}
+
+bool
+MatrixView::isInChordMode()
+{
+ return ((KToggleAction *)actionCollection()->action("chord_mode"))->
+ isChecked();
+}
+
+void MatrixView::slotDockParametersBack()
+{
+ m_dockLeft->dockBack();
+}
+
+void MatrixView::slotParametersClosed()
+{
+ stateChanged("parametersbox_closed");
+ m_dockVisible = false;
+}
+
+void MatrixView::slotParametersDockedBack(KDockWidget* dw, KDockWidget::DockPosition)
+{
+ if (dw == m_dockLeft) {
+ stateChanged("parametersbox_closed", KXMLGUIClient::StateReverse);
+ m_dockVisible = true;
+ }
+}
+
+void MatrixView::slotCheckTrackAssignments()
+{
+ Track *track =
+ m_staffs[0]->getSegment().getComposition()->
+ getTrackById(m_staffs[0]->getSegment().getTrack());
+
+ Instrument *instr = getDocument()->getStudio().
+ getInstrumentById(track->getInstrument());
+
+ m_parameterBox->useInstrument(instr);
+}
+
+void MatrixView::initStatusBar()
+{
+ KStatusBar* sb = statusBar();
+
+ m_hoveredOverAbsoluteTime = new QLabel(sb);
+ m_hoveredOverNoteName = new QLabel(sb);
+
+ m_hoveredOverAbsoluteTime->setMinimumWidth(175);
+ m_hoveredOverNoteName->setMinimumWidth(65);
+
+ sb->addWidget(m_hoveredOverAbsoluteTime);
+ sb->addWidget(m_hoveredOverNoteName);
+
+ m_insertModeLabel = new QLabel(sb);
+ m_insertModeLabel->setMinimumWidth(20);
+ sb->addWidget(m_insertModeLabel);
+
+ sb->insertItem(KTmpStatusMsg::getDefaultMsg(),
+ KTmpStatusMsg::getDefaultId(), 1);
+ sb->setItemAlignment(KTmpStatusMsg::getDefaultId(),
+ AlignLeft | AlignVCenter);
+
+ m_selectionCounter = new QLabel(sb);
+ sb->addWidget(m_selectionCounter);
+}
+
+void MatrixView::slotToolHelpChanged(const QString &s)
+{
+ QString msg = " " + s;
+ if (m_toolContextHelp == msg) return;
+ m_toolContextHelp = msg;
+
+ m_config->setGroup(GeneralOptionsConfigGroup);
+ if (!m_config->readBoolEntry("toolcontexthelp", true)) return;
+
+ if (m_mouseInCanvasView) statusBar()->changeItem(m_toolContextHelp, 1);
+}
+
+void MatrixView::slotMouseEnteredCanvasView()
+{
+ m_config->setGroup(GeneralOptionsConfigGroup);
+ if (!m_config->readBoolEntry("toolcontexthelp", true)) return;
+
+ m_mouseInCanvasView = true;
+ statusBar()->changeItem(m_toolContextHelp, 1);
+}
+
+void MatrixView::slotMouseLeftCanvasView()
+{
+ m_mouseInCanvasView = false;
+ statusBar()->changeItem(KTmpStatusMsg::getDefaultMsg(), 1);
+}
+
+bool MatrixView::applyLayout(int staffNo,
+ timeT startTime,
+ timeT endTime)
+{
+ Profiler profiler("MatrixView::applyLayout", true);
+
+ m_hlayout.reset();
+ m_vlayout.reset();
+
+ for (unsigned int i = 0; i < m_staffs.size(); ++i) {
+
+ if (staffNo >= 0 && (int)i != staffNo)
+ continue;
+
+ m_hlayout.scanStaff(*m_staffs[i], startTime, endTime);
+ m_vlayout.scanStaff(*m_staffs[i], startTime, endTime);
+ }
+
+ m_hlayout.finishLayout();
+ m_vlayout.finishLayout();
+
+ if (m_staffs[0]->getSegment().getEndMarkerTime() != m_lastEndMarkerTime ||
+ m_lastEndMarkerTime == 0 ||
+ isCompositionModified()) {
+ readjustCanvasSize();
+ m_lastEndMarkerTime = m_staffs[0]->getSegment().getEndMarkerTime();
+ }
+
+ return true;
+}
+
+void MatrixView::refreshSegment(Segment *segment,
+ timeT startTime, timeT endTime)
+{
+ Profiler profiler("MatrixView::refreshSegment", true);
+
+ MATRIX_DEBUG << "MatrixView::refreshSegment(" << startTime
+ << ", " << endTime << ")\n";
+
+ applyLayout( -1, startTime, endTime);
+
+ if (!segment)
+ segment = m_segments[0];
+
+ if (endTime == 0)
+ endTime = segment->getEndTime();
+ else if (startTime == endTime) {
+ startTime = segment->getStartTime();
+ endTime = segment->getEndTime();
+ }
+
+ m_staffs[0]->positionElements(startTime, endTime);
+ repaintRulers();
+}
+
+QSize MatrixView::getViewSize()
+{
+ return canvas()->size();
+}
+
+void MatrixView::setViewSize(QSize s)
+{
+ MATRIX_DEBUG << "MatrixView::setViewSize() w = " << s.width() << endl;
+
+ canvas()->resize(getXbyInverseWorldMatrix(s.width()), s.height());
+ getCanvasView()->resizeContents(s.width(), s.height());
+
+ MATRIX_DEBUG << "MatrixView::setViewSize() contentsWidth = " << getCanvasView()->contentsWidth() << endl;
+}
+
+void MatrixView::repaintRulers()
+{
+ for (unsigned int i = 0; i != m_propertyViewRulers.size(); i++)
+ m_propertyViewRulers[i].first->repaint();
+}
+
+void MatrixView::updateView()
+{
+ canvas()->update();
+}
+
+void MatrixView::setCurrentSelection(EventSelection* s, bool preview,
+ bool redrawNow)
+{
+ //!!! rather too much here shared with notationview -- could much of
+ // this be in editview?
+
+ if (m_currentEventSelection == s) {
+ updateQuantizeCombo();
+ return ;
+ }
+
+ if (m_currentEventSelection) {
+ getStaff(0)->positionElements(m_currentEventSelection->getStartTime(),
+ m_currentEventSelection->getEndTime());
+ }
+
+ EventSelection *oldSelection = m_currentEventSelection;
+ m_currentEventSelection = s;
+
+ timeT startA, endA, startB, endB;
+
+ if (oldSelection) {
+ startA = oldSelection->getStartTime();
+ endA = oldSelection->getEndTime();
+ startB = s ? s->getStartTime() : startA;
+ endB = s ? s->getEndTime() : endA;
+ } else {
+ // we know they can't both be null -- first thing we tested above
+ startA = startB = s->getStartTime();
+ endA = endB = s->getEndTime();
+ }
+
+ // refreshSegment takes start==end to mean refresh everything
+ if (startA == endA)
+ ++endA;
+ if (startB == endB)
+ ++endB;
+
+ bool updateRequired = true;
+
+ if (s) {
+
+ bool foundNewEvent = false;
+
+ for (EventSelection::eventcontainer::iterator i =
+ s->getSegmentEvents().begin();
+ i != s->getSegmentEvents().end(); ++i) {
+
+ if (oldSelection && oldSelection->getSegment() == s->getSegment()
+ && oldSelection->contains(*i))
+ continue;
+
+ foundNewEvent = true;
+
+ if (preview) {
+ long pitch;
+ if ((*i)->get<Int>(BaseProperties::PITCH, pitch)) {
+ long velocity = -1;
+ (void)((*i)->get<Int>(BaseProperties::VELOCITY, velocity));
+ if (!((*i)->has(BaseProperties::TIED_BACKWARD) &&
+ (*i)->get<Bool>(BaseProperties::TIED_BACKWARD)))
+ playNote(s->getSegment(), pitch, velocity);
+ }
+ }
+ }
+
+ if (!foundNewEvent) {
+ if (oldSelection &&
+ oldSelection->getSegment() == s->getSegment() &&
+ oldSelection->getSegmentEvents().size() ==
+ s->getSegmentEvents().size())
+ updateRequired = false;
+ }
+ }
+
+ if (updateRequired) {
+
+ if ((endA >= startB && endB >= startA) &&
+ (!s || !oldSelection ||
+ oldSelection->getSegment() == s->getSegment())) {
+
+ Segment &segment(s ? s->getSegment() :
+ oldSelection->getSegment());
+
+ if (redrawNow) {
+ // recolour the events now
+ getStaff(segment)->positionElements(std::min(startA, startB),
+ std::max(endA, endB));
+ } else {
+ // mark refresh status and then request a repaint
+ segment.getRefreshStatus
+ (m_segmentsRefreshStatusIds
+ [getStaff(segment)->getId()]).
+ push(std::min(startA, startB), std::max(endA, endB));
+ }
+
+ } else {
+ // do two refreshes, one for each -- here we know neither is null
+
+ if (redrawNow) {
+ // recolour the events now
+ getStaff(oldSelection->getSegment())->positionElements(startA,
+ endA);
+
+ getStaff(s->getSegment())->positionElements(startB, endB);
+ } else {
+ // mark refresh status and then request a repaint
+
+ oldSelection->getSegment().getRefreshStatus
+ (m_segmentsRefreshStatusIds
+ [getStaff(oldSelection->getSegment())->getId()]).
+ push(startA, endA);
+
+ s->getSegment().getRefreshStatus
+ (m_segmentsRefreshStatusIds
+ [getStaff(s->getSegment())->getId()]).
+ push(startB, endB);
+ }
+ }
+ }
+
+ delete oldSelection;
+
+ if (s) {
+
+ int eventsSelected = s->getSegmentEvents().size();
+ m_selectionCounter->setText
+ (i18n(" 1 event selected ",
+ " %n events selected ", eventsSelected));
+
+ } else {
+ m_selectionCounter->setText(i18n(" No selection "));
+ }
+
+ m_selectionCounter->update();
+
+ slotSetCurrentVelocityFromSelection();
+
+ // Clear states first, then enter only those ones that apply
+ // (so as to avoid ever clearing one after entering another, in
+ // case the two overlap at all)
+ stateChanged("have_selection", KXMLGUIClient::StateReverse);
+ stateChanged("have_notes_in_selection", KXMLGUIClient::StateReverse);
+ stateChanged("have_rests_in_selection", KXMLGUIClient::StateReverse);
+
+ if (s) {
+ stateChanged("have_selection", KXMLGUIClient::StateNoReverse);
+ if (s->contains(Note::EventType)) {
+ stateChanged("have_notes_in_selection",
+ KXMLGUIClient::StateNoReverse);
+ }
+ if (s->contains(Note::EventRestType)) {
+ stateChanged("have_rests_in_selection",
+ KXMLGUIClient::StateNoReverse);
+ }
+ }
+
+ updateQuantizeCombo();
+
+ if (redrawNow)
+ updateView();
+ else
+ update();
+}
+
+void MatrixView::updateQuantizeCombo()
+{
+ timeT unit = 0;
+
+ if (m_currentEventSelection) {
+ unit =
+ BasicQuantizer::getStandardQuantization
+ (m_currentEventSelection);
+ } else {
+ unit =
+ BasicQuantizer::getStandardQuantization
+ (&(m_staffs[0]->getSegment()));
+ }
+
+ for (unsigned int i = 0; i < m_quantizations.size(); ++i) {
+ if (unit == m_quantizations[i]) {
+ m_quantizeCombo->setCurrentItem(i);
+ return ;
+ }
+ }
+
+ m_quantizeCombo->setCurrentItem(m_quantizeCombo->count() - 1); // "Off"
+}
+
+void MatrixView::slotPaintSelected()
+{
+ EditTool* painter = m_toolBox->getTool(MatrixPainter::ToolName);
+
+ setTool(painter);
+}
+
+void MatrixView::slotEraseSelected()
+{
+ EditTool* eraser = m_toolBox->getTool(MatrixEraser::ToolName);
+
+ setTool(eraser);
+}
+
+void MatrixView::slotSelectSelected()
+{
+ EditTool* selector = m_toolBox->getTool(MatrixSelector::ToolName);
+
+ connect(selector, SIGNAL(gotSelection()),
+ this, SLOT(slotNewSelection()));
+
+ connect(selector, SIGNAL(editTriggerSegment(int)),
+ this, SIGNAL(editTriggerSegment(int)));
+
+ setTool(selector);
+}
+
+void MatrixView::slotMoveSelected()
+{
+ EditTool* mover = m_toolBox->getTool(MatrixMover::ToolName);
+
+ setTool(mover);
+}
+
+void MatrixView::slotResizeSelected()
+{
+ EditTool* resizer = m_toolBox->getTool(MatrixResizer::ToolName);
+
+ setTool(resizer);
+}
+
+void MatrixView::slotTransformsQuantize()
+{
+ if (!m_currentEventSelection)
+ return ;
+
+ QuantizeDialog dialog(this);
+
+ if (dialog.exec() == QDialog::Accepted) {
+ KTmpStatusMsg msg(i18n("Quantizing..."), this);
+ addCommandToHistory(new EventQuantizeCommand
+ (*m_currentEventSelection,
+ dialog.getQuantizer()));
+ }
+}
+
+void MatrixView::slotTransformsRepeatQuantize()
+{
+ if (!m_currentEventSelection)
+ return ;
+
+ KTmpStatusMsg msg(i18n("Quantizing..."), this);
+ addCommandToHistory(new EventQuantizeCommand
+ (*m_currentEventSelection,
+ "Quantize Dialog Grid", false)); // no i18n (config group name)
+}
+
+void MatrixView::slotTransformsCollapseNotes()
+{
+ if (!m_currentEventSelection)
+ return ;
+ KTmpStatusMsg msg(i18n("Collapsing notes..."), this);
+
+ addCommandToHistory(new CollapseNotesCommand
+ (*m_currentEventSelection));
+}
+
+void MatrixView::slotTransformsLegato()
+{
+ if (!m_currentEventSelection)
+ return ;
+
+ KTmpStatusMsg msg(i18n("Making legato..."), this);
+ addCommandToHistory(new EventQuantizeCommand
+ (*m_currentEventSelection,
+ new LegatoQuantizer(0))); // no quantization
+}
+
+void MatrixView::slotMousePressed(timeT time, int pitch,
+ QMouseEvent* e, MatrixElement* el)
+{
+ MATRIX_DEBUG << "MatrixView::mousePressed at pitch "
+ << pitch << ", time " << time << endl;
+
+ // Don't allow moving/insertion before the beginning of the
+ // segment
+ timeT curSegmentStartTime = getCurrentSegment()->getStartTime();
+ if (curSegmentStartTime > time)
+ time = curSegmentStartTime;
+
+ m_tool->handleMousePress(time, pitch, 0, e, el);
+
+ if (e->button() != RightButton) {
+ getCanvasView()->startAutoScroll();
+ }
+
+ // play a preview
+ //playPreview(pitch);
+}
+
+void MatrixView::slotMouseMoved(timeT time, int pitch, QMouseEvent* e)
+{
+ // Don't allow moving/insertion before the beginning of the
+ // segment
+ timeT curSegmentStartTime = getCurrentSegment()->getStartTime();
+ if (curSegmentStartTime > time)
+ time = curSegmentStartTime;
+
+ if (activeItem()) {
+ activeItem()->handleMouseMove(e);
+ updateView();
+ } else {
+ int follow = m_tool->handleMouseMove(time, pitch, e);
+ getCanvasView()->setScrollDirectionConstraint(follow);
+
+ // if (follow != RosegardenCanvasView::NoFollow) {
+ // getCanvasView()->doAutoScroll();
+ // }
+
+ // play a preview
+ if (pitch != m_previousEvPitch) {
+ //playPreview(pitch);
+ m_previousEvPitch = pitch;
+ }
+ }
+
+}
+
+void MatrixView::slotMouseReleased(timeT time, int pitch, QMouseEvent* e)
+{
+ // Don't allow moving/insertion before the beginning of the
+ // segment
+ timeT curSegmentStartTime = getCurrentSegment()->getStartTime();
+ if (curSegmentStartTime > time)
+ time = curSegmentStartTime;
+
+ if (activeItem()) {
+ activeItem()->handleMouseRelease(e);
+ setActiveItem(0);
+ updateView();
+ }
+
+ // send the real event time now (not adjusted for beginning of bar)
+ m_tool->handleMouseRelease(time, pitch, e);
+ m_previousEvPitch = 0;
+ getCanvasView()->stopAutoScroll();
+}
+
+void
+MatrixView::slotHoveredOverNoteChanged(int evPitch,
+ bool haveEvent,
+ timeT evTime)
+{
+ MidiPitchLabel label(evPitch);
+
+ if (haveEvent) {
+
+ m_haveHoveredOverNote = true;
+
+ int bar, beat, fraction, remainder;
+ getDocument()->getComposition().getMusicalTimeForAbsoluteTime
+ (evTime, bar, beat, fraction, remainder);
+
+ RealTime rt =
+ getDocument()->getComposition().getElapsedRealTime(evTime);
+ long ms = rt.msec();
+
+ QString msg = i18n("Note: %1 (%2.%3s)")
+ .arg(QString("%1-%2-%3-%4")
+ .arg(QString("%1").arg(bar + 1).rightJustify(3, '0'))
+ .arg(QString("%1").arg(beat).rightJustify(2, '0'))
+ .arg(QString("%1").arg(fraction).rightJustify(2, '0'))
+ .arg(QString("%1").arg(remainder).rightJustify(2, '0')))
+ .arg(rt.sec)
+ .arg(QString("%1").arg(ms).rightJustify(3, '0'));
+
+ m_hoveredOverAbsoluteTime->setText(msg);
+ }
+
+ m_haveHoveredOverNote = false;
+
+ m_hoveredOverNoteName->setText(i18n("%1 (%2)")
+ .arg(label.getQString())
+ .arg(evPitch));
+
+ m_pitchRuler->drawHoverNote(evPitch);
+}
+
+void
+MatrixView::slotHoveredOverKeyChanged(unsigned int y)
+{
+ MatrixStaff& staff = *(m_staffs[0]);
+
+ int evPitch = staff.getHeightAtCanvasCoords( -1, y);
+
+ if (evPitch != m_previousEvPitch) {
+ MidiPitchLabel label(evPitch);
+ m_hoveredOverNoteName->setText(QString("%1 (%2)").
+ arg(label.getQString()).arg(evPitch));
+ m_previousEvPitch = evPitch;
+ }
+}
+
+void
+MatrixView::slotHoveredOverAbsoluteTimeChanged(unsigned int time)
+{
+ if (m_haveHoveredOverNote) return;
+
+ timeT t = time;
+
+ int bar, beat, fraction, remainder;
+ getDocument()->getComposition().getMusicalTimeForAbsoluteTime
+ (t, bar, beat, fraction, remainder);
+
+ RealTime rt =
+ getDocument()->getComposition().getElapsedRealTime(t);
+ long ms = rt.msec();
+
+ // At the advice of doc.trolltech.com/3.0/qstring.html#sprintf
+ // we replaced this QString format("%ld (%ld.%03lds)");
+ // to support Unicode
+
+ QString message = i18n("Time: %1 (%2.%3s)")
+ .arg(QString("%1-%2-%3-%4")
+ .arg(QString("%1").arg(bar + 1).rightJustify(3, '0'))
+ .arg(QString("%1").arg(beat).rightJustify(2, '0'))
+ .arg(QString("%1").arg(fraction).rightJustify(2, '0'))
+ .arg(QString("%1").arg(remainder).rightJustify(2, '0')))
+ .arg(rt.sec)
+ .arg(QString("%1").arg(ms).rightJustify(3, '0'));
+
+ m_hoveredOverAbsoluteTime->setText(message);
+}
+
+void
+MatrixView::slotSetPointerPosition(timeT time)
+{
+ slotSetPointerPosition(time, m_playTracking);
+}
+
+void
+MatrixView::slotSetPointerPosition(timeT time, bool scroll)
+{
+ Composition &comp = getDocument()->getComposition();
+ int barNo = comp.getBarNumber(time);
+
+ if (barNo >= m_hlayout.getLastVisibleBarOnStaff(*m_staffs[0])) {
+
+ Segment &seg = m_staffs[0]->getSegment();
+
+ if (seg.isRepeating() && time < seg.getRepeatEndTime()) {
+ time =
+ seg.getStartTime() +
+ ((time - seg.getStartTime()) %
+ (seg.getEndMarkerTime() - seg.getStartTime()));
+ m_staffs[0]->setPointerPosition(m_hlayout, time);
+ } else {
+ m_staffs[0]->hidePointer();
+ scroll = false;
+ }
+ } else if (barNo < m_hlayout.getFirstVisibleBarOnStaff(*m_staffs[0])) {
+ m_staffs[0]->hidePointer();
+ scroll = false;
+ } else {
+ m_staffs[0]->setPointerPosition(m_hlayout, time);
+ }
+
+ if (scroll && !getCanvasView()->isAutoScrolling())
+ getCanvasView()->slotScrollHoriz(static_cast<int>(getXbyWorldMatrix(m_hlayout.getXForTime(time))));
+
+ updateView();
+}
+
+void
+MatrixView::slotSetInsertCursorPosition(timeT time, bool scroll)
+{
+ //!!! For now. Probably unlike slotSetPointerPosition this one
+ // should snap to the nearest event or grid line.
+
+ m_staffs[0]->setInsertCursorPosition(m_hlayout, time);
+
+ if (scroll && !getCanvasView()->isAutoScrolling()) {
+ getCanvasView()->slotScrollHoriz
+ (static_cast<int>(getXbyWorldMatrix(m_hlayout.getXForTime(time))));
+ }
+
+ updateView();
+}
+
+void MatrixView::slotEditCut()
+{
+ MATRIX_DEBUG << "MatrixView::slotEditCut()\n";
+
+ if (!m_currentEventSelection)
+ return ;
+ KTmpStatusMsg msg(i18n("Cutting selection to clipboard..."), this);
+
+ addCommandToHistory(new CutCommand(*m_currentEventSelection,
+ getDocument()->getClipboard()));
+}
+
+void MatrixView::slotEditCopy()
+{
+ if (!m_currentEventSelection)
+ return ;
+ KTmpStatusMsg msg(i18n("Copying selection to clipboard..."), this);
+
+ addCommandToHistory(new CopyCommand(*m_currentEventSelection,
+ getDocument()->getClipboard()));
+
+ emit usedSelection();
+}
+
+void MatrixView::slotEditPaste()
+{
+ if (getDocument()->getClipboard()->isEmpty()) {
+ slotStatusHelpMsg(i18n("Clipboard is empty"));
+ return ;
+ }
+
+ KTmpStatusMsg msg(i18n("Inserting clipboard contents..."), this);
+
+ PasteEventsCommand *command = new PasteEventsCommand
+ (m_staffs[0]->getSegment(), getDocument()->getClipboard(),
+ getInsertionTime(), PasteEventsCommand::MatrixOverlay);
+
+ if (!command->isPossible()) {
+ slotStatusHelpMsg(i18n("Couldn't paste at this point"));
+ } else {
+ addCommandToHistory(command);
+ setCurrentSelection(new EventSelection(command->getPastedEvents()));
+ }
+}
+
+void MatrixView::slotEditDelete()
+{
+ if (!m_currentEventSelection)
+ return ;
+ KTmpStatusMsg msg(i18n("Deleting selection..."), this);
+
+ addCommandToHistory(new EraseCommand(*m_currentEventSelection));
+
+ // clear and clear
+ setCurrentSelection(0, false);
+}
+
+void MatrixView::slotKeyPressed(unsigned int y, bool repeating)
+{
+ slotHoveredOverKeyChanged(y);
+
+ getCanvasView()->slotScrollVertSmallSteps(y);
+
+ Composition &comp = getDocument()->getComposition();
+ Studio &studio = getDocument()->getStudio();
+
+ MatrixStaff& staff = *(m_staffs[0]);
+ MidiByte evPitch = staff.getHeightAtCanvasCoords( -1, y);
+
+ // Don't do anything if we're part of a run up the keyboard
+ // and the pitch hasn't changed
+ //
+ if (m_lastNote == evPitch && repeating)
+ return ;
+
+ // Save value
+ m_lastNote = evPitch;
+ if (!repeating)
+ m_firstNote = evPitch;
+
+ Track *track = comp.getTrackById(
+ staff.getSegment().getTrack());
+
+ Instrument *ins =
+ studio.getInstrumentById(track->getInstrument());
+
+ // check for null instrument
+ //
+ if (ins == 0)
+ return ;
+
+ MappedEvent mE(ins->getId(),
+ MappedEvent::MidiNote,
+ evPitch + staff.getSegment().getTranspose(),
+ MidiMaxValue,
+ RealTime::zeroTime,
+ RealTime::zeroTime,
+ RealTime::zeroTime);
+ StudioControl::sendMappedEvent(mE);
+
+}
+
+void MatrixView::slotKeySelected(unsigned int y, bool repeating)
+{
+ slotHoveredOverKeyChanged(y);
+
+ getCanvasView()->slotScrollVertSmallSteps(y);
+
+ MatrixStaff& staff = *(m_staffs[0]);
+ Segment &segment(staff.getSegment());
+ MidiByte evPitch = staff.getHeightAtCanvasCoords( -1, y);
+
+ // Don't do anything if we're part of a run up the keyboard
+ // and the pitch hasn't changed
+ //
+ if (m_lastNote == evPitch && repeating)
+ return ;
+
+ // Save value
+ m_lastNote = evPitch;
+ if (!repeating)
+ m_firstNote = evPitch;
+
+ EventSelection *s = new EventSelection(segment);
+
+ for (Segment::iterator i = segment.begin();
+ segment.isBeforeEndMarker(i); ++i) {
+
+ if ((*i)->isa(Note::EventType) &&
+ (*i)->has(BaseProperties::PITCH)) {
+
+ MidiByte p = (*i)->get
+ <Int>
+ (BaseProperties::PITCH);
+ if (p >= std::min(m_firstNote, evPitch) &&
+ p <= std::max(m_firstNote, evPitch)) {
+ s->addEvent(*i);
+ }
+ }
+ }
+
+ if (m_currentEventSelection) {
+ // allow addFromSelection to deal with eliminating duplicates
+ s->addFromSelection(m_currentEventSelection);
+ }
+
+ setCurrentSelection(s, false);
+
+ // now play the note as well
+
+ Composition &comp = getDocument()->getComposition();
+ Studio &studio = getDocument()->getStudio();
+ Track *track = comp.getTrackById(segment.getTrack());
+ Instrument *ins =
+ studio.getInstrumentById(track->getInstrument());
+
+ // check for null instrument
+ //
+ if (ins == 0)
+ return ;
+
+ MappedEvent mE(ins->getId(),
+ MappedEvent::MidiNoteOneShot,
+ evPitch + segment.getTranspose(),
+ MidiMaxValue,
+ RealTime::zeroTime,
+ RealTime(0, 250000000),
+ RealTime::zeroTime);
+ StudioControl::sendMappedEvent(mE);
+}
+
+void MatrixView::slotKeyReleased(unsigned int y, bool repeating)
+{
+ MatrixStaff& staff = *(m_staffs[0]);
+ int evPitch = staff.getHeightAtCanvasCoords(-1, y);
+
+ if (m_lastNote == evPitch && repeating)
+ return;
+
+ Rosegarden::Segment &segment(staff.getSegment());
+
+ // send note off (note on at zero velocity)
+
+ Rosegarden::Composition &comp = getDocument()->getComposition();
+ Rosegarden::Studio &studio = getDocument()->getStudio();
+ Rosegarden::Track *track = comp.getTrackById(segment.getTrack());
+ Rosegarden::Instrument *ins =
+ studio.getInstrumentById(track->getInstrument());
+
+ // check for null instrument
+ //
+ if (ins == 0)
+ return;
+
+ evPitch = evPitch + segment.getTranspose();
+ if (evPitch < 0 || evPitch > 127) return;
+
+ Rosegarden::MappedEvent mE(ins->getId(),
+ Rosegarden::MappedEvent::MidiNote,
+ evPitch,
+ 0,
+ Rosegarden::RealTime::zeroTime,
+ Rosegarden::RealTime::zeroTime,
+ Rosegarden::RealTime::zeroTime);
+ Rosegarden::StudioControl::sendMappedEvent(mE);
+}
+
+void MatrixView::slotVerticalScrollPianoKeyboard(int y)
+{
+ if (m_pianoView) // check that the piano view still exists (see dtor)
+ m_pianoView->setContentsPos(0, y);
+}
+
+void MatrixView::slotInsertNoteFromAction()
+{
+ const QObject *s = sender();
+ QString name = s->name();
+
+ Segment &segment = *getCurrentSegment();
+ int pitch = 0;
+
+ Accidental accidental =
+ Accidentals::NoAccidental;
+
+ timeT time(getInsertionTime());
+ ::Rosegarden::Key key = segment.getKeyAtTime(time);
+ Clef clef = segment.getClefAtTime(time);
+
+ try {
+
+ pitch = getPitchFromNoteInsertAction(name, accidental, clef, key);
+
+ } catch (...) {
+
+ KMessageBox::sorry
+ (this, i18n("Unknown note insert action %1").arg(name));
+ return ;
+ }
+
+ KTmpStatusMsg msg(i18n("Inserting note"), this);
+
+ MATRIX_DEBUG << "Inserting note at pitch " << pitch << endl;
+
+ Event modelEvent(Note::EventType, 0, 1);
+ modelEvent.set<Int>(BaseProperties::PITCH, pitch);
+ modelEvent.set<String>(BaseProperties::ACCIDENTAL, accidental);
+ timeT endTime(time + m_snapGrid->getSnapTime(time));
+
+ MatrixInsertionCommand* command =
+ new MatrixInsertionCommand(segment, time, endTime, &modelEvent);
+
+ addCommandToHistory(command);
+
+ if (!isInChordMode()) {
+ slotSetInsertCursorPosition(endTime);
+ }
+}
+
+void MatrixView::closeWindow()
+{
+ delete this;
+}
+
+bool MatrixView::canPreviewAnotherNote()
+{
+ static time_t lastCutOff = 0;
+ static int sinceLastCutOff = 0;
+
+ time_t now = time(0);
+ ++sinceLastCutOff;
+
+ if ((now - lastCutOff) > 0) {
+ sinceLastCutOff = 0;
+ lastCutOff = now;
+ } else {
+ if (sinceLastCutOff >= 20) {
+ // don't permit more than 20 notes per second, to avoid
+ // gungeing up the sound drivers
+ MATRIX_DEBUG << "Rejecting preview (too busy)" << endl;
+ return false;
+ }
+ }
+
+ return true;
+}
+
+void MatrixView::playNote(Event *event)
+{
+ // Only play note events
+ //
+ if (!event->isa(Note::EventType))
+ return ;
+
+ Composition &comp = getDocument()->getComposition();
+ Studio &studio = getDocument()->getStudio();
+
+ // Get the Instrument
+ //
+ Track *track = comp.getTrackById(
+ m_staffs[0]->getSegment().getTrack());
+
+ Instrument *ins =
+ studio.getInstrumentById(track->getInstrument());
+
+ if (ins == 0)
+ return ;
+
+ if (!canPreviewAnotherNote())
+ return ;
+
+ // Get a velocity
+ //
+ MidiByte velocity = MidiMaxValue / 4; // be easy on the user's ears
+ long eventVelocity = 0;
+ if (event->get
+ <Int>(BaseProperties::VELOCITY, eventVelocity))
+ velocity = eventVelocity;
+
+ RealTime duration =
+ comp.getElapsedRealTime(event->getDuration());
+
+ // create
+ MappedEvent mE(ins->getId(),
+ MappedEvent::MidiNoteOneShot,
+ (MidiByte)
+ event->get
+ <Int>
+ (BaseProperties::PITCH) +
+ m_staffs[0]->getSegment().getTranspose(),
+ velocity,
+ RealTime::zeroTime,
+ duration,
+ RealTime::zeroTime);
+
+ StudioControl::sendMappedEvent(mE);
+}
+
+void MatrixView::playNote(const Segment &segment, int pitch,
+ int velocity)
+{
+ Composition &comp = getDocument()->getComposition();
+ Studio &studio = getDocument()->getStudio();
+
+ Track *track = comp.getTrackById(segment.getTrack());
+
+ Instrument *ins =
+ studio.getInstrumentById(track->getInstrument());
+
+ // check for null instrument
+ //
+ if (ins == 0)
+ return ;
+
+ if (velocity < 0)
+ velocity = getCurrentVelocity();
+
+ MappedEvent mE(ins->getId(),
+ MappedEvent::MidiNoteOneShot,
+ pitch + segment.getTranspose(),
+ velocity,
+ RealTime::zeroTime,
+ RealTime(0, 250000000),
+ RealTime::zeroTime);
+
+ StudioControl::sendMappedEvent(mE);
+}
+
+MatrixStaff*
+MatrixView::getStaff(const Segment &segment)
+{
+ for (unsigned int i = 0; i < m_staffs.size(); ++i) {
+ if (&(m_staffs[i]->getSegment()) == &segment)
+ return m_staffs[i];
+ }
+
+ return 0;
+}
+
+void
+MatrixView::setSingleSelectedEvent(int staffNo, Event *event,
+ bool preview, bool redrawNow)
+{
+ setSingleSelectedEvent(getStaff(staffNo)->getSegment(), event,
+ preview, redrawNow);
+}
+
+void
+MatrixView::setSingleSelectedEvent(Segment &segment,
+ Event *event,
+ bool preview, bool redrawNow)
+{
+ setCurrentSelection(0, false);
+
+ EventSelection *selection = new EventSelection(segment);
+ selection->addEvent(event);
+
+ //!!!
+ // this used to say
+ // setCurrentSelection(selection, true)
+ // since the default arg for preview is false, this changes the
+ // default semantics -- test what circumstance this matters in
+ // and choose an acceptable solution for both matrix & notation
+ setCurrentSelection(selection, preview, redrawNow);
+}
+
+void
+MatrixView::slotNewSelection()
+{
+ MATRIX_DEBUG << "MatrixView::slotNewSelection\n";
+
+ // m_parameterBox->setSelection(m_currentEventSelection);
+}
+
+void
+MatrixView::slotSetSnapFromIndex(int s)
+{
+ slotSetSnap(m_snapValues[s]);
+}
+
+void
+MatrixView::slotSetSnapFromAction()
+{
+ const QObject *s = sender();
+ QString name = s->name();
+
+ if (name.left(5) == "snap_") {
+ int snap = name.right(name.length() - 5).toInt();
+ if (snap > 0) {
+ slotSetSnap(Note(Note::Semibreve).getDuration() / snap);
+ } else if (name == "snap_none") {
+ slotSetSnap(SnapGrid::NoSnap);
+ } else if (name == "snap_beat") {
+ slotSetSnap(SnapGrid::SnapToBeat);
+ } else if (name == "snap_bar") {
+ slotSetSnap(SnapGrid::SnapToBar);
+ } else if (name == "snap_unit") {
+ slotSetSnap(SnapGrid::SnapToUnit);
+ } else {
+ MATRIX_DEBUG << "Warning: MatrixView::slotSetSnapFromAction: unrecognised action " << name << endl;
+ }
+ }
+}
+
+void
+MatrixView::slotSetSnap(timeT t)
+{
+ MATRIX_DEBUG << "MatrixView::slotSetSnap: time is " << t << endl;
+ m_snapGrid->setSnapTime(t);
+
+ for (unsigned int i = 0; i < m_snapValues.size(); ++i) {
+ if (m_snapValues[i] == t) {
+ m_snapGridCombo->setCurrentItem(i);
+ break;
+ }
+ }
+
+ for (unsigned int i = 0; i < m_staffs.size(); ++i)
+ m_staffs[i]->sizeStaff(m_hlayout);
+
+ m_segments[0]->setSnapGridSize(t);
+
+ m_config->setGroup(MatrixViewConfigGroup);
+ m_config->writeEntry("Snap Grid Size", t);
+
+ updateView();
+}
+
+void
+MatrixView::slotQuantizeSelection(int q)
+{
+ MATRIX_DEBUG << "MatrixView::slotQuantizeSelection\n";
+
+ timeT unit =
+ ((unsigned int)q < m_quantizations.size() ? m_quantizations[q] : 0);
+
+ Quantizer *quant =
+ new BasicQuantizer
+ (unit ? unit :
+ Note(Note::Shortest).getDuration(), false);
+
+ if (unit) {
+ KTmpStatusMsg msg(i18n("Quantizing..."), this);
+ if (m_currentEventSelection &&
+ m_currentEventSelection->getAddedEvents()) {
+ addCommandToHistory(new EventQuantizeCommand
+ (*m_currentEventSelection, quant));
+ } else {
+ Segment &s = m_staffs[0]->getSegment();
+ addCommandToHistory(new EventQuantizeCommand
+ (s, s.getStartTime(), s.getEndMarkerTime(),
+ quant));
+ }
+ } else {
+ KTmpStatusMsg msg(i18n("Unquantizing..."), this);
+ if (m_currentEventSelection &&
+ m_currentEventSelection->getAddedEvents()) {
+ addCommandToHistory(new EventUnquantizeCommand
+ (*m_currentEventSelection, quant));
+ } else {
+ Segment &s = m_staffs[0]->getSegment();
+ addCommandToHistory(new EventUnquantizeCommand
+ (s, s.getStartTime(), s.getEndMarkerTime(),
+ quant));
+ }
+ }
+}
+
+void
+MatrixView::initActionsToolbar()
+{
+ MATRIX_DEBUG << "MatrixView::initActionsToolbar" << endl;
+
+ KToolBar *actionsToolbar = toolBar("Actions Toolbar");
+
+ if (!actionsToolbar) {
+ MATRIX_DEBUG << "MatrixView::initActionsToolbar - "
+ << "tool bar not found" << endl;
+ return ;
+ }
+
+ // The SnapGrid combo and Snap To... menu items
+ //
+ QLabel *sLabel = new QLabel(i18n(" Grid: "), actionsToolbar, "kde toolbar widget");
+ sLabel->setIndent(10);
+
+ QPixmap noMap = NotePixmapFactory::toQPixmap(NotePixmapFactory::makeToolbarPixmap("menu-no-note"));
+
+ m_snapGridCombo = new KComboBox(actionsToolbar);
+
+ for (unsigned int i = 0; i < m_snapValues.size(); i++) {
+
+ timeT d = m_snapValues[i];
+
+ if (d == SnapGrid::NoSnap) {
+ m_snapGridCombo->insertItem(i18n("None"));
+ } else if (d == SnapGrid::SnapToUnit) {
+ m_snapGridCombo->insertItem(i18n("Unit"));
+ } else if (d == SnapGrid::SnapToBeat) {
+ m_snapGridCombo->insertItem(i18n("Beat"));
+ } else if (d == SnapGrid::SnapToBar) {
+ m_snapGridCombo->insertItem(i18n("Bar"));
+ } else {
+ timeT err = 0;
+ QString label = NotationStrings::makeNoteMenuLabel(d, true, err);
+ QPixmap pixmap = NotePixmapFactory::toQPixmap
+ (NotePixmapFactory::makeNoteMenuPixmap(d, err));
+ m_snapGridCombo->insertItem((err ? noMap : pixmap), label);
+ }
+
+ if (d == m_snapGrid->getSnapSetting()) {
+ m_snapGridCombo->setCurrentItem(m_snapGridCombo->count() - 1);
+ }
+ }
+
+ connect(m_snapGridCombo, SIGNAL(activated(int)),
+ this, SLOT(slotSetSnapFromIndex(int)));
+
+ // Velocity combo. Not a spin box, because the spin box is too
+ // slow to use unless we make it typeable into, and then it takes
+ // focus away from our more important widgets
+
+ QLabel *vlabel = new QLabel(i18n(" Velocity: "), actionsToolbar, "kde toolbar widget");
+ vlabel->setIndent(10);
+
+ m_velocityCombo = new KComboBox(actionsToolbar);
+ for (int i = 0; i <= 127; ++i) {
+ m_velocityCombo->insertItem(QString("%1").arg(i));
+ }
+ m_velocityCombo->setCurrentItem(100); //!!! associate with segment
+
+ // Quantize combo
+ //
+ QLabel *qLabel = new QLabel(i18n(" Quantize: "), actionsToolbar, "kde toolbar widget");
+ qLabel->setIndent(10);
+
+ m_quantizeCombo = new KComboBox(actionsToolbar);
+
+ for (unsigned int i = 0; i < m_quantizations.size(); ++i) {
+
+ timeT time = m_quantizations[i];
+ timeT error = 0;
+ QString label = NotationStrings::makeNoteMenuLabel(time, true, error);
+ QPixmap pmap = NotePixmapFactory::toQPixmap(NotePixmapFactory::makeNoteMenuPixmap(time, error));
+ m_quantizeCombo->insertItem(error ? noMap : pmap, label);
+ }
+
+ m_quantizeCombo->insertItem(noMap, i18n("Off"));
+
+ connect(m_quantizeCombo, SIGNAL(activated(int)),
+ this, SLOT(slotQuantizeSelection(int)));
+}
+
+void
+MatrixView::initZoomToolbar()
+{
+ MATRIX_DEBUG << "MatrixView::initZoomToolbar" << endl;
+
+ KToolBar *zoomToolbar = toolBar("Zoom Toolbar");
+
+ if (!zoomToolbar) {
+ MATRIX_DEBUG << "MatrixView::initZoomToolbar - "
+ << "tool bar not found" << endl;
+ return ;
+ }
+
+ std::vector<double> zoomSizes; // in units-per-pixel
+
+ //double defaultBarWidth44 = 100.0;
+ //double duration44 = TimeSignature(4,4).getBarDuration();
+
+ static double factors[] = { 0.025, 0.05, 0.1, 0.2, 0.5,
+ 1.0, 1.5, 2.5, 5.0, 10.0, 20.0 };
+ // Zoom labels
+ //
+ for (unsigned int i = 0; i < sizeof(factors) / sizeof(factors[0]); ++i) {
+// zoomSizes.push_back(duration44 / (defaultBarWidth44 * factors[i]));
+
+// zoomSizes.push_back(factors[i] / 2); // GROSS HACK - see in matrixstaff.h - BREAKS MATRIX VIEW, see bug 1000595
+ zoomSizes.push_back(factors[i]);
+ }
+
+ m_hZoomSlider = new ZoomSlider<double>
+ (zoomSizes, -1, QSlider::Horizontal, zoomToolbar, "kde toolbar widget");
+ m_hZoomSlider->setTracking(true);
+ m_hZoomSlider->setFocusPolicy(QWidget::NoFocus);
+
+ m_zoomLabel = new QLabel(zoomToolbar, "kde toolbar widget");
+ m_zoomLabel->setIndent(10);
+ m_zoomLabel->setFixedWidth(80);
+
+ connect(m_hZoomSlider,
+ SIGNAL(valueChanged(int)),
+ SLOT(slotChangeHorizontalZoom(int)));
+
+}
+
+void
+MatrixView::slotChangeHorizontalZoom(int)
+{
+ double zoomValue = m_hZoomSlider->getCurrentSize();
+
+ // m_zoomLabel->setText(i18n("%1%").arg(zoomValue*100.0 * 2)); // GROSS HACK - see in matrixstaff.h - BREAKS MATRIX VIEW, see bug 1000595
+ m_zoomLabel->setText(i18n("%1%").arg(zoomValue*100.0));
+
+ MATRIX_DEBUG << "MatrixView::slotChangeHorizontalZoom() : zoom factor = "
+ << zoomValue << endl;
+
+ m_referenceRuler->setHScaleFactor(zoomValue);
+
+ if (m_tempoRuler)
+ m_tempoRuler->repaint();
+ if (m_chordNameRuler)
+ m_chordNameRuler->repaint();
+
+ // Set zoom matrix
+ //
+ QWMatrix zoomMatrix;
+ zoomMatrix.scale(zoomValue, 1.0);
+ m_canvasView->setWorldMatrix(zoomMatrix);
+
+ // make control rulers zoom too
+ //
+ setControlRulersZoom(zoomMatrix);
+
+ if (m_topStandardRuler)
+ m_topStandardRuler->setHScaleFactor(zoomValue);
+ if (m_bottomStandardRuler)
+ m_bottomStandardRuler->setHScaleFactor(zoomValue);
+
+ for (unsigned int i = 0; i < m_propertyViewRulers.size(); ++i) {
+ m_propertyViewRulers[i].first->setHScaleFactor(zoomValue);
+ m_propertyViewRulers[i].first->repaint();
+ }
+
+ if (m_topStandardRuler)
+ m_topStandardRuler->update();
+ if (m_bottomStandardRuler)
+ m_bottomStandardRuler->update();
+
+ m_config->setGroup(MatrixViewConfigGroup);
+ m_config->writeEntry("Zoom Level", zoomValue);
+
+ // If you do adjust the viewsize then please remember to
+ // either re-center() or remember old scrollbar position
+ // and restore.
+ //
+
+ int newWidth = computePostLayoutWidth();
+
+ // int newWidth = int(getXbyWorldMatrix(getCanvasView()->canvas()->width()));
+
+ // We DO NOT resize the canvas(), only the area it's displaying on
+ //
+ getCanvasView()->resizeContents(newWidth, getViewSize().height());
+
+ // This forces a refresh of the h. scrollbar, even if the canvas width
+ // hasn't changed
+ //
+ getCanvasView()->polish();
+
+ getCanvasView()->slotScrollHoriz
+ (getXbyWorldMatrix(m_staffs[0]->getLayoutXOfInsertCursor()));
+}
+
+void
+MatrixView::slotZoomIn()
+{
+ m_hZoomSlider->increment();
+}
+
+void
+MatrixView::slotZoomOut()
+{
+ m_hZoomSlider->decrement();
+}
+
+void
+MatrixView::scrollToTime(timeT t)
+{
+ double layoutCoord = m_hlayout.getXForTime(t);
+ getCanvasView()->slotScrollHoriz(int(layoutCoord));
+}
+
+int
+MatrixView::getCurrentVelocity() const
+{
+ return m_velocityCombo->currentItem();
+}
+
+void
+MatrixView::slotSetCurrentVelocity(int value)
+{
+ m_velocityCombo->setCurrentItem(value);
+}
+
+
+void
+MatrixView::slotSetCurrentVelocityFromSelection()
+{
+ if (!m_currentEventSelection) return;
+
+ float totalVelocity = 0;
+ int count = 0;
+
+ for (EventSelection::eventcontainer::iterator i =
+ m_currentEventSelection->getSegmentEvents().begin();
+ i != m_currentEventSelection->getSegmentEvents().end(); ++i) {
+
+ if ((*i)->has(BaseProperties::VELOCITY)) {
+ totalVelocity += (*i)->get<Int>(BaseProperties::VELOCITY);
+ ++count;
+ }
+ }
+
+ if (count > 0) {
+ slotSetCurrentVelocity((totalVelocity / count) + 0.5);
+ }
+}
+
+unsigned int
+MatrixView::addPropertyViewRuler(const PropertyName &property)
+{
+ // Try and find this controller if it exists
+ //
+ for (unsigned int i = 0; i != m_propertyViewRulers.size(); i++) {
+ if (m_propertyViewRulers[i].first->getPropertyName() == property)
+ return i;
+ }
+
+ int height = 20;
+
+ PropertyViewRuler *newRuler = new PropertyViewRuler(&m_hlayout,
+ m_segments[0],
+ property,
+ xorigin,
+ height,
+ getCentralWidget());
+
+ addRuler(newRuler);
+
+ PropertyBox *newControl = new PropertyBox(strtoqstr(property),
+ m_parameterBox->width() + m_pitchRuler->width(),
+ height,
+ getCentralWidget());
+
+ addPropertyBox(newControl);
+
+ m_propertyViewRulers.push_back(
+ std::pair<PropertyViewRuler*, PropertyBox*>(newRuler, newControl));
+
+ return m_propertyViewRulers.size() - 1;
+}
+
+bool
+MatrixView::removePropertyViewRuler(unsigned int number)
+{
+ if (number > m_propertyViewRulers.size() - 1)
+ return false;
+
+ std::vector<std::pair<PropertyViewRuler*, PropertyBox*> >::iterator it
+ = m_propertyViewRulers.begin();
+ while (number--)
+ it++;
+
+ delete it->first;
+ delete it->second;
+ m_propertyViewRulers.erase(it);
+
+ return true;
+}
+
+RulerScale*
+MatrixView::getHLayout()
+{
+ return &m_hlayout;
+}
+
+Staff*
+MatrixView::getCurrentStaff()
+{
+ return getStaff(0);
+}
+
+Segment *
+MatrixView::getCurrentSegment()
+{
+ MatrixStaff *staff = getStaff(0);
+ return (staff ? &staff->getSegment() : 0);
+}
+
+timeT
+MatrixView::getInsertionTime()
+{
+ MatrixStaff *staff = m_staffs[0];
+ return staff->getInsertCursorTime(m_hlayout);
+}
+
+void
+MatrixView::slotStepBackward()
+{
+ timeT time(getInsertionTime());
+ slotSetInsertCursorPosition(SnapGrid(&m_hlayout).snapTime
+ (time - 1,
+ SnapGrid::SnapLeft));
+}
+
+void
+MatrixView::slotStepForward()
+{
+ timeT time(getInsertionTime());
+ slotSetInsertCursorPosition(SnapGrid(&m_hlayout).snapTime
+ (time + 1,
+ SnapGrid::SnapRight));
+}
+
+void
+MatrixView::slotJumpCursorToPlayback()
+{
+ slotSetInsertCursorPosition(getDocument()->getComposition().getPosition());
+}
+
+void
+MatrixView::slotJumpPlaybackToCursor()
+{
+ emit jumpPlaybackTo(getInsertionTime());
+}
+
+void
+MatrixView::slotToggleTracking()
+{
+ m_playTracking = !m_playTracking;
+}
+
+void
+MatrixView::slotSelectAll()
+{
+ Segment *segment = m_segments[0];
+ Segment::iterator it = segment->begin();
+ EventSelection *selection = new EventSelection(*segment);
+
+ for (; segment->isBeforeEndMarker(it); it++)
+ if ((*it)->isa(Note::EventType))
+ selection->addEvent(*it);
+
+ setCurrentSelection(selection, false);
+}
+
+void MatrixView::slotPreviewSelection()
+{
+ if (!m_currentEventSelection)
+ return ;
+
+ getDocument()->slotSetLoop(m_currentEventSelection->getStartTime(),
+ m_currentEventSelection->getEndTime());
+}
+
+void MatrixView::slotClearLoop()
+{
+ getDocument()->slotSetLoop(0, 0);
+}
+
+void MatrixView::slotClearSelection()
+{
+ // Actually we don't clear the selection immediately: if we're
+ // using some tool other than the select tool, then the first
+ // press switches us back to the select tool.
+
+ MatrixSelector *selector = dynamic_cast<MatrixSelector *>(m_tool);
+
+ if (!selector) {
+ slotSelectSelected();
+ } else {
+ setCurrentSelection(0);
+ }
+}
+
+void MatrixView::slotFilterSelection()
+{
+ RG_DEBUG << "MatrixView::slotFilterSelection" << endl;
+
+ Segment *segment = getCurrentSegment();
+ EventSelection *existingSelection = m_currentEventSelection;
+ if (!segment || !existingSelection)
+ return ;
+
+ EventFilterDialog dialog(this);
+ if (dialog.exec() == QDialog::Accepted) {
+ RG_DEBUG << "slotFilterSelection- accepted" << endl;
+
+ bool haveEvent = false;
+
+ EventSelection *newSelection = new EventSelection(*segment);
+ EventSelection::eventcontainer &ec =
+ existingSelection->getSegmentEvents();
+ for (EventSelection::eventcontainer::iterator i =
+ ec.begin(); i != ec.end(); ++i) {
+ if (dialog.keepEvent(*i)) {
+ haveEvent = true;
+ newSelection->addEvent(*i);
+ }
+ }
+
+ if (haveEvent)
+ setCurrentSelection(newSelection);
+ else
+ setCurrentSelection(0);
+ }
+}
+
+void
+MatrixView::readjustCanvasSize()
+{
+ int maxHeight = 0;
+
+ for (unsigned int i = 0; i < m_staffs.size(); ++i) {
+
+ MatrixStaff &staff = *m_staffs[i];
+
+ staff.sizeStaff(m_hlayout);
+
+ // if (staff.getTotalWidth() + staff.getX() > maxWidth) {
+ // maxWidth = staff.getTotalWidth() + staff.getX() + 1;
+ // }
+
+ if (staff.getTotalHeight() + staff.getY() > maxHeight) {
+ if (isDrumMode()) {
+ maxHeight = staff.getTotalHeight() + staff.getY() + 5;
+ } else {
+ maxHeight = staff.getTotalHeight() + staff.getY() + 1;
+ }
+ }
+
+ }
+
+ int newWidth = computePostLayoutWidth();
+
+ // now get the EditView to do the biz
+ readjustViewSize(QSize(newWidth, maxHeight), true);
+
+ repaintRulers();
+}
+
+void MatrixView::slotVelocityUp()
+{
+ if (!m_currentEventSelection)
+ return ;
+ KTmpStatusMsg msg(i18n("Raising velocities..."), this);
+
+ addCommandToHistory
+ (new ChangeVelocityCommand(10, *m_currentEventSelection));
+
+ slotSetCurrentVelocityFromSelection();
+}
+
+void MatrixView::slotVelocityDown()
+{
+ if (!m_currentEventSelection)
+ return ;
+ KTmpStatusMsg msg(i18n("Lowering velocities..."), this);
+
+ addCommandToHistory
+ (new ChangeVelocityCommand( -10, *m_currentEventSelection));
+
+ slotSetCurrentVelocityFromSelection();
+}
+
+void
+MatrixView::slotSetVelocities()
+{
+ if (!m_currentEventSelection)
+ return ;
+
+ EventParameterDialog dialog(this,
+ i18n("Set Event Velocities"),
+ BaseProperties::VELOCITY,
+ getCurrentVelocity());
+
+ if (dialog.exec() == QDialog::Accepted) {
+ KTmpStatusMsg msg(i18n("Setting Velocities..."), this);
+ addCommandToHistory(new SelectionPropertyCommand
+ (m_currentEventSelection,
+ BaseProperties::VELOCITY,
+ dialog.getPattern(),
+ dialog.getValue1(),
+ dialog.getValue2()));
+ }
+}
+
+void
+MatrixView::slotSetVelocitiesToCurrent()
+{
+ if (!m_currentEventSelection) return;
+
+ addCommandToHistory(new SelectionPropertyCommand
+ (m_currentEventSelection,
+ BaseProperties::VELOCITY,
+ FlatPattern,
+ getCurrentVelocity(),
+ getCurrentVelocity()));
+}
+
+void
+MatrixView::slotTriggerSegment()
+{
+ if (!m_currentEventSelection)
+ return ;
+
+ TriggerSegmentDialog dialog(this, &getDocument()->getComposition());
+ if (dialog.exec() != QDialog::Accepted)
+ return ;
+
+ addCommandToHistory(new SetTriggerCommand(*m_currentEventSelection,
+ dialog.getId(),
+ true,
+ dialog.getRetune(),
+ dialog.getTimeAdjust(),
+ Marks::NoMark,
+ i18n("Trigger Segment")));
+}
+
+void
+MatrixView::slotRemoveTriggers()
+{
+ if (!m_currentEventSelection)
+ return ;
+
+ addCommandToHistory(new ClearTriggersCommand(*m_currentEventSelection,
+ i18n("Remove Triggers")));
+}
+
+void
+MatrixView::slotToggleChordsRuler()
+{
+ toggleWidget(m_chordNameRuler, "show_chords_ruler");
+}
+
+void
+MatrixView::slotToggleTempoRuler()
+{
+ toggleWidget(m_tempoRuler, "show_tempo_ruler");
+}
+
+void
+MatrixView::paintEvent(QPaintEvent* e)
+{
+ //!!! There's a lot of code shared between matrix and notation for
+ // dealing with step recording (the insertable note event stuff).
+ // It should probably be factored out into a base class, but I'm
+ // not sure I wouldn't rather wait until the functionality is all
+ // sorted in both matrix and notation so we can be sure how much
+ // of it is actually common.
+
+ EditView::paintEvent(e);
+
+ // now deal with any backlog of insertable notes that appeared
+ // during paint (because it's not safe to modify a segment from
+ // within a sub-event-loop in a processEvents call from a paint)
+ if (!m_pendingInsertableNotes.empty()) {
+ std::vector<std::pair<int, int> > notes = m_pendingInsertableNotes;
+ m_pendingInsertableNotes.clear();
+ for (unsigned int i = 0; i < notes.size(); ++i) {
+ slotInsertableNoteEventReceived(notes[i].first, notes[i].second, true);
+ }
+ }
+}
+
+void
+MatrixView::updateViewCaption()
+{
+ // Set client label
+ //
+ QString view = i18n("Matrix");
+ if (isDrumMode())
+ view = i18n("Percussion");
+
+ if (m_segments.size() == 1) {
+
+ TrackId trackId = m_segments[0]->getTrack();
+ Track *track =
+ m_segments[0]->getComposition()->getTrackById(trackId);
+
+ int trackPosition = -1;
+ if (track)
+ trackPosition = track->getPosition();
+
+ setCaption(i18n("%1 - Segment Track #%2 - %3")
+ .arg(getDocument()->getTitle())
+ .arg(trackPosition + 1)
+ .arg(view));
+
+ } else if (m_segments.size() == getDocument()->getComposition().getNbSegments()) {
+
+ setCaption(i18n("%1 - All Segments - %2")
+ .arg(getDocument()->getTitle())
+ .arg(view));
+
+ } else {
+
+ setCaption(i18n("%1 - 1 Segment - %2",
+ "%1 - %n Segments - %2",
+ m_segments.size())
+ .arg(getDocument()->getTitle())
+ .arg(view));
+ }
+}
+
+int MatrixView::computePostLayoutWidth()
+{
+ Segment *segment = m_segments[0];
+ Composition *composition = segment->getComposition();
+ int endX = int(m_hlayout.getXForTime
+ (composition->getBarEndForTime
+ (segment->getEndMarkerTime())));
+ int startX = int(m_hlayout.getXForTime
+ (composition->getBarStartForTime
+ (segment->getStartTime())));
+
+ int newWidth = int(getXbyWorldMatrix(endX - startX));
+
+ MATRIX_DEBUG << "MatrixView::readjustCanvasSize() : startX = "
+ << startX
+ << " endX = " << endX
+ << " newWidth = " << newWidth
+ << " endmarkertime : " << segment->getEndMarkerTime()
+ << " barEnd for time : " << composition->getBarEndForTime(segment->getEndMarkerTime())
+ << endl;
+
+ newWidth += 12;
+ if (isDrumMode())
+ newWidth += 12;
+
+ return newWidth;
+}
+
+bool MatrixView::getMinMaxPitches(int& minPitch, int& maxPitch)
+{
+ minPitch = MatrixVLayout::maxMIDIPitch + 1;
+ maxPitch = MatrixVLayout::minMIDIPitch - 1;
+
+ std::vector<MatrixStaff*>::iterator sit;
+ for (sit = m_staffs.begin(); sit != m_staffs.end(); ++sit) {
+
+ MatrixElementList *mel = (*sit)->getViewElementList();
+ MatrixElementList::iterator eit;
+ for (eit = mel->begin(); eit != mel->end(); ++eit) {
+
+ NotationElement *el = static_cast<NotationElement*>(*eit);
+ if (el->isNote()) {
+ Event* ev = el->event();
+ int pitch = ev->get
+ <Int>
+ (BaseProperties::PITCH);
+ if (minPitch > pitch)
+ minPitch = pitch;
+ if (maxPitch < pitch)
+ maxPitch = pitch;
+ }
+ }
+ }
+
+ return maxPitch >= minPitch;
+}
+
+void MatrixView::extendKeyMapping()
+{
+ int minStaffPitch, maxStaffPitch;
+ if (getMinMaxPitches(minStaffPitch, maxStaffPitch)) {
+ int minKMPitch = m_localMapping->getPitchForOffset(0);
+ int maxKMPitch = m_localMapping->getPitchForOffset(0)
+ + m_localMapping->getPitchExtent() - 1;
+ if (minStaffPitch < minKMPitch)
+ m_localMapping->getMap()[minStaffPitch] = std::string("");
+ if (maxStaffPitch > maxKMPitch)
+ m_localMapping->getMap()[maxStaffPitch] = std::string("");
+ }
+}
+
+void
+MatrixView::slotInsertableNoteEventReceived(int pitch, int velocity, bool noteOn)
+{
+ // hjj:
+ // The default insertion mode is implemented equivalently in
+ // notationviewslots.cpp:
+ // - proceed if notes do not overlap
+ // - make the chord if notes do overlap, and do not proceed
+
+ static int numberOfNotesOn = 0;
+ static time_t lastInsertionTime = 0;
+ if (!noteOn) {
+ numberOfNotesOn--;
+ return ;
+ }
+
+ KToggleAction *action = dynamic_cast<KToggleAction *>
+ (actionCollection()->action("toggle_step_by_step"));
+ if (!action) {
+ MATRIX_DEBUG << "WARNING: No toggle_step_by_step action" << endl;
+ return ;
+ }
+ if (!action->isChecked())
+ return ;
+
+ if (m_inPaintEvent) {
+ m_pendingInsertableNotes.push_back(std::pair<int, int>(pitch, velocity));
+ return ;
+ }
+
+ Segment &segment = *getCurrentSegment();
+
+ // If the segment is transposed, we want to take that into
+ // account. But the note has already been played back to the user
+ // at its untransposed pitch, because that's done by the MIDI THRU
+ // code in the sequencer which has no way to know whether a note
+ // was intended for step recording. So rather than adjust the
+ // pitch for playback according to the transpose setting, we have
+ // to adjust the stored pitch in the opposite direction.
+
+ pitch -= segment.getTranspose();
+
+ KTmpStatusMsg msg(i18n("Inserting note"), this);
+
+ MATRIX_DEBUG << "Inserting note at pitch " << pitch << endl;
+
+ Event modelEvent(Note::EventType, 0, 1);
+ modelEvent.set<Int>(BaseProperties::PITCH, pitch);
+ static timeT insertionTime(getInsertionTime());
+ if (insertionTime >= segment.getEndMarkerTime()) {
+ MATRIX_DEBUG << "WARNING: off end of segment" << endl;
+ return ;
+ }
+ time_t now;
+ time (&now);
+ double elapsed = difftime(now, lastInsertionTime);
+ time (&lastInsertionTime);
+
+ if (numberOfNotesOn <= 0 || elapsed > 10.0 ) {
+ numberOfNotesOn = 0;
+ insertionTime = getInsertionTime();
+ }
+ numberOfNotesOn++;
+ timeT endTime(insertionTime + m_snapGrid->getSnapTime(insertionTime));
+
+ if (endTime <= insertionTime) {
+ static bool showingError = false;
+ if (showingError)
+ return ;
+ showingError = true;
+ KMessageBox::sorry(this, i18n("Can't insert note: No grid duration selected"));
+ showingError = false;
+ return ;
+ }
+
+ MatrixInsertionCommand* command =
+ new MatrixInsertionCommand(segment, insertionTime, endTime, &modelEvent);
+
+ addCommandToHistory(command);
+
+ if (!isInChordMode()) {
+ slotSetInsertCursorPosition(endTime);
+ }
+}
+
+void
+MatrixView::slotInsertableNoteOnReceived(int pitch, int velocity)
+{
+ MATRIX_DEBUG << "MatrixView::slotInsertableNoteOnReceived: " << pitch << endl;
+ slotInsertableNoteEventReceived(pitch, velocity, true);
+}
+
+void
+MatrixView::slotInsertableNoteOffReceived(int pitch, int velocity)
+{
+ MATRIX_DEBUG << "MatrixView::slotInsertableNoteOffReceived: " << pitch << endl;
+ slotInsertableNoteEventReceived(pitch, velocity, false);
+}
+
+void
+MatrixView::slotToggleStepByStep()
+{
+ KToggleAction *action = dynamic_cast<KToggleAction *>
+ (actionCollection()->action("toggle_step_by_step"));
+ if (!action) {
+ MATRIX_DEBUG << "WARNING: No toggle_step_by_step action" << endl;
+ return ;
+ }
+ if (action->isChecked()) { // after toggling, that is
+ emit stepByStepTargetRequested(this);
+ } else {
+ emit stepByStepTargetRequested(0);
+ }
+}
+
+void
+MatrixView::slotUpdateInsertModeStatus()
+{
+ QString message;
+ if (isInChordMode()) {
+ message = i18n(" Chord ");
+ } else {
+ message = "";
+ }
+ m_insertModeLabel->setText(message);
+}
+
+void
+MatrixView::slotStepByStepTargetRequested(QObject *obj)
+{
+ KToggleAction *action = dynamic_cast<KToggleAction *>
+ (actionCollection()->action("toggle_step_by_step"));
+ if (!action) {
+ MATRIX_DEBUG << "WARNING: No toggle_step_by_step action" << endl;
+ return ;
+ }
+ action->setChecked(obj == this);
+}
+
+void
+MatrixView::slotInstrumentLevelsChanged(InstrumentId id,
+ const LevelInfo &info)
+{
+ if (!m_parameterBox)
+ return ;
+
+ Composition &comp = getDocument()->getComposition();
+
+ Track *track =
+ comp.getTrackById(m_staffs[0]->getSegment().getTrack());
+ if (!track || track->getInstrument() != id)
+ return ;
+
+ Instrument *instr = getDocument()->getStudio().
+ getInstrumentById(track->getInstrument());
+ if (!instr || instr->getType() != Instrument::SoftSynth)
+ return ;
+
+ float dBleft = AudioLevel::fader_to_dB
+ (info.level, 127, AudioLevel::LongFader);
+ float dBright = AudioLevel::fader_to_dB
+ (info.levelRight, 127, AudioLevel::LongFader);
+
+ m_parameterBox->setAudioMeter(dBleft, dBright,
+ AudioLevel::DB_FLOOR,
+ AudioLevel::DB_FLOOR);
+}
+
+void
+MatrixView::slotPercussionSetChanged(Instrument * newInstr)
+{
+ // Must be called only when in drum mode
+ assert(m_drumMode);
+
+ int resolution = 8;
+ if (newInstr && newInstr->getKeyMapping()) {
+ resolution = 11;
+ }
+
+ const MidiKeyMapping *mapping = 0;
+ if (newInstr) {
+ mapping = newInstr->getKeyMapping();
+ }
+
+ // Construct a local new keymapping :
+ if (m_localMapping)
+ delete m_localMapping;
+ if (mapping) {
+ m_localMapping = new MidiKeyMapping(*mapping);
+ extendKeyMapping();
+ } else {
+ m_localMapping = 0;
+ }
+
+ m_staffs[0]->setResolution(resolution);
+
+ delete m_pitchRuler;
+
+ QWidget *vport = m_pianoView->viewport();
+
+ // Create a new pitchruler widget
+ PitchRuler *pitchRuler;
+ if (newInstr && newInstr->getKeyMapping() &&
+ !newInstr->getKeyMapping()->getMap().empty()) {
+ pitchRuler = new PercussionPitchRuler(vport,
+ m_localMapping,
+ resolution); // line spacing
+ } else {
+ pitchRuler = new PianoKeyboard(vport);
+ }
+
+
+ QObject::connect
+ (pitchRuler, SIGNAL(hoveredOverKeyChanged(unsigned int)),
+ this, SLOT (slotHoveredOverKeyChanged(unsigned int)));
+
+ QObject::connect
+ (pitchRuler, SIGNAL(keyPressed(unsigned int, bool)),
+ this, SLOT (slotKeyPressed(unsigned int, bool)));
+
+ QObject::connect
+ (pitchRuler, SIGNAL(keySelected(unsigned int, bool)),
+ this, SLOT (slotKeySelected(unsigned int, bool)));
+
+ QObject::connect
+ (pitchRuler, SIGNAL(keyReleased(unsigned int, bool)),
+ this, SLOT (slotKeyReleased(unsigned int, bool)));
+
+ // Replace the old pitchruler widget
+ m_pitchRuler = pitchRuler;
+ m_pianoView->addChild(m_pitchRuler);
+ m_pitchRuler->show();
+ m_pianoView->setFixedWidth(pitchRuler->sizeHint().width());
+
+ // Update matrix canvas
+ readjustCanvasSize();
+ bool layoutApplied = applyLayout();
+ if (!layoutApplied)
+ KMessageBox::sorry(0, i18n("Couldn't apply piano roll layout"));
+ else {
+ MATRIX_DEBUG << "MatrixView : rendering elements\n";
+ m_staffs[0]->positionAllElements();
+ m_staffs[0]->getSegment().getRefreshStatus
+ (m_segmentsRefreshStatusIds[0]).setNeedsRefresh(false);
+ update();
+ }
+}
+
+void
+MatrixView::slotCanvasBottomWidgetHeightChanged(int newHeight)
+{
+ m_pianoView->setBottomMargin(newHeight +
+ m_canvasView->horizontalScrollBar()->height());
+}
+
+MatrixCanvasView* MatrixView::getCanvasView()
+{
+ return dynamic_cast<MatrixCanvasView *>(m_canvasView);
+}
+
+}
+#include "MatrixView.moc"
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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_MATRIXVIEW_H_
+#define _RG_MATRIXVIEW_H_
+
+#include "base/MidiProgram.h"
+#include "base/PropertyName.h"
+#include "base/SnapGrid.h"
+#include "gui/general/EditView.h"
+#include "gui/widgets/ZoomSlider.h"
+#include "MatrixHLayout.h"
+#include "MatrixVLayout.h"
+#include "MatrixCanvasView.h"
+#include <kdockwidget.h>
+#include <qpoint.h>
+#include <qsize.h>
+#include <vector>
+#include "base/Event.h"
+#include "document/ConfigGroups.h"
+
+
+class QWidget;
+class QPaintEvent;
+class QObject;
+class QMouseEvent;
+class QLabel;
+class QCursor;
+class QCanvas;
+class KComboBox;
+
+
+namespace Rosegarden
+{
+
+class Staff;
+class Segment;
+class RulerScale;
+class RosegardenGUIDoc;
+class QDeferScrollView;
+class PropertyViewRuler;
+class PropertyBox;
+class PitchRuler;
+class MidiKeyMapping;
+class MatrixStaff;
+class MatrixElement;
+class InstrumentParameterBox;
+class Instrument;
+class EventSelection;
+class Event;
+class ChordNameRuler;
+class LevelInfo;
+
+
+/**
+ * Matrix ("Piano Roll") View
+ *
+ * Note: we currently display only one staff
+ */
+class MatrixView : public EditView
+{
+ Q_OBJECT
+
+ friend class MatrixSelector;
+
+public:
+ MatrixView(RosegardenGUIDoc *doc,
+ std::vector<Segment *> segments,
+ QWidget *parent, bool drumMode);
+
+ virtual ~MatrixView();
+
+ virtual bool applyLayout(int staffNo = -1,
+ timeT startTime = 0,
+ timeT endTime = 0);
+
+ virtual void refreshSegment(Segment *segment,
+ timeT startTime = 0,
+ timeT endTime = 0);
+
+ QCanvas* canvas() { return getCanvasView()->canvas(); }
+
+ void setCanvasCursor(const QCursor &cursor) {
+ getCanvasView()->viewport()->setCursor(cursor);
+ }
+
+ MatrixStaff* getStaff(int i)
+ {
+ if (i >= 0 && unsigned(i) < m_staffs.size()) return m_staffs[i];
+ else return 0;
+ }
+
+ MatrixStaff *getStaff(const Segment &segment);
+
+ virtual void updateView();
+
+ bool isDrumMode() { return m_drumMode; }
+
+ /**
+ * Discover whether chord-mode insertions are enabled (as opposed
+ * to the default melody-mode)
+ */
+ bool isInChordMode();
+
+ /**
+ * Set the current event selection.
+ *
+ * If preview is true, sound the selection as well.
+ *
+ * If redrawNow is true, recolour the elements on the canvas;
+ * otherwise just line up a refresh for the next paint event.
+ *
+ * (If the selection has changed as part of a modification to a
+ * segment, redrawNow should be unnecessary and undesirable, as a
+ * paint event will occur in the next event loop following the
+ * command invocation anyway.)
+ */
+ virtual void setCurrentSelection(EventSelection* s,
+ bool preview = false,
+ bool redrawNow = false);
+
+ /**
+ * Set the current event selection to a single event
+ */
+ void setSingleSelectedEvent(int staffNo,
+ Event *event,
+ bool preview = false,
+ bool redrawNow = false);
+
+ /**
+ * Set the current event selection to a single event
+ */
+ void setSingleSelectedEvent(Segment &segment,
+ Event *event,
+ bool preview = false,
+ bool redrawNow = false);
+
+
+ /**
+ * Play a Note Event using the keyPressed() signal
+ */
+ void playNote(Event *event);
+
+ /**
+ * Play a preview (same as above but a simpler interface)
+ */
+ void playNote(const Segment &segment, int pitch, int velocity = -1);
+
+ /**
+ * Get the SnapGrid
+ */
+ const SnapGrid &getSnapGrid() const { return *m_snapGrid; }
+
+ /**
+ * Add a ruler that allows control of a single property -
+ * return the number of the added ruler
+ *
+ */
+ unsigned int addPropertyViewRuler(const PropertyName &property);
+
+ /**
+ * Remove a control ruler - return true if it's a valid ruler number
+ */
+ bool removePropertyViewRuler(unsigned int number);
+
+ /**
+ * Adjust an X coord by world matrix
+ */
+ double getXbyWorldMatrix(double value)
+ { return m_canvasView->worldMatrix().m11() * value; }
+
+ double getXbyInverseWorldMatrix(double value)
+ { return m_canvasView->inverseWorldMatrix().m11() * value; }
+
+ QPoint inverseMapPoint(const QPoint& p) { return m_canvasView->inverseMapPoint(p); }
+
+ /*
+ * Repaint the control rulers
+ *
+ */
+ void repaintRulers();
+
+ /*
+ * Readjust the canvas size
+ *
+ */
+ void readjustCanvasSize();
+
+ /*
+ * Scrolls the view such that the given time is centered
+ */
+ void scrollToTime(timeT t);
+
+ /**
+ * Get the local keyMapping (when in drum mode)
+ */
+ MidiKeyMapping *getKeyMapping() { return m_localMapping; }
+
+ /**
+ * Get the velocity currently set in the velocity menu.
+ */
+ int getCurrentVelocity() const;
+
+signals:
+ /**
+ * Emitted when the selection has been cut or copied
+ *
+ * @see MatrixSelector#hideSelection
+ */
+ void usedSelection();
+
+ void play();
+ void stop();
+ void fastForwardPlayback();
+ void rewindPlayback();
+ void fastForwardPlaybackToEnd();
+ void rewindPlaybackToBeginning();
+ void jumpPlaybackTo(timeT);
+ void panic();
+
+ void stepByStepTargetRequested(QObject *);
+
+ void editTriggerSegment(int);
+
+ void editTimeSignature(timeT);
+
+public slots:
+
+ /**
+ * put the indicationed text/object into the clipboard and remove * it
+ * from the document
+ */
+ virtual void slotEditCut();
+
+ /**
+ * put the indicationed text/object into the clipboard
+ */
+ virtual void slotEditCopy();
+
+ /**
+ * paste the clipboard into the document
+ */
+ virtual void slotEditPaste();
+
+ /**
+ * Delete the current selection
+ */
+ void slotEditDelete();
+
+ virtual void slotStepBackward(); // override from EditView
+ virtual void slotStepForward(); // override from EditView
+
+ void slotPreviewSelection();
+ void slotClearLoop();
+ void slotClearSelection();
+
+ /**
+ * Filter selection by event type
+ */
+ void slotFilterSelection(); // dummy - not actually functional yet
+
+ /// edition tools
+ void slotPaintSelected();
+ void slotEraseSelected();
+ void slotSelectSelected();
+ void slotMoveSelected();
+ void slotResizeSelected();
+
+ void slotToggleStepByStep();
+
+ /// status stuff
+ void slotUpdateInsertModeStatus();
+
+ /// transforms
+ void slotTransformsQuantize();
+ void slotTransformsRepeatQuantize();
+ void slotTransformsLegato();
+ void slotVelocityUp();
+ void slotVelocityDown();
+
+ /// settings
+ void slotToggleChordsRuler();
+ void slotToggleTempoRuler();
+
+ /// cursor moves
+ void slotJumpCursorToPlayback();
+ void slotJumpPlaybackToCursor();
+ void slotToggleTracking();
+
+ /// Canvas actions slots
+
+ /**
+ * Called when a mouse press occurred on a matrix element
+ * or somewhere on the staff
+ */
+ void slotMousePressed(timeT time, int pitch,
+ QMouseEvent*, MatrixElement*);
+
+ void slotMouseMoved(timeT time, int pitch, QMouseEvent*);
+ void slotMouseReleased(timeT time, int pitch, QMouseEvent*);
+
+ /**
+ * Called when the mouse cursor moves over a different height on
+ * the staff
+ *
+ * @see MatrixCanvasView#hoveredOverNoteChanged()
+ */
+ void slotHoveredOverNoteChanged(int evPitch, bool haveEvent,
+ timeT evTime);
+
+ /**
+ * Called when the mouse cursor moves over a different key on
+ * the piano keyboard
+ *
+ * @see PianoKeyboard#hoveredOverKeyChanged()
+ */
+ void slotHoveredOverKeyChanged(unsigned int);
+
+ /**
+ * Called when the mouse cursor moves over a note which is at a
+ * different time on the staff
+ *
+ * @see MatrixCanvasView#hoveredOverNoteChange()
+ */
+ void slotHoveredOverAbsoluteTimeChanged(unsigned int);
+
+ /**
+ * Set the time pointer position during playback
+ */
+ void slotSetPointerPosition(timeT time);
+
+ /**
+ * Set the time pointer position during playback
+ */
+ void slotSetPointerPosition(timeT time,
+ bool scroll);
+
+ /**
+ * Set the insertion pointer position (from the bottom LoopRuler)
+ */
+ void slotSetInsertCursorPosition(timeT position, bool scroll);
+
+ virtual void slotSetInsertCursorPosition(timeT position) {
+ slotSetInsertCursorPosition(position, true);
+ }
+
+ /**
+ * Catch the keyboard being pressed
+ */
+ void slotKeyPressed(unsigned int y, bool repeating);
+
+ /**
+ * Catch the keyboard being released
+ */
+ void slotKeyReleased(unsigned int y, bool repeating);
+
+ /**
+ * Catch the keyboard being pressed with selection modifier
+ */
+ void slotKeySelected(unsigned int y, bool repeating);
+
+ /**
+ * Handle scrolling between view and PianoKeyboard
+ */
+ void slotVerticalScrollPianoKeyboard(int y);
+
+ /**
+ * Close
+ */
+ void closeWindow();
+
+ /**
+ * A new selection has been acquired by a tool
+ */
+ void slotNewSelection();
+
+ /**
+ * Set the snaptime of the grid from an item in the snap combo
+ */
+ void slotSetSnapFromIndex(int);
+
+ /**
+ * Set the snaptime of the grid based on the name of the invoking action
+ */
+ void slotSetSnapFromAction();
+
+ /**
+ * Set the snaptime of the grid
+ */
+ void slotSetSnap(timeT);
+
+ /**
+ * Quantize a selection to a given level
+ */
+ void slotQuantizeSelection(int);
+
+ /**
+ * Collapse equal pitch notes
+ */
+ void slotTransformsCollapseNotes();
+
+ /**
+ * Pop-up the velocity modification dialog
+ */
+ void slotSetVelocities();
+
+ /**
+ * Set selected event velocities to whatever's in the velocity widget
+ */
+ void slotSetVelocitiesToCurrent();
+
+ /**
+ * Pop-up the select trigger segment dialog
+ */
+ void slotTriggerSegment();
+
+ /**
+ * Clear triggers from selection
+ */
+ void slotRemoveTriggers();
+
+ /**
+ * Change horizontal zoom
+ */
+ void slotChangeHorizontalZoom(int);
+
+ void slotZoomIn();
+ void slotZoomOut();
+
+ /**
+ * Select all
+ */
+ void slotSelectAll();
+
+ /**
+ * Keyboard insert
+ */
+ void slotInsertNoteFromAction();
+
+ /// Note-on received asynchronously -- consider step-by-step editing
+ void slotInsertableNoteOnReceived(int pitch, int velocity);
+
+ /// Note-off received asynchronously -- consider step-by-step editing
+ void slotInsertableNoteOffReceived(int pitch, int velocity);
+
+ /// Note-on or note-off received asynchronously -- as above
+ void slotInsertableNoteEventReceived(int pitch, int velocity, bool noteOn);
+
+ /// The given QObject has originated a step-by-step-editing request
+ void slotStepByStepTargetRequested(QObject *);
+
+ void slotInstrumentLevelsChanged(InstrumentId,
+ const LevelInfo &);
+
+ /// Set the velocity menu to the given value
+ void slotSetCurrentVelocity(int);
+ void slotSetCurrentVelocityFromSelection();
+
+protected slots:
+ void slotCanvasBottomWidgetHeightChanged(int newHeight);
+
+ /**
+ * A new percussion key mapping has to be displayed
+ */
+ void slotPercussionSetChanged(Instrument *);
+
+ /**
+ * Re-dock the parameters box to its initial position
+ */
+ void slotDockParametersBack();
+
+ /**
+ * The parameters box was closed
+ */
+ void slotParametersClosed();
+
+ /**
+ * The parameters box was docked back
+ */
+ void slotParametersDockedBack(KDockWidget*, KDockWidget::DockPosition);
+
+ /**
+ * The instrument for this track may have changed
+ */
+ void slotCheckTrackAssignments();
+
+ void slotToolHelpChanged(const QString &);
+ void slotMouseEnteredCanvasView();
+ void slotMouseLeftCanvasView();
+
+protected:
+ virtual RulerScale* getHLayout();
+
+ virtual Segment *getCurrentSegment();
+ virtual Staff *getCurrentStaff();
+ virtual timeT getInsertionTime();
+
+ /**
+ * save general Options like all bar positions and status as well
+ * as the geometry and the recent file list to the configuration
+ * file
+ */
+ virtual void slotSaveOptions();
+
+ /**
+ * read general Options again and initialize all variables like the recent file list
+ */
+ virtual void readOptions();
+
+ /**
+ * create menus and toolbars
+ */
+ virtual void setupActions();
+
+ /**
+ * setup status bar
+ */
+ virtual void initStatusBar();
+
+ /**
+ * update the current quantize level from selection or entire segment
+ */
+ virtual void updateQuantizeCombo();
+
+ /**
+ * Return the size of the MatrixCanvasView
+ */
+ virtual QSize getViewSize();
+
+ /**
+ * Set the size of the MatrixCanvasView
+ */
+ virtual void setViewSize(QSize);
+
+ virtual MatrixCanvasView *getCanvasView();
+
+ /**
+ * Init matrix actions toolbar
+ */
+ void initActionsToolbar();
+
+ /**
+ * Zoom toolbar
+ */
+ void initZoomToolbar();
+
+ /**
+ * Test whether we've had too many preview notes recently
+ */
+ bool canPreviewAnotherNote();
+
+ virtual void paintEvent(QPaintEvent* e);
+
+ virtual void updateViewCaption();
+
+ int computePostLayoutWidth();
+
+ /**
+ * Get min and max pitches of notes on matrix.
+ * Return false if no notes.
+ */
+ bool getMinMaxPitches(int& minPitch, int& maxPitch);
+
+ /**
+ * If necessary, extend local keymapping to contain
+ * all notes currently on staff
+ */
+ void extendKeyMapping();
+
+ //--------------- Data members ---------------------------------
+
+ std::vector<MatrixStaff*> m_staffs;
+
+ MatrixHLayout m_hlayout;
+ MatrixVLayout m_vlayout;
+ SnapGrid *m_snapGrid;
+
+ timeT m_lastEndMarkerTime;
+
+ // Status bar elements
+ QLabel* m_hoveredOverAbsoluteTime;
+ QLabel* m_hoveredOverNoteName;
+ QLabel *m_selectionCounter;
+ QLabel *m_insertModeLabel;
+ bool m_haveHoveredOverNote;
+
+ /**
+ * used in slotHoveredOverKeyChanged to track moves over the piano
+ * keyboard
+ */
+ int m_previousEvPitch;
+
+ KDockWidget *m_dockLeft;
+ MatrixCanvasView *m_canvasView;
+ QDeferScrollView *m_pianoView;
+ PitchRuler *m_pitchRuler;
+
+ MidiKeyMapping *m_localMapping;
+
+ // The last note we sent in case we're swooshing up and
+ // down the keyboard and don't want repeat notes sending
+ //
+ MidiByte m_lastNote;
+
+ // The first note we sent in similar case (only used for
+ // doing effective sweep selections
+ //
+ MidiByte m_firstNote;
+
+ PropertyName m_selectedProperty;
+
+ // The parameter box
+ //
+ InstrumentParameterBox *m_parameterBox;
+
+ // Toolbar flora
+ //
+ KComboBox *m_velocityCombo;
+ KComboBox *m_quantizeCombo;
+ KComboBox *m_snapGridCombo;
+ ZoomSlider<double> *m_hZoomSlider;
+ ZoomSlider<double> *m_vZoomSlider;
+ QLabel *m_zoomLabel;
+
+ // Hold our matrix quantization values and snap values
+ //
+ std::vector<timeT> m_quantizations;
+ std::vector<timeT> m_snapValues;
+
+ std::vector<std::pair<PropertyViewRuler*, PropertyBox*> > m_propertyViewRulers;
+
+ ChordNameRuler *m_chordNameRuler;
+ QWidget *m_tempoRuler;
+
+ // ruler used to scale tempo and chord name ruler
+ ZoomableMatrixHLayoutRulerScale* m_referenceRuler;
+
+ std::vector<std::pair<int, int> > m_pendingInsertableNotes;
+
+ bool m_playTracking;
+ bool m_dockVisible;
+ bool m_drumMode;
+
+ bool m_mouseInCanvasView;
+ QString m_toolContextHelp;
+};
+
+// Commented this out - was a MatrixView inner class, but we get a warning
+// that Q_OBJECT can't be used in an inner class - gl
+//
+
+// class NoteSender : public QObject
+// {
+// Q_OBJECT
+
+// public:
+// NoteSender(int i, int p) : m_insid(i), m_pitch(p) { }
+// virtual ~NoteSender();
+
+// public slots:
+// void sendNote();
+
+// private:
+// int m_insid, m_pitch;
+// };
+
+
+}
+
+#endif
diff --git a/src/gui/editors/matrix/PianoKeyboard.cpp b/src/gui/editors/matrix/PianoKeyboard.cpp
new file mode 100644
index 0000000..e4641d0
--- /dev/null
+++ b/src/gui/editors/matrix/PianoKeyboard.cpp
@@ -0,0 +1,299 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
+
+/*
+ Rosegarden
+ A MIDI and audio sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "PianoKeyboard.h"
+#include "misc/Debug.h"
+
+#include "gui/general/GUIPalette.h"
+#include "gui/general/MidiPitchLabel.h"
+#include "gui/rulers/PitchRuler.h"
+#include "MatrixStaff.h"
+#include "MatrixView.h"
+#include <qcolor.h>
+#include <qcursor.h>
+#include <qevent.h>
+#include <qfont.h>
+#include <qpainter.h>
+#include <qsize.h>
+#include <qwidget.h>
+
+
+namespace Rosegarden
+{
+
+const unsigned int _smallWhiteKeyHeight = 14;
+const unsigned int _whiteKeyHeight = 18;
+
+PianoKeyboard::PianoKeyboard(QWidget *parent, int keys)
+ : PitchRuler(parent),
+ m_keySize(48, 18),
+ m_blackKeySize(24, 8),
+ m_nbKeys(keys),
+ m_mouseDown(false),
+ m_hoverHighlight(new QWidget(this)),
+ m_lastHoverHighlight(0),
+ m_lastKeyPressed(0)
+{
+ m_hoverHighlight->hide();
+ m_hoverHighlight->setPaletteBackgroundColor(GUIPalette::getColour(GUIPalette::MatrixKeyboardFocus));
+
+ setPaletteBackgroundColor(QColor(238, 238, 224));
+
+ computeKeyPos();
+ setMouseTracking(true);
+}
+
+QSize PianoKeyboard::sizeHint() const
+{
+ return QSize(m_keySize.width(),
+ m_keySize.height() * m_nbKeys);
+}
+
+QSize PianoKeyboard::minimumSizeHint() const
+{
+ return m_keySize;
+}
+
+void PianoKeyboard::computeKeyPos()
+{
+ // int y = -9;
+ int y = -4;
+
+ unsigned int posInOctave = 0,
+ keyHeight = _smallWhiteKeyHeight;
+
+ for (unsigned int i = 0; i < m_nbKeys; ++i) {
+ posInOctave = (i + 5) % 7;
+
+ if (y >= 0) {
+ m_whiteKeyPos.push_back(y);
+ m_allKeyPos.push_back(y);
+ }
+
+ if (posInOctave == 2)
+ m_labelKeyPos.push_back(y + (keyHeight * 3 / 4) - 2);
+
+ if (posInOctave == 0 ||
+ posInOctave == 2 ||
+ posInOctave == 6 ||
+ posInOctave == 3) { // draw shorter white key
+
+
+ keyHeight = _smallWhiteKeyHeight;
+
+ if (posInOctave == 2 ||
+ posInOctave == 6)
+ --keyHeight;
+
+ } else {
+
+ keyHeight = _whiteKeyHeight;
+ }
+
+ if (posInOctave != 2 && posInOctave != 6) { // draw black key
+
+ unsigned int bY = y + keyHeight - m_blackKeySize.height() / 2;
+
+ m_blackKeyPos.push_back(bY);
+ m_allKeyPos.push_back(bY);
+
+ }
+
+ y += keyHeight;
+ }
+}
+
+void PianoKeyboard::paintEvent(QPaintEvent*)
+{
+ static QFont *pFont = 0;
+ if (!pFont) {
+ pFont = new QFont();
+ pFont->setPixelSize(9);
+ }
+
+ QPainter paint(this);
+
+ paint.setFont(*pFont);
+
+ for (unsigned int i = 0; i < m_whiteKeyPos.size(); ++i)
+ paint.drawLine(0, m_whiteKeyPos[i],
+ m_keySize.width(), m_whiteKeyPos[i]);
+
+ for (unsigned int i = 0; i < m_labelKeyPos.size(); ++i) {
+
+ int pitch = (m_labelKeyPos.size() - i) * 12;
+
+ // for some reason I don't immediately comprehend,
+ // m_labelKeyPos contains two more octaves than we need
+ pitch -= 24;
+
+ MidiPitchLabel label(pitch);
+ paint.drawText(m_blackKeySize.width(), m_labelKeyPos[i],
+ label.getQString());
+ }
+
+ paint.setBrush(colorGroup().foreground());
+
+ for (unsigned int i = 0; i < m_blackKeyPos.size(); ++i)
+ paint.drawRect(0, m_blackKeyPos[i],
+ m_blackKeySize.width(), m_blackKeySize.height());
+}
+
+void PianoKeyboard::enterEvent(QEvent *)
+{
+ //drawHoverNote(e->y());
+}
+
+void PianoKeyboard::leaveEvent(QEvent*)
+{
+ m_hoverHighlight->hide();
+
+ int pos = mapFromGlobal( cursor().pos() ).x();
+ if ( pos > m_keySize.width() - 5 || pos < 0 ) { // bit of a hack
+ emit keyReleased(m_lastKeyPressed, false);
+ }
+}
+
+void PianoKeyboard::drawHoverNote(int evPitch)
+{
+ if (m_lastHoverHighlight != evPitch) {
+ //MATRIX_DEBUG << "PianoKeyboard::drawHoverNote : note = " << evPitch << endl;
+ m_lastHoverHighlight = evPitch;
+
+ int count = 0;
+ std::vector<unsigned int>::iterator it;
+ for (it = m_allKeyPos.begin(); it != m_allKeyPos.end(); ++it, ++count) {
+ if (126 - evPitch == count) {
+ int width = m_keySize.width() - 8;
+ int yPos = *it + 5;
+
+ // check if this is a black key
+ //
+ std::vector<unsigned int>::iterator bIt;
+ bool isBlack = false;
+ for (bIt = m_blackKeyPos.begin(); bIt != m_blackKeyPos.end(); ++bIt) {
+ if (*bIt == *it) {
+ isBlack = true;
+ break;
+ }
+ }
+
+ // Adjust for black note
+ //
+ if (isBlack) {
+ width = m_blackKeySize.width() - 8;
+ yPos -= 3;
+ } else {
+ // If a white note then ensure that we allow for short/tall ones
+ //
+ std::vector<unsigned int>::iterator wIt = m_whiteKeyPos.begin(), tIt;
+
+ while (wIt != m_whiteKeyPos.end()) {
+ if (*wIt == *it) {
+ tIt = wIt;
+
+ if (++tIt != m_whiteKeyPos.end()) {
+ //MATRIX_DEBUG << "WHITE KEY HEIGHT = " << *tIt - *wIt << endl;
+ if (*tIt - *wIt == _whiteKeyHeight) {
+ yPos += 2;
+ }
+
+ }
+ }
+
+ ++wIt;
+ }
+
+
+ }
+
+ m_hoverHighlight->setFixedSize(width, 4);
+ m_hoverHighlight->move(3, yPos);
+ m_hoverHighlight->show();
+
+ return ;
+ }
+ }
+ }
+
+
+}
+
+void PianoKeyboard::mouseMoveEvent(QMouseEvent* e)
+{
+ // The routine to work out where this should appear doesn't coincide with the note
+ // that we send to the sequencer - hence this is a bit pointless and crap at the moment.
+ // My own fault it's so crap but there you go.
+ //
+ // RWB (20040220)
+ //
+ MatrixView *matrixView = dynamic_cast<MatrixView*>(topLevelWidget());
+ if (matrixView) {
+ MatrixStaff *staff = matrixView->getStaff(0);
+
+ if (staff) {
+ drawHoverNote(staff->getHeightAtCanvasCoords(e->x(), e->y()));
+ }
+ }
+
+ if (e->state() & Qt::LeftButton) {
+ if (m_selecting)
+ emit keySelected(e->y(), true);
+ else
+ emit keyPressed(e->y(), true); // we're swooshing
+
+ emit keyReleased(m_lastKeyPressed, true);
+ m_lastKeyPressed = e->y();
+ } else
+ emit hoveredOverKeyChanged(e->y());
+}
+
+void PianoKeyboard::mousePressEvent(QMouseEvent *e)
+{
+ Qt::ButtonState bs = e->state();
+
+ if (e->button() == LeftButton) {
+ m_mouseDown = true;
+ m_selecting = (bs & Qt::ShiftButton);
+ m_lastKeyPressed = e->y();
+
+ if (m_selecting)
+ emit keySelected(e->y(), false);
+ else
+ emit keyPressed(e->y(), false);
+ }
+}
+
+void PianoKeyboard::mouseReleaseEvent(QMouseEvent *e)
+{
+ if (e->button() == LeftButton) {
+ m_mouseDown = false;
+ m_selecting = false;
+ emit keyReleased(e->y(), false);
+ }
+}
+
+}
+#include "PianoKeyboard.moc"
diff --git a/src/gui/editors/matrix/PianoKeyboard.h b/src/gui/editors/matrix/PianoKeyboard.h
new file mode 100644
index 0000000..e8b06bb
--- /dev/null
+++ b/src/gui/editors/matrix/PianoKeyboard.h
@@ -0,0 +1,133 @@
+
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
+
+/*
+ Rosegarden
+ A MIDI and audio sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_PIANOKEYBOARD_H_
+#define _RG_PIANOKEYBOARD_H_
+
+#include "gui/rulers/PitchRuler.h"
+#include <qsize.h>
+#include <vector>
+
+
+class QWidget;
+class QPaintEvent;
+class QMouseEvent;
+class QEvent;
+
+
+namespace Rosegarden
+{
+
+
+
+class PianoKeyboard : public PitchRuler
+{
+ Q_OBJECT
+public:
+ PianoKeyboard(QWidget *parent, int keys = 88);
+
+ virtual QSize sizeHint() const;
+ virtual QSize minimumSizeHint() const;
+
+ /*
+ * We want to be able to call this from the matrix view
+ */
+ void drawHoverNote(int evPitch);
+
+signals:
+
+ /**
+ * A key has been clicked on the keyboard.
+ *
+ * The repeating flag is there to tell the MatrixView not to send
+ * the same note again as we're in the middle of a swoosh.
+ * MatrixView does the y -> Note calculation.
+ */
+ void keyPressed(unsigned int y, bool repeating);
+
+ /**
+ * A key has been clicked with the selection modifier pressed.
+ * The MatrixView will probably interpret this as meaning to
+ * select all notes of that pitch.
+ *
+ * The repeating flag is there to tell the MatrixView not to
+ * clear the selection as we're in the middle of a swoosh.
+ * MatrixView does the y -> Note calculation.
+ */
+ void keySelected(unsigned int y, bool repeating);
+
+ /**
+ * A key has been released on the keyboard.
+ *
+ * The repeating flag is there to tell the MatrixView not to send
+ * the same note again as we're in the middle of a swoosh.
+ * MatrixView does the y -> Note calculation.
+ */
+ void keyReleased(unsigned int y, bool repeating);
+
+ /**
+ * Emitted when the mouse cursor moves to a different key when
+ * not clicking or selecting.
+ * MatrixView does the y -> Note calculation.
+ */
+ void hoveredOverKeyChanged(unsigned int y);
+
+protected:
+
+ virtual void paintEvent(QPaintEvent*);
+
+ virtual void mouseMoveEvent(QMouseEvent*);
+ virtual void mousePressEvent(QMouseEvent*);
+ virtual void mouseReleaseEvent(QMouseEvent*);
+ virtual void enterEvent(QEvent *);
+ virtual void leaveEvent(QEvent *);
+
+ // compute all key positions and store them
+ //
+ void computeKeyPos();
+
+ //--------------- Data members ---------------------------------
+ QSize m_keySize;
+ QSize m_blackKeySize;
+ unsigned int m_nbKeys;
+
+ std::vector<unsigned int> m_whiteKeyPos;
+ std::vector<unsigned int> m_blackKeyPos;
+ std::vector<unsigned int> m_labelKeyPos;
+ std::vector<unsigned int> m_allKeyPos;
+
+ bool m_mouseDown;
+ bool m_selecting;
+
+ // highlight element on the keyboard
+ QWidget *m_hoverHighlight;
+ int m_lastHoverHighlight;
+ int m_lastKeyPressed;
+};
+
+
+}
+
+#endif
diff --git a/src/gui/editors/matrix/QCanvasMatrixDiamond.cpp b/src/gui/editors/matrix/QCanvasMatrixDiamond.cpp
new file mode 100644
index 0000000..582b53a
--- /dev/null
+++ b/src/gui/editors/matrix/QCanvasMatrixDiamond.cpp
@@ -0,0 +1,82 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
+
+/*
+ Rosegarden
+ A MIDI and audio sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "QCanvasMatrixDiamond.h"
+
+#include "MatrixElement.h"
+#include "QCanvasMatrixRectangle.h"
+#include <qcanvas.h>
+#include <qpainter.h>
+#include <qpointarray.h>
+#include <qpoint.h>
+
+
+namespace Rosegarden
+{
+
+QCanvasMatrixDiamond::QCanvasMatrixDiamond(MatrixElement &n,
+ QCanvas* canvas) :
+ QCanvasMatrixRectangle(n, canvas)
+{}
+
+QCanvasMatrixDiamond::~QCanvasMatrixDiamond()
+{
+ hide();
+}
+
+QPointArray QCanvasMatrixDiamond::areaPoints() const
+{
+ QPointArray pa(4);
+ int pw = (pen().width() + 1) / 2;
+ if ( pw < 1 )
+ pw = 1;
+ if ( pen() == NoPen )
+ pw = 0;
+ pa[0] = QPoint((int)x() - height() / 2 - pw, (int)y() - pw);
+ pa[1] = pa[0] + QPoint(height() + pw * 2, 0);
+ pa[2] = pa[1] + QPoint(0, height() + pw * 2);
+ pa[3] = pa[0] + QPoint(0, height() + pw * 2);
+ return pa;
+}
+
+void QCanvasMatrixDiamond::drawShape(QPainter & p)
+{
+ p.save();
+ p.setWorldXForm(false);
+
+ QPointArray pa(4);
+ int q = height() / 2 + 2;
+ QPoint mapPos = p.worldMatrix().map(QPoint(int(x()), int(y())));
+
+ pa[0] = QPoint(mapPos.x(), mapPos.y() - 3);
+ pa[1] = QPoint(mapPos.x() + q, mapPos.y() - 3 + q);
+ pa[2] = pa[0] + QPoint(0, q * 2);
+ pa[3] = pa[1] - QPoint(q * 2, 0);
+ p.drawConvexPolygon(pa);
+
+ p.restore();
+}
+
+}
diff --git a/src/gui/editors/matrix/QCanvasMatrixDiamond.h b/src/gui/editors/matrix/QCanvasMatrixDiamond.h
new file mode 100644
index 0000000..5163b12
--- /dev/null
+++ b/src/gui/editors/matrix/QCanvasMatrixDiamond.h
@@ -0,0 +1,61 @@
+
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
+
+/*
+ Rosegarden
+ A MIDI and audio sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_QCANVASMATRIXDIAMOND_H_
+#define _RG_QCANVASMATRIXDIAMOND_H_
+
+#include "QCanvasMatrixRectangle.h"
+#include <qpointarray.h>
+
+
+class QPainter;
+class QCanvas;
+
+
+namespace Rosegarden
+{
+
+class MatrixElement;
+
+
+/**
+ * A QCanvas diamond shape referencing a MatrixElement
+ */
+class QCanvasMatrixDiamond : public QCanvasMatrixRectangle
+{
+public:
+ QCanvasMatrixDiamond(MatrixElement&, QCanvas *);
+ ~QCanvasMatrixDiamond();
+
+ QPointArray areaPoints() const;
+
+protected:
+ void drawShape(QPainter &);
+};
+
+
+}
+
+#endif
diff --git a/src/gui/editors/matrix/QCanvasMatrixRectangle.cpp b/src/gui/editors/matrix/QCanvasMatrixRectangle.cpp
new file mode 100644
index 0000000..a27b480
--- /dev/null
+++ b/src/gui/editors/matrix/QCanvasMatrixRectangle.cpp
@@ -0,0 +1,44 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
+
+/*
+ Rosegarden
+ A MIDI and audio sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "QCanvasMatrixRectangle.h"
+
+#include "MatrixElement.h"
+#include <qcanvas.h>
+
+
+namespace Rosegarden
+{
+
+QCanvasMatrixRectangle::QCanvasMatrixRectangle(MatrixElement& n,
+ QCanvas* canvas)
+ : QCanvasRectangle(canvas),
+ m_matrixElement(n)
+{}
+
+QCanvasMatrixRectangle::~QCanvasMatrixRectangle()
+{}
+
+}
diff --git a/src/gui/editors/matrix/QCanvasMatrixRectangle.h b/src/gui/editors/matrix/QCanvasMatrixRectangle.h
new file mode 100644
index 0000000..64b6e65
--- /dev/null
+++ b/src/gui/editors/matrix/QCanvasMatrixRectangle.h
@@ -0,0 +1,60 @@
+
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
+
+/*
+ Rosegarden
+ A MIDI and audio sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_QCANVASMATRIXRECTANGLE_H_
+#define _RG_QCANVASMATRIXRECTANGLE_H_
+
+#include <qcanvas.h>
+
+
+namespace Rosegarden
+{
+
+class MatrixElement;
+
+
+/**
+ * A QCanvasRectangle referencing a MatrixElement
+ */
+class QCanvasMatrixRectangle : public QCanvasRectangle
+{
+public:
+ QCanvasMatrixRectangle(MatrixElement&, QCanvas*);
+
+ virtual ~QCanvasMatrixRectangle();
+
+ MatrixElement& getMatrixElement() { return m_matrixElement; }
+
+protected:
+ //--------------- Data members ---------------------------------
+
+ MatrixElement& m_matrixElement;
+
+};
+
+
+}
+
+#endif
diff --git a/src/gui/editors/notation/ClefInserter.cpp b/src/gui/editors/notation/ClefInserter.cpp
new file mode 100644
index 0000000..f39327e
--- /dev/null
+++ b/src/gui/editors/notation/ClefInserter.cpp
@@ -0,0 +1,132 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
+
+/*
+ Rosegarden
+ A MIDI and audio sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "ClefInserter.h"
+
+#include <klocale.h>
+#include "base/Event.h"
+#include "base/NotationTypes.h"
+#include "base/ViewElement.h"
+#include "commands/notation/ClefInsertionCommand.h"
+#include "gui/general/EditTool.h"
+#include "gui/general/LinedStaff.h"
+#include "NotationElement.h"
+#include "NotationTool.h"
+#include "NotationView.h"
+#include "NotePixmapFactory.h"
+#include <kaction.h>
+#include <qiconset.h>
+#include <qstring.h>
+
+
+namespace Rosegarden
+{
+
+ClefInserter::ClefInserter(NotationView* view)
+ : NotationTool("ClefInserter", view),
+ m_clef(Clef::Treble)
+{
+ QIconSet icon = QIconSet(NotePixmapFactory::toQPixmap(NotePixmapFactory::
+ makeToolbarPixmap("select")));
+ new KAction(i18n("Switch to Select Tool"), icon, 0, this,
+ SLOT(slotSelectSelected()), actionCollection(),
+ "select");
+
+ new KAction(i18n("Switch to Erase Tool"), "eraser", 0, this,
+ SLOT(slotEraseSelected()), actionCollection(),
+ "erase");
+
+ icon = QIconSet
+ (NotePixmapFactory::toQPixmap(NotePixmapFactory::
+ makeToolbarPixmap("crotchet")));
+ new KAction(i18n("Switch to Inserting Notes"), icon, 0, this,
+ SLOT(slotNotesSelected()), actionCollection(),
+ "notes");
+
+ createMenu("clefinserter.rc");
+}
+
+void ClefInserter::slotNotesSelected()
+{
+ m_nParentView->slotLastNoteAction();
+}
+
+void ClefInserter::slotEraseSelected()
+{
+ m_parentView->actionCollection()->action("erase")->activate();
+}
+
+void ClefInserter::slotSelectSelected()
+{
+ m_parentView->actionCollection()->action("select")->activate();
+}
+
+void ClefInserter::ready()
+{
+ m_nParentView->setCanvasCursor(Qt::crossCursor);
+ m_nParentView->setHeightTracking(false);
+}
+
+void ClefInserter::setClef(std::string clefType)
+{
+ m_clef = clefType;
+}
+
+void ClefInserter::handleLeftButtonPress(timeT,
+ int,
+ int staffNo,
+ QMouseEvent* e,
+ ViewElement*)
+{
+ if (staffNo < 0)
+ return ;
+ Event *clef = 0, *key = 0;
+
+ LinedStaff *staff = m_nParentView->getLinedStaff(staffNo);
+
+ NotationElementList::iterator closestElement =
+ staff->getClosestElementToCanvasCoords(e->x(), (int)e->y(),
+ clef, key, false, -1);
+
+ if (closestElement == staff->getViewElementList()->end())
+ return ;
+
+ timeT time = (*closestElement)->event()->getAbsoluteTime(); // not getViewAbsoluteTime()
+
+
+ ClefInsertionCommand *command =
+ new ClefInsertionCommand(staff->getSegment(), time, m_clef);
+
+ m_nParentView->addCommandToHistory(command);
+
+ Event *event = command->getLastInsertedEvent();
+ if (event)
+ m_nParentView->setSingleSelectedEvent(staffNo, event);
+}
+
+const QString ClefInserter::ToolName = "clefinserter";
+
+}
+#include "ClefInserter.moc"
diff --git a/src/gui/editors/notation/ClefInserter.h b/src/gui/editors/notation/ClefInserter.h
new file mode 100644
index 0000000..460bfa5
--- /dev/null
+++ b/src/gui/editors/notation/ClefInserter.h
@@ -0,0 +1,83 @@
+
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
+
+/*
+ Rosegarden
+ A MIDI and audio sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_CLEFINSERTER_H_
+#define _RG_CLEFINSERTER_H_
+
+#include "base/NotationTypes.h"
+#include "NotationTool.h"
+#include <qstring.h>
+#include "base/Event.h"
+
+
+class QMouseEvent;
+
+
+namespace Rosegarden
+{
+
+class ViewElement;
+class NotationView;
+
+
+/**
+ * This tool will insert clefs on mouse click events
+ */
+class ClefInserter : public NotationTool
+{
+ Q_OBJECT
+
+ friend class NotationToolBox;
+
+public:
+ void setClef(std::string clefType);
+
+ virtual void ready();
+
+ virtual void handleLeftButtonPress(timeT,
+ int height,
+ int staffNo,
+ QMouseEvent*,
+ ViewElement* el);
+ static const QString ToolName;
+
+protected slots:
+ void slotNotesSelected();
+ void slotEraseSelected();
+ void slotSelectSelected();
+
+protected:
+ ClefInserter(NotationView*);
+
+ //--------------- Data members ---------------------------------
+
+ Clef m_clef;
+};
+
+
+
+}
+
+#endif
diff --git a/src/gui/editors/notation/FontViewFrame.cpp b/src/gui/editors/notation/FontViewFrame.cpp
new file mode 100644
index 0000000..ab0498f
--- /dev/null
+++ b/src/gui/editors/notation/FontViewFrame.cpp
@@ -0,0 +1,252 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
+
+/*
+ Rosegarden
+ A MIDI and audio sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "FontViewFrame.h"
+#include <kapplication.h>
+
+#include <klocale.h>
+#include <kmessagebox.h>
+#include <qfontmetrics.h>
+#include <qframe.h>
+#include <qsize.h>
+#include <qstring.h>
+#include <qwidget.h>
+#include <qpainter.h>
+
+#ifdef HAVE_XFT
+#include <ft2build.h>
+#include FT_FREETYPE_H
+#include FT_OUTLINE_H
+#include FT_GLYPH_H
+#include <X11/Xft/Xft.h>
+#endif
+
+namespace Rosegarden
+{
+
+FontViewFrame::FontViewFrame( int pixelSize, QWidget* parent, const char* name ) :
+ QFrame(parent, name),
+ m_fontSize(pixelSize),
+ m_tableFont(0)
+{
+ setBackgroundMode(PaletteBase);
+ setFrameStyle(Panel | Sunken);
+ setMargin(8);
+ setRow(0);
+}
+
+FontViewFrame::~FontViewFrame()
+{
+ // empty
+}
+
+void
+FontViewFrame::setFont(QString font)
+{
+ m_fontName = font;
+ loadFont();
+ update();
+}
+
+void
+FontViewFrame::loadFont()
+{
+#ifdef HAVE_XFT
+ if (m_tableFont) {
+ XftFontClose(x11AppDisplay(), (XftFont *)m_tableFont);
+ }
+ m_tableFont = 0;
+
+ static bool haveDir = false;
+ if (!haveDir) {
+ FcConfigAppFontAddDir(FcConfigGetCurrent(),
+ (const FcChar8 *)"/opt/kde3/share/apps/rosegarden/fonts");
+ haveDir = true;
+ }
+
+ FcPattern *pattern = FcPatternCreate();
+ FcPatternAddString(pattern, FC_FAMILY, (FcChar8 *)m_fontName.latin1());
+ FcPatternAddInteger(pattern, FC_PIXEL_SIZE, m_fontSize);
+
+ FcConfigSubstitute(FcConfigGetCurrent(), pattern, FcMatchPattern);
+
+ FcResult result = FcResultMatch;
+ FcPattern *match = FcFontMatch(FcConfigGetCurrent(), pattern, &result);
+ FcPatternDestroy(pattern);
+
+ if (!match || result != FcResultMatch) {
+ KMessageBox::error(this, i18n("Error: Unable to match font name %1").arg(m_fontName));
+ return ;
+ }
+
+ FcChar8 *matchFamily;
+ FcPatternGetString(match, FC_FAMILY, 0, &matchFamily);
+
+ if (QString((const char *)matchFamily).lower() != m_fontName.lower()) {
+ KMessageBox::sorry(this, i18n("Warning: No good match for font name %1 (best is %2)").
+ arg(m_fontName).arg(QString((const char *)matchFamily)));
+ m_fontName = (const char *)matchFamily;
+ }
+
+ m_tableFont = XftFontOpenPattern(x11AppDisplay(), match);
+
+ if (!m_tableFont) {
+ KMessageBox::error(this, i18n("Error: Unable to open best-match font %1").
+ arg(QString((const char *)matchFamily)));
+ }
+#endif
+}
+
+void FontViewFrame::setGlyphs(bool glyphs)
+{
+ m_glyphs = glyphs;
+ update();
+}
+
+QSize FontViewFrame::sizeHint() const
+{
+ return QSize(16 * m_fontSize * 3 / 2 + margin() + 2 * frameWidth(),
+ 16 * m_fontSize * 3 / 2 + margin() + 2 * frameWidth());
+}
+
+QSize FontViewFrame::cellSize() const
+{
+ QFontMetrics fm = fontMetrics();
+ return QSize( fm.maxWidth(), fm.lineSpacing() + 1 );
+}
+
+void FontViewFrame::paintEvent( QPaintEvent* e )
+{
+#ifdef HAVE_XFT
+ if (!m_tableFont)
+ return ;
+
+ QFrame::paintEvent(e);
+ QPainter p(this);
+
+ int ll = 25;
+ int ml = frameWidth() + margin() + ll + 1;
+ int mt = frameWidth() + margin();
+ QSize cell((width() - 16 - ml) / 17, (height() - 16 - mt) / 17);
+
+ if ( !cell.width() || !cell.height() )
+ return ;
+
+ QColor body(255, 255, 192);
+ QColor negative(255, 192, 192);
+ QColor positive(192, 192, 255);
+ QColor rnegative(255, 128, 128);
+ QColor rpositive(128, 128, 255);
+
+ Drawable drawable = (Drawable)handle();
+ XftDraw *draw = XftDrawCreate(x11AppDisplay(), drawable,
+ (Visual *)x11Visual(), x11Colormap());
+
+ QColor pen(Qt::black);
+ XftColor col;
+ col.color.red = pen.red () | pen.red() << 8;
+ col.color.green = pen.green () | pen.green() << 8;
+ col.color.blue = pen.blue () | pen.blue() << 8;
+ col.color.alpha = 0xffff;
+ col.pixel = pen.pixel();
+
+ for (int j = 0; j <= 16; j++) {
+ for (int i = 0; i <= 16; i++) {
+
+ int x = i * cell.width();
+ int y = j * cell.height();
+
+ x += ml;
+ y += mt; // plus ascent
+
+ if (i == 0) {
+ if (j == 0)
+ continue;
+ p.setFont(kapp->font());
+ QFontMetrics afm(kapp->font());
+ QString s = QString("%1").arg(m_row * 256 + (j - 1) * 16);
+ p.drawText(x - afm.width(s), y, s);
+ p.setPen(QColor(190, 190, 255));
+ p.drawLine(0, y, width(), y);
+ p.setPen(Qt::black);
+ continue;
+ } else if (j == 0) {
+ p.setFont(kapp->font());
+ QString s = QString("%1").arg(i - 1);
+ p.drawText(x, y, s);
+ p.setPen(QColor(190, 190, 255));
+ p.drawLine(x, 0, x, height());
+ p.setPen(Qt::black);
+ continue;
+ }
+
+ p.save();
+
+ if (m_glyphs) {
+ FT_UInt ui = m_row * 256 + (j - 1) * 16 + i - 1;
+ XftDrawGlyphs(draw, &col, (XftFont *)m_tableFont, x, y, &ui, 1);
+ } else {
+ FcChar32 ch = m_row * 256 + (j - 1) * 16 + i - 1;
+ if (XftCharExists(x11AppDisplay(), (XftFont *)m_tableFont, ch)) {
+ XftDrawString32(draw, &col, (XftFont *)m_tableFont, x, y, &ch, 1);
+ }
+ }
+
+ p.restore();
+ }
+ }
+#endif
+}
+
+bool
+FontViewFrame::hasRow(int r) const
+{
+#ifdef HAVE_XFT
+ if (m_glyphs) {
+
+ if (r < 256)
+ return true;
+
+ } else {
+
+ for (int c = 0; c < 256; ++c) {
+ FcChar32 ch = r * 256 + c;
+ if (XftCharExists(x11AppDisplay(), (XftFont *)m_tableFont, ch)) {
+ return true;
+ }
+ }
+ }
+#endif
+ return false;
+}
+
+void FontViewFrame::setRow(int row)
+{
+ m_row = row;
+ update();
+}
+
+}
+#include "FontViewFrame.moc"
diff --git a/src/gui/editors/notation/FontViewFrame.h b/src/gui/editors/notation/FontViewFrame.h
new file mode 100644
index 0000000..8a1a946
--- /dev/null
+++ b/src/gui/editors/notation/FontViewFrame.h
@@ -0,0 +1,77 @@
+
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
+
+/*
+ Rosegarden
+ A MIDI and audio sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_FONTVIEWFRAME_H_
+#define _RG_FONTVIEWFRAME_H_
+
+#include <qframe.h>
+#include <qsize.h>
+#include <qstring.h>
+
+
+class QWidget;
+class QPaintEvent;
+
+
+namespace Rosegarden
+{
+
+
+
+class FontViewFrame : public QFrame
+{
+ Q_OBJECT
+
+public:
+ FontViewFrame(int pixelSize, QWidget *parent = 0, const char *name = 0);
+ virtual ~FontViewFrame();
+
+ QSize sizeHint() const;
+ bool hasRow(int row) const;
+
+public slots:
+ void setFont(QString name);
+ void setRow(int);
+ void setGlyphs(bool glyphs);
+
+protected:
+ QSize cellSize() const;
+ void paintEvent( QPaintEvent* );
+ void loadFont();
+
+private:
+ QString m_fontName;
+ int m_fontSize;
+ void *m_tableFont;
+ int m_row;
+ bool m_glyphs;
+};
+
+
+
+
+}
+
+#endif
diff --git a/src/gui/editors/notation/GuitarChordInserter.cpp b/src/gui/editors/notation/GuitarChordInserter.cpp
new file mode 100644
index 0000000..2482b87
--- /dev/null
+++ b/src/gui/editors/notation/GuitarChordInserter.cpp
@@ -0,0 +1,185 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
+
+/*
+ Rosegarden
+ A MIDI and audio sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "GuitarChordInserter.h"
+
+#include <klocale.h>
+#include "base/Event.h"
+#include "base/Exception.h"
+#include "base/Staff.h"
+#include "base/ViewElement.h"
+#include "commands/notation/EraseEventCommand.h"
+#include "commands/notation/GuitarChordInsertionCommand.h"
+#include "gui/general/EditTool.h"
+#include "gui/general/LinedStaff.h"
+#include "gui/editors/guitar/GuitarChordSelectorDialog.h"
+#include "misc/Debug.h"
+#include "NotationElement.h"
+#include "NotationTool.h"
+#include "NotationView.h"
+#include "NotePixmapFactory.h"
+#include <kaction.h>
+#include <qdialog.h>
+#include <qiconset.h>
+#include <qstring.h>
+
+
+namespace Rosegarden
+{
+
+GuitarChordInserter::GuitarChordInserter(NotationView* view)
+ : NotationTool("GuitarChordInserter", view),
+ m_guitarChordSelector(0)
+{
+ QIconSet icon = QIconSet(NotePixmapFactory::toQPixmap(NotePixmapFactory::
+ makeToolbarPixmap("select")));
+
+ new KAction(i18n("Switch to Select Tool"), icon, 0, this,
+ SLOT(slotSelectSelected()), actionCollection(),
+ "select");
+
+ new KAction(i18n("Switch to Erase Tool"), "eraser", 0, this,
+ SLOT(slotEraseSelected()), actionCollection(),
+ "erase");
+
+ icon = QIconSet
+ (NotePixmapFactory::toQPixmap(NotePixmapFactory::
+ makeToolbarPixmap("crotchet")));
+
+ new KAction(i18n("Switch to Inserting Notes"), icon, 0, this,
+ SLOT(slotNoteSelected()), actionCollection(),
+ "notes");
+
+ m_guitarChordSelector = new GuitarChordSelectorDialog(m_nParentView);
+ m_guitarChordSelector->init();
+ createMenu("guitarchordinserter.rc");
+}
+
+void GuitarChordInserter::slotGuitarChordSelected()
+{
+ // Switch to last selected Guitar Chord
+ // m_nParentView->slotLastGuitarChordAction();
+}
+
+void GuitarChordInserter::slotEraseSelected()
+{
+ m_parentView->actionCollection()->action("erase")->activate();
+}
+
+void GuitarChordInserter::slotSelectSelected()
+{
+ m_parentView->actionCollection()->action("select")->activate();
+}
+
+void GuitarChordInserter::handleLeftButtonPress(timeT,
+ int,
+ int staffNo,
+ QMouseEvent* e,
+ ViewElement *element)
+{
+ NOTATION_DEBUG << "GuitarChordInserter::handleLeftButtonPress" << endl;
+
+ if (staffNo < 0) {
+ return ;
+ }
+
+ Staff *staff = m_nParentView->getStaff(staffNo);
+
+ if (element && element->event()->isa(Guitar::Chord::EventType)) {
+ handleSelectedGuitarChord (element, staff);
+ } else {
+ createNewGuitarChord (element, staff, e);
+ }
+}
+
+bool GuitarChordInserter::processDialog( Staff* staff,
+ timeT& insertionTime)
+{
+ bool result = false;
+
+ if (m_guitarChordSelector->exec() == QDialog::Accepted) {
+ Guitar::Chord chord = m_guitarChordSelector->getChord();
+
+ GuitarChordInsertionCommand *command =
+ new GuitarChordInsertionCommand
+ (staff->getSegment(), insertionTime, chord);
+
+ m_nParentView->addCommandToHistory(command);
+ result = true;
+ }
+
+ return result;
+}
+
+void GuitarChordInserter::handleSelectedGuitarChord (ViewElement* element, Staff *staff)
+{
+ NOTATION_DEBUG << "GuitarChordInserter::handleSelectedGuitarChord" << endl;
+
+
+ // Get time of where guitar chord is inserted
+ timeT insertionTime = element->event()->getAbsoluteTime(); // not getViewAbsoluteTime()
+
+ // edit an existing guitar chord, if that's what we clicked on
+ try {
+ Guitar::Chord chord(*(element->event()));
+
+ m_guitarChordSelector->setChord(chord);
+
+ if ( processDialog( staff, insertionTime ) ) {
+ // Erase old guitar chord
+ EraseEventCommand *command =
+ new EraseEventCommand(staff->getSegment(),
+ element->event(),
+ false);
+
+ m_nParentView->addCommandToHistory(command);
+ }
+ } catch (Exception e) {}
+}
+
+void GuitarChordInserter::createNewGuitarChord (ViewElement* element, Staff *staff, QMouseEvent* e)
+{
+ NOTATION_DEBUG << "GuitarChordInserter::createNewGuitarChord" << endl;
+ Event *clef = 0, *key = 0;
+
+ LinedStaff *s = dynamic_cast<LinedStaff *>(staff);
+
+ NotationElementList::iterator closestElement =
+ s->getClosestElementToCanvasCoords(e->x(), (int)e->y(),
+ clef, key, false, -1);
+
+ if (closestElement == staff->getViewElementList()->end()) {
+ return ;
+ }
+
+ timeT insertionTime = (*closestElement)->event()->getAbsoluteTime(); // not getViewAbsoluteTime()
+
+ processDialog( staff, insertionTime );
+}
+
+const QString GuitarChordInserter::ToolName = "guitarchordinserter";
+
+}
+#include "GuitarChordInserter.moc"
diff --git a/src/gui/editors/notation/GuitarChordInserter.h b/src/gui/editors/notation/GuitarChordInserter.h
new file mode 100644
index 0000000..3bd5660
--- /dev/null
+++ b/src/gui/editors/notation/GuitarChordInserter.h
@@ -0,0 +1,96 @@
+
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
+
+/*
+ Rosegarden
+ A MIDI and audio sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_GUITAR_CHORD_INSERTER_H_
+#define _RG_GUITAR_CHORD_INSERTER_H_
+
+#include "NotationTool.h"
+#include <qstring.h>
+#include "base/Event.h"
+
+
+class QMouseEvent;
+
+
+namespace Rosegarden
+{
+
+class ViewElement;
+class Staff;
+class NotationView;
+class GuitarChordSelectorDialog;
+
+/**
+ * This tool will insert guitar chord on mouse click events
+*/
+class GuitarChordInserter : public NotationTool
+{
+ Q_OBJECT
+
+ friend class NotationToolBox;
+
+public:
+
+ virtual void handleLeftButtonPress(timeT t,
+ int height,
+ int staffNo,
+ QMouseEvent* e,
+ ViewElement *element);
+
+/*
+ virtual void handleMouseDoubleClick(timeT,
+ int height, int staffNo,
+ QMouseEvent*,
+ ViewElement* el);
+*/
+
+ static const QString ToolName;
+
+protected slots:
+ void slotGuitarChordSelected();
+ void slotEraseSelected();
+ void slotSelectSelected();
+
+protected:
+ GuitarChordSelectorDialog* m_guitarChordSelector;
+
+ GuitarChordInserter(NotationView*);
+
+private:
+ void handleSelectedGuitarChord (ViewElement* element,
+ Staff *staff);
+
+ void createNewGuitarChord (ViewElement* element,
+ Staff *staff,
+ QMouseEvent* e);
+
+ bool processDialog (Staff *staff,
+ timeT& insertionTime);
+};
+
+
+}
+
+#endif
diff --git a/src/gui/editors/notation/HeadersGroup.cpp b/src/gui/editors/notation/HeadersGroup.cpp
new file mode 100644
index 0000000..c0a2de0
--- /dev/null
+++ b/src/gui/editors/notation/HeadersGroup.cpp
@@ -0,0 +1,160 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
+
+/*
+ Rosegarden
+ A MIDI and audio sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ This file is Copyright 2007-2008
+ Yves Guillemot <yc.guillemot@wanadoo.fr>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include <limits>
+#include <qsize.h>
+#include <qwidget.h>
+#include <qvbox.h>
+#include <qlabel.h>
+
+#include "HeadersGroup.h"
+#include "TrackHeader.h"
+#include "NotationView.h"
+#include "NotePixmapFactory.h"
+
+
+namespace Rosegarden
+{
+
+
+HeadersGroup::
+HeadersGroup(QWidget *parent, NotationView * nv, Composition * comp) :
+ QVBox(parent),
+ m_notationView(nv),
+ m_composition(comp),
+ m_usedHeight(0),
+ m_filler(0),
+ m_lastX(INT_MIN),
+ m_lastWidth(-1)
+{
+}
+
+void
+HeadersGroup::removeAllHeaders()
+{
+ TrackHeaderVector::iterator i;
+ for (i=m_headers.begin(); i!=m_headers.end(); i++) {
+ delete *i;
+ }
+ m_headers.erase(m_headers.begin(), m_headers.end());
+
+ if (m_filler) {
+ delete m_filler;
+ m_filler = 0;
+ }
+ m_usedHeight = 0;
+ m_lastWidth = -1;
+}
+
+void
+HeadersGroup::addHeader(int trackId, int height, int ypos, double xcur)
+{
+ TrackHeader * sh = new TrackHeader(this, trackId, height, ypos);
+ m_headers.push_back(sh);
+ m_usedHeight += height;
+}
+
+void
+HeadersGroup::completeToHeight(int height)
+{
+ if (height > m_usedHeight) {
+ if (!m_filler) m_filler = new QLabel(this);
+ m_filler->setFixedHeight(height - m_usedHeight);
+ }
+}
+
+void
+HeadersGroup::slotUpdateAllHeaders(int x, int y, bool force)
+{
+ // Minimum header width
+ int headerMinWidth = m_notationView->getHeadersTopFrameMinWidth();
+
+ // Maximum header width (may be overriden by clef and key width)
+ int headerMaxWidth = (m_notationView->getCanvasVisibleWidth() * 10) / 100;
+
+ if ((x != m_lastX) || force) {
+ m_lastX = x;
+ TrackHeaderVector::iterator i;
+ int neededWidth = 0;
+
+ // Pass 1 : get the max width needed
+ for (i=m_headers.begin(); i!=m_headers.end(); i++) {
+ int w = (*i)->lookAtStaff(x, headerMaxWidth);
+ if (w > neededWidth) neededWidth = w;
+ }
+
+ if (neededWidth < headerMinWidth) neededWidth = headerMinWidth;
+
+ // Only when m_lastWidth is valid (the first time, m_lastWidth = -1)
+ if (m_lastWidth > 0) {
+ // Don't redraw the headers when change of width is very small
+ const int treshold = 10; // Treshold value should be refined ...
+ int deltaWidth = m_lastWidth - neededWidth;
+ if ((deltaWidth < treshold) && (deltaWidth > -treshold))
+ neededWidth = m_lastWidth;
+ }
+
+ // Pass 2 : redraw the headers when necessary
+ for (i=m_headers.begin(); i!=m_headers.end(); i++) {
+ (*i)->updateHeader(neededWidth);
+ }
+
+ if (neededWidth != m_lastWidth) {
+ setFixedWidth(neededWidth);
+ m_lastWidth = neededWidth;
+
+ // Suppress vertical white stripes on canvas when headers
+ // width changes while scrolling
+ /// TODO : Limit "setChanged()" to the useful part of canvas
+ m_notationView->canvas()->setAllChanged();
+ m_notationView->canvas()->update();
+ }
+ }
+}
+
+
+
+
+void
+HeadersGroup::setCurrent(TrackId trackId)
+{
+ TrackHeaderVector::iterator i;
+ for (i=m_headers.begin(); i!=m_headers.end(); i++)
+ (*i)->setCurrent((*i)->getId() == trackId);
+}
+
+void
+HeadersGroup::resizeEvent(QResizeEvent * ev)
+{
+ // Needed to avoid gray zone at the right of headers
+ // when width is decreasing
+ emit headersResized(ev->size().width());
+}
+
+}
+#include "HeadersGroup.moc"
diff --git a/src/gui/editors/notation/HeadersGroup.h b/src/gui/editors/notation/HeadersGroup.h
new file mode 100644
index 0000000..22d25da
--- /dev/null
+++ b/src/gui/editors/notation/HeadersGroup.h
@@ -0,0 +1,144 @@
+
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
+
+/*
+ Rosegarden
+ A MIDI and audio sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ This file is Copyright 2007-2008
+ Yves Guillemot <yc.guillemot@wanadoo.fr>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#ifndef _RG_HEADERSGROUP_H_
+#define _RG_HEADERSGROUP_H_
+
+#include "base/Track.h"
+
+#include <vector>
+#include <qsize.h>
+#include <qwidget.h>
+#include <qvbox.h>
+
+
+class QLabel;
+class QResizeEvent;
+
+
+namespace Rosegarden
+{
+
+
+class NotationView;
+class Composition;
+class TrackHeader;
+
+
+class HeadersGroup : public QVBox
+{
+ Q_OBJECT
+public:
+ /**
+ * Create an empty headers group
+ */
+ HeadersGroup(QWidget *parent, NotationView * nv, Composition * comp);
+
+ void removeAllHeaders();
+
+ void addHeader(int trackId, int height, int ypos, double xcur);
+
+ /**
+ * Resize a filler at bottom of group to set the headersGroup height
+ * to the value specified in parameter.
+ * (Used to give to the headers group exactly the same height as the
+ * canvas. Necessary to get synchronous vertical scroll.)
+ */
+ void completeToHeight(int height);
+
+ NotationView * getNotationView()
+ { return m_notationView;
+ }
+
+ Composition * getComposition()
+ { return m_composition;
+ }
+
+ /**
+ * Return the total height of all the headers (without the filler).
+ */
+ int getUsedHeight()
+ { return m_usedHeight;
+ }
+
+ /**
+ * Highlight as "current" the header of the specified track.
+ */
+ void setCurrent(TrackId trackId);
+
+ /**
+ * Highlight as "current" the header of the specified track.
+ */
+ int getWidth()
+ {
+ return m_lastWidth;
+ }
+
+ typedef enum { ShowNever, ShowWhenNeeded, ShowAlways } ShowHeadersModeType;
+
+ // Used to ensure to have one default value and only one.
+ static const ShowHeadersModeType DefaultShowMode = ShowAlways;
+
+ // Useful in configuration dialog.
+ static bool isValidShowMode(int mode)
+ {
+ return ((mode >= ShowNever) && (mode <= ShowAlways));
+ }
+
+public slots :
+ /**
+ * Called when notation canvas moves.
+ * Setting force to true forces the headers to be redrawn even
+ * if x has not changed since the last call.
+ */
+ void slotUpdateAllHeaders(int x, int y, bool force = false);
+
+signals :
+ void headersResized(int newWidth);
+
+private:
+ void resizeEvent(QResizeEvent * ev);
+
+ NotationView * m_notationView;
+ Composition * m_composition;
+
+ typedef std::vector<TrackHeader *> TrackHeaderVector;
+ TrackHeaderVector m_headers;
+
+ int m_usedHeight;
+ QLabel * m_filler;
+ int m_lastX;
+ int m_lastWidth;
+
+};
+
+
+}
+
+#endif
diff --git a/src/gui/editors/notation/NotationCanvasView.cpp b/src/gui/editors/notation/NotationCanvasView.cpp
new file mode 100644
index 0000000..55e63ac
--- /dev/null
+++ b/src/gui/editors/notation/NotationCanvasView.cpp
@@ -0,0 +1,485 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
+
+/*
+ Rosegarden
+ A MIDI and audio sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "NotationCanvasView.h"
+#include "misc/Debug.h"
+
+#include "misc/Strings.h"
+#include "gui/general/LinedStaffManager.h"
+#include "gui/general/RosegardenCanvasView.h"
+#include "gui/kdeext/QCanvasGroupableItem.h"
+#include "gui/kdeext/QCanvasSimpleSprite.h"
+#include "NotationElement.h"
+#include "NotationProperties.h"
+#include "NotationStaff.h"
+#include <qcanvas.h>
+#include <qcolor.h>
+#include <qpainter.h>
+#include <qpen.h>
+#include <qpoint.h>
+#include <qrect.h>
+#include <qsize.h>
+#include <qstring.h>
+#include <qwidget.h>
+
+
+namespace Rosegarden
+{
+
+NotationCanvasView::NotationCanvasView(const LinedStaffManager &staffmgr,
+ QCanvas *viewing, QWidget *parent,
+ const char *name, WFlags f) :
+ RosegardenCanvasView(viewing, parent, name, f),
+ m_linedStaffManager(staffmgr),
+ m_lastYPosNearStaff(0),
+ m_currentStaff(0),
+ m_currentHeight( -1000),
+ m_legerLineOffset(false),
+ m_heightTracking(false)
+{
+ // -- switching mandolin-sonatina first staff to page mode:
+ // default params (I think 16,100): render 1000ms position 1070ms
+ // 64,100: 1000ms 980ms
+ // 8, 100: 1140ms 1140ms
+ // 128, 100: 1060ms 980ms
+ // 256, 100: 1060ms 980ms / 930ms 920ms
+
+ // canvas()->retune(256, 100);
+
+ viewport()->setMouseTracking(true);
+
+ m_heightMarker = new QCanvasItemGroup(viewing);
+
+ m_vert1 = new QCanvasLineGroupable(viewing, m_heightMarker);
+ m_vert1->setPoints(0, 0, 0, 8);
+ m_vert1->setPen(QPen(QColor(64, 64, 64), 1));
+
+ m_vert2 = new QCanvasLineGroupable(viewing, m_heightMarker);
+ m_vert2->setPoints(17, 0, 17, 8);
+ m_vert2->setPen(QPen(QColor(64, 64, 64), 1));
+
+ m_heightMarker->hide();
+}
+
+NotationCanvasView::~NotationCanvasView()
+{
+ // All canvas items are deleted in ~NotationView()
+}
+
+void
+NotationCanvasView::setHeightTracking(bool t)
+{
+ m_heightTracking = t;
+ if (!t) {
+ m_heightMarker->hide();
+ canvas()->update();
+ }
+}
+
+void
+NotationCanvasView::contentsMouseReleaseEvent(QMouseEvent *e)
+{
+ emit mouseReleased(e);
+}
+
+void
+NotationCanvasView::contentsMouseMoveEvent(QMouseEvent *e)
+{
+ NotationStaff *prevStaff = m_currentStaff;
+ int prevHeight = m_currentHeight;
+
+ m_currentStaff = dynamic_cast<NotationStaff *>
+ (m_linedStaffManager.getStaffForCanvasCoords(e->x(), e->y()));
+
+ if (!m_currentStaff) {
+
+ emit hoveredOverNoteChanged(QString::null);
+ if (prevStaff) {
+ m_heightMarker->hide();
+ canvas()->update();
+ }
+
+ } else {
+
+ m_currentHeight = m_currentStaff->getHeightAtCanvasCoords(e->x(), e->y());
+
+ int x = e->x() - 8; // magic based on mouse cursor size
+ bool needUpdate = (m_heightTracking && (m_heightMarker->x() != x));
+ m_heightMarker->setX(x);
+
+ if (prevStaff != m_currentStaff ||
+ prevHeight != m_currentHeight) {
+
+ if (m_heightTracking) {
+ setHeightMarkerHeight(e);
+ m_heightMarker->show();
+ needUpdate = true;
+ }
+
+ emit hoveredOverNoteChanged
+ (strtoqstr
+ (m_currentStaff->getNoteNameAtCanvasCoords(e->x(), e->y())));
+ }
+
+ if (needUpdate)
+ canvas()->update();
+ }
+
+ NotationElement *elt = getElementAtXCoord(e);
+ if (elt) {
+ emit hoveredOverAbsoluteTimeChanged(elt->getViewAbsoluteTime());
+ }
+
+ // if(tracking) ??
+ emit mouseMoved(e);
+}
+
+void NotationCanvasView::contentsMousePressEvent(QMouseEvent *e)
+{
+ NOTATION_DEBUG << "NotationCanvasView::contentsMousePressEvent() - btn : "
+ << e->button() << " - state : " << e->state()
+ << endl;
+
+ QCanvasItemList itemList = canvas()->collisions(e->pos());
+
+ // We don't want to use m_currentStaff/Height, because we want
+ // to make sure the event happens at the point we clicked at
+ // rather than the last point for which contentsMouseMoveEvent
+ // happened to be called
+
+ NotationStaff *staff = dynamic_cast<NotationStaff *>
+ (m_linedStaffManager.getStaffForCanvasCoords(e->x(), e->y()));
+
+ QCanvasItemList::Iterator it;
+ NotationElement *clickedNote = 0;
+ NotationElement *clickedVagueNote = 0;
+ NotationElement *clickedNonNote = 0;
+
+ bool haveClickHeight = false;
+ int clickHeight = 0;
+ if (staff) {
+ clickHeight = staff->getHeightAtCanvasCoords(e->x(), e->y());
+ haveClickHeight = true;
+ }
+
+ for (it = itemList.begin(); it != itemList.end(); ++it) {
+
+ if ((*it)->active()) {
+ emit activeItemPressed(e, *it);
+ return ;
+ }
+
+ QCanvasNotationSprite *sprite =
+ dynamic_cast<QCanvasNotationSprite*>(*it);
+ if (!sprite) {
+ if (dynamic_cast<QCanvasNonElementSprite *>(*it)) {
+ emit nonNotationItemPressed(e, *it);
+ return ;
+ } else if (dynamic_cast<QCanvasText *>(*it)) {
+ emit textItemPressed(e, *it);
+ return ;
+ }
+ continue;
+ }
+
+ NotationElement &el = sprite->getNotationElement();
+
+ // #957364 (Notation: Hard to select upper note in chords of
+ // seconds) -- adjust x-coord for shifted note head
+
+ double cx = el.getCanvasX();
+ int nbw = 10;
+
+ if (staff) {
+ nbw = staff->getNotePixmapFactory(false).getNoteBodyWidth();
+ bool shifted = false;
+
+ if (el.event()->get
+ <Bool>
+ (staff->getProperties().NOTE_HEAD_SHIFTED, shifted) && shifted) {
+ cx += nbw;
+ }
+ }
+
+ if (el.isNote() && haveClickHeight) {
+ long eventHeight = 0;
+ if (el.event()->get
+ <Int>
+ (NotationProperties::HEIGHT_ON_STAFF, eventHeight)) {
+
+ if (eventHeight == clickHeight) {
+
+ if (!clickedNote &&
+ e->x() >= cx &&
+ e->x() <= cx + nbw) {
+ clickedNote = &el;
+ } else if (!clickedVagueNote &&
+ e->x() >= cx - 2 &&
+ e->x() <= cx + nbw + 2) {
+ clickedVagueNote = &el;
+ }
+
+ } else if (eventHeight - 1 == clickHeight ||
+ eventHeight + 1 == clickHeight) {
+ if (!clickedVagueNote)
+ clickedVagueNote = &el;
+ }
+ }
+ } else if (!el.isNote()) {
+ if (!clickedNonNote)
+ clickedNonNote = &el;
+ }
+ }
+
+ int staffNo = -1;
+ if (staff)
+ staffNo = staff->getId();
+
+ if (clickedNote)
+ handleMousePress(clickHeight, staffNo, e, clickedNote);
+ else if (clickedNonNote)
+ handleMousePress(clickHeight, staffNo, e, clickedNonNote);
+ else if (clickedVagueNote)
+ handleMousePress(clickHeight, staffNo, e, clickedVagueNote);
+ else
+ handleMousePress(clickHeight, staffNo, e);
+}
+
+void NotationCanvasView::contentsMouseDoubleClickEvent(QMouseEvent* e)
+{
+ NOTATION_DEBUG << "NotationCanvasView::contentsMouseDoubleClickEvent()\n";
+
+ contentsMousePressEvent(e);
+}
+
+void
+NotationCanvasView::processActiveItems(QMouseEvent* e,
+ QCanvasItemList itemList)
+{
+ QCanvasItem* pressedItem = 0;
+ QCanvasItemList::Iterator it;
+
+ for (it = itemList.begin(); it != itemList.end(); ++it) {
+
+ QCanvasItem *item = *it;
+ if (item->active() && !pressedItem) {
+ NOTATION_DEBUG << "mousepress : got active item\n";
+ pressedItem = item;
+ }
+ }
+
+ if (pressedItem)
+ emit activeItemPressed(e, pressedItem);
+
+}
+
+void
+NotationCanvasView::handleMousePress(int height,
+ int staffNo,
+ QMouseEvent *e,
+ NotationElement *el)
+{
+ NOTATION_DEBUG << "NotationCanvasView::handleMousePress() at height "
+ << height << endl;
+
+ emit itemPressed(height, staffNo, e, el);
+}
+
+bool
+NotationCanvasView::posIsTooFarFromStaff(const QPoint &pos)
+{
+ // return true if pos.y is more than m_staffLineThreshold away from
+ // the last pos for which a collision was detected
+ //
+ return (pos.y() > m_lastYPosNearStaff) ?
+ (pos.y() - m_lastYPosNearStaff) > (int)m_staffLineThreshold :
+ (m_lastYPosNearStaff - pos.y()) > (int)m_staffLineThreshold;
+
+}
+
+int
+NotationCanvasView::getLegerLineCount(int height, bool &offset)
+{
+ //!!! This is far too specifically notation-related to be here, really
+
+ if (height < 0) {
+
+ offset = (( -height % 2) == 1);
+ return height / 2;
+
+ } else if (height > 8) {
+
+ offset = ((height % 2) == 1);
+ return (height - 8) / 2;
+ }
+
+ return 0;
+}
+
+void
+NotationCanvasView::setHeightMarkerHeight(QMouseEvent *e)
+{
+ NotationStaff *staff = dynamic_cast<NotationStaff *>
+ (m_linedStaffManager.getStaffForCanvasCoords(e->x(), e->y()));
+
+ int height = staff->getHeightAtCanvasCoords(e->x(), e->y());
+ int lineY = staff->getCanvasYForHeight(height, e->x(), e->y());
+
+ // NOTATION_DEBUG << "NotationCanvasView::setHeightMarkerHeight: "
+ // << e->y() << " snapped to line -> " << lineY
+ // << " (height " << height << ")" << endl;
+
+ int spacing = staff->getLineSpacing() - 1;
+
+ m_staffLineThreshold = spacing;
+ m_vert1->setPoints(0, -spacing / 2, 0, spacing / 2);
+ m_vert2->setPoints(17, -spacing / 2, 17, spacing / 2); // magic based on mouse cursor size
+ m_heightMarker->setY(lineY);
+
+ bool legerLineOffset = false;
+ int legerLineCount = getLegerLineCount(height, legerLineOffset);
+
+ if (legerLineCount != (int)m_legerLines.size() ||
+ legerLineOffset != m_legerLineOffset) {
+
+ bool above = false;
+ if (legerLineCount < 0) {
+ above = true;
+ legerLineCount = -legerLineCount;
+ }
+
+ int i;
+ for (i = 0; i < (int)m_legerLines.size(); ++i) {
+ delete m_legerLines[i];
+ }
+ m_legerLines.clear();
+
+ for (i = 0; i < legerLineCount; ++i) {
+
+ QCanvasLineGroupable *line =
+ new QCanvasLineGroupable(canvas(), m_heightMarker);
+
+ line->setPen(QPen(QColor(64, 64, 64), 1));
+
+ int y = (int)m_heightMarker->y() +
+ (above ? -1 : 1) * (i * (spacing + 1));
+ int x = (int)m_heightMarker->x() + 1;
+
+ if (legerLineOffset) {
+ if (above)
+ y -= spacing / 2 + 1;
+ else
+ y += spacing / 2;
+ }
+
+ line->setPoints(x, y, x + 15, y); // magic based on mouse cursor size
+ m_legerLines.push_back(line);
+ }
+
+ m_legerLineOffset = legerLineOffset;
+ }
+}
+
+NotationElement *
+NotationCanvasView::getElementAtXCoord(QMouseEvent *e) // any old element
+{
+ QRect threshold(e->pos(), QSize(4, 100)); //!!!
+ threshold.moveCenter(e->pos());
+
+ QCanvasItemList itemList = canvas()->collisions(threshold);
+
+ QCanvasItemList::Iterator it;
+ QCanvasNotationSprite* sprite = 0;
+
+ for (it = itemList.begin(); it != itemList.end(); ++it)
+ {
+
+ QCanvasItem *item = *it;
+
+ if ((sprite = dynamic_cast<QCanvasNotationSprite*>(item))) {
+ return & (sprite->getNotationElement());
+ }
+ }
+
+ return 0;
+}
+
+void
+NotationCanvasView::viewportPaintEvent(QPaintEvent *e)
+{
+ int cx(e->rect().x()),
+ cy(e->rect().y()),
+ cw(e->rect().width()) /*,
+ ch(e->rect().height())*/;
+ // NOTATION_DEBUG << "NotationCanvasView::viewportPaintEvent: (" << cx << ","
+ // << cy << ") size (" << cw << "x" << ch << ")" << endl;
+ QCanvasView::viewportPaintEvent(e);
+
+ cx += contentsX();
+ cy += contentsY();
+ m_lastRender = e->rect();
+ emit renderRequired(std::min(contentsX(), cx),
+ std::max(contentsX() + visibleWidth(), cx + cw));
+}
+
+void
+NotationCanvasView::drawContents(QPainter *p, int cx, int cy, int cw, int ch)
+{
+ /*
+ m_lastRender = QRect(cx, cy, cw, ch);
+ NOTATION_DEBUG << "NotationCanvasView::drawContents: (" << cx << ","
+ << cy << ") size (" << cw << "x" << ch << ")" << endl;
+ */
+ QCanvasView::drawContents(p, cx, cy, cw, ch);
+ /*
+ emit renderRequired(std::min(contentsX(), cx),
+ std::max(contentsX() + visibleWidth(), cx + cw));
+ */
+}
+
+void
+NotationCanvasView::slotRenderComplete()
+{
+ /* QPainter painter(viewport());
+ int cx(m_lastRender.x()),
+ cy(m_lastRender.y()),
+ cw(m_lastRender.width()),
+ ch(m_lastRender.height());
+ NOTATION_DEBUG << "NotationCanvasView::slotRenderComplete: (" << cx << ","
+ << cy << ") size (" << cw << "x" << ch << ")" << endl;
+ QCanvasView::drawContents(&painter, cx, cy, cw, ch);
+ */
+ QPaintEvent ev(m_lastRender);
+ QCanvasView::viewportPaintEvent(&ev);
+}
+
+void
+NotationCanvasView::slotExternalWheelEvent(QWheelEvent* e)
+{
+ wheelEvent(e);
+}
+
+}
+#include "NotationCanvasView.moc"
diff --git a/src/gui/editors/notation/NotationCanvasView.h b/src/gui/editors/notation/NotationCanvasView.h
new file mode 100644
index 0000000..5c88fb0
--- /dev/null
+++ b/src/gui/editors/notation/NotationCanvasView.h
@@ -0,0 +1,218 @@
+
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
+
+/*
+ Rosegarden
+ A MIDI and audio sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_NOTATIONCANVASVIEW_H_
+#define _RG_NOTATIONCANVASVIEW_H_
+
+#include "gui/general/RosegardenCanvasView.h"
+#include <qrect.h>
+#include <vector>
+
+
+class QWidget;
+class QString;
+class QPoint;
+class QPaintEvent;
+class QPainter;
+class QMouseEvent;
+class QCanvasLineGroupable;
+class QCanvasItemGroup;
+class QCanvasItem;
+class QCanvas;
+
+
+namespace Rosegarden
+{
+
+class NotationStaff;
+class NotationElement;
+class LinedStaffManager;
+
+
+/**
+ * Central widget for the NotationView window
+ *
+ * This class only takes care of the event handling
+ * (see the various signals).
+ *
+ * It does not deal with any canvas element. All elements are added by
+ * the NotationView.
+ *
+ *@see NotationView
+ */
+
+class NotationCanvasView : public RosegardenCanvasView
+{
+ Q_OBJECT
+
+public:
+ NotationCanvasView(const LinedStaffManager &staffmgr,
+ QCanvas *viewing, QWidget *parent=0,
+ const char *name=0, WFlags f=0);
+
+ ~NotationCanvasView();
+
+ void setHeightTracking(bool t);
+
+signals:
+
+ /**
+ * Emitted when the user clicks on a staff (e.g. mouse button press)
+ * \a pitch is set to the MIDI pitch on which the click occurred
+ * \a staffNo is set to the staff on which the click occurred
+ * \a point is set to the coordinates of the click event
+ * \a el points to the NotationElement which was clicked on, if any
+ */
+ void itemPressed(int pitch, int staffNo,
+ QMouseEvent*,
+ NotationElement* el);
+
+ /**
+ * Emitted when the user clicks on a QCanvasItem which is active
+ *
+ * @see QCanvasItem#setActive
+ */
+ void activeItemPressed(QMouseEvent*,
+ QCanvasItem* item);
+
+ /**
+ * Emitted when the user clicks on a QCanvasItem which is neither
+ * active nor a notation element
+ */
+ void nonNotationItemPressed(QMouseEvent *,
+ QCanvasItem *);
+
+ /**
+ * Emitted when the user clicks on a QCanvasItem which is a
+ * plain QCanvasText
+ */
+ void textItemPressed(QMouseEvent *,
+ QCanvasItem *);
+
+ /**
+ * Emitted when the mouse cursor moves to a different height
+ * on the staff
+ *
+ * \a noteName contains the MIDI name of the corresponding note
+ */
+ void hoveredOverNoteChanged(const QString &noteName);
+
+ /**
+ * Emitted when the mouse cursor moves to a note which is at a
+ * different time
+ *
+ * \a time is set to the absolute time of the note the cursor is
+ * hovering on
+ */
+ void hoveredOverAbsoluteTimeChanged(unsigned int time);
+
+ /**
+ * Emitted when the mouse cursor moves (used by the selection tool)
+ */
+ void mouseMoved(QMouseEvent*);
+
+ /**
+ * Emitted when the mouse button is released
+ */
+ void mouseReleased(QMouseEvent*);
+
+ /**
+ * Emitted when a region is about to be drawn by the canvas view.
+ * Indicates that any on-demand rendering for that region should
+ * be carried out.
+ */
+ void renderRequired(double cx0, double cx1);
+
+public slots:
+ void slotRenderComplete();
+
+ void slotExternalWheelEvent(QWheelEvent* e);
+
+protected:
+
+ virtual void viewportPaintEvent(QPaintEvent *e);
+ virtual void drawContents(QPainter *p, int cx, int cy, int cw, int ch);
+
+ const LinedStaffManager &m_linedStaffManager;
+
+ /**
+ * Callback for a mouse button press event in the canvas
+ */
+ virtual void contentsMousePressEvent(QMouseEvent*);
+
+ /**
+ * Callback for a mouse button release event in the canvas
+ */
+ virtual void contentsMouseReleaseEvent(QMouseEvent*);
+
+ /**
+ * Callback for a mouse move event in the canvas
+ */
+ virtual void contentsMouseMoveEvent(QMouseEvent*);
+
+ /**
+ * Callback for a mouse double click event in the canvas
+ */
+ virtual void contentsMouseDoubleClickEvent(QMouseEvent*);
+
+ void processActiveItems(QMouseEvent*, QCanvasItemList);
+
+ void handleMousePress(int height, int staffNo,
+ QMouseEvent*,
+ NotationElement* pressedNotationElement = 0);
+
+ bool posIsTooFarFromStaff(const QPoint &pos);
+
+ int getLegerLineCount(int height, bool &offset);
+
+ void setHeightMarkerHeight(QMouseEvent *e);
+
+ NotationElement *getElementAtXCoord(QMouseEvent *e);
+
+ //--------------- Data members ---------------------------------
+
+ int m_lastYPosNearStaff;
+
+ unsigned int m_staffLineThreshold;
+
+ NotationStaff *m_currentStaff;
+ int m_currentHeight;
+
+ QCanvasItemGroup *m_heightMarker;
+ QCanvasLineGroupable *m_vert1;
+ QCanvasLineGroupable *m_vert2;
+ std::vector<QCanvasLineGroupable *> m_legerLines;
+ bool m_legerLineOffset;
+
+ bool m_heightTracking;
+
+ QRect m_lastRender;
+};
+
+
+
+}
+
+#endif
diff --git a/src/gui/editors/notation/NotationChord.cpp b/src/gui/editors/notation/NotationChord.cpp
new file mode 100644
index 0000000..7b0a263
--- /dev/null
+++ b/src/gui/editors/notation/NotationChord.cpp
@@ -0,0 +1,335 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
+
+/*
+ Rosegarden
+ A MIDI and audio sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "NotationChord.h"
+
+#include "base/Sets.h"
+#include "base/Event.h"
+#include "base/NotationRules.h"
+#include "base/NotationTypes.h"
+#include "base/Quantizer.h"
+#include "NotationProperties.h"
+#include "NoteStyleFactory.h"
+
+namespace Rosegarden
+{
+
+template <>
+Event *
+AbstractSet<NotationElement, NotationElementList>::getAsEvent(const NotationElementList::iterator &i)
+{
+ return (*i)->event();
+}
+
+NotationChord::NotationChord(NotationElementList &c,
+ NotationElementList::iterator i,
+ const Quantizer *quantizer,
+ const NotationProperties &properties,
+ const Clef &clef,
+ const ::Rosegarden::Key &key) :
+ GenericChord < NotationElement,
+ NotationElementList, true > (c, i, quantizer,
+ NotationProperties::STEM_UP),
+ m_properties(properties),
+ m_clef(clef),
+ m_key(key)
+{
+ // nothing else
+}
+
+int
+NotationChord::getHeight(const Iterator &i) const
+{
+ //!!! We use HEIGHT_ON_STAFF in preference to the passed clef/key,
+ //but what if the clef/key changed since HEIGHT_ON_STAFF was
+ //written? Who updates the properties then? Check this.
+
+ long h = 0;
+ if (getAsEvent(i)->get
+ <Int>(NotationProperties::HEIGHT_ON_STAFF, h)) {
+ return h;
+ }
+
+ try {
+ Pitch pitch(*getAsEvent(i));
+ h = pitch.getHeightOnStaff(m_clef, m_key);
+ } catch (...) {
+ // no pitch!
+ }
+
+ // set non-persistent, not setMaybe, as we know the property is absent:
+ getAsEvent(i)->set
+ <Int>(NotationProperties::HEIGHT_ON_STAFF, h, false);
+ return h;
+}
+
+bool
+NotationChord::hasStem() const
+{
+ // true if any of the notes is stemmed
+
+ Iterator i(getInitialNote());
+ for (;;) {
+ long note;
+ if (!getAsEvent(i)->get
+ <Int>(BaseProperties::NOTE_TYPE, note)) return true;
+ if (NoteStyleFactory::getStyleForEvent(getAsEvent(i))->hasStem(note))
+ return true;
+ if (i == getFinalNote())
+ return false;
+ ++i;
+ }
+ return false;
+}
+
+bool
+NotationChord::hasStemUp() const
+{
+ NotationRules rules;
+
+ // believe anything found in any of the notes, if in a persistent
+ // property or a property apparently set by the beaming algorithm
+
+ Iterator i(getInitialNote());
+
+ for (;;) {
+ Event *e = getAsEvent(i);
+ /*!!!
+ if (e->has(m_properties.VIEW_LOCAL_STEM_UP)) {
+ return e->get<Bool>(m_properties.VIEW_LOCAL_STEM_UP);
+ }
+ */
+ if (e->has(NotationProperties::STEM_UP)) {
+ return e->get
+ <Bool>(NotationProperties::STEM_UP);
+ }
+
+ if (e->has(NotationProperties::BEAM_ABOVE)) {
+ if (e->has(NotationProperties::BEAMED) &&
+ e->get
+ <Bool>(NotationProperties::BEAMED)) {
+ return e->get
+ <Bool>(NotationProperties::BEAM_ABOVE);
+ }
+ else {
+ return !e->get
+ <Bool>(NotationProperties::BEAM_ABOVE);
+ }
+ }
+
+ if (i == getFinalNote())
+ break;
+ ++i;
+ }
+
+ return rules.isStemUp(getHighestNoteHeight(),getLowestNoteHeight());
+}
+
+bool
+NotationChord::hasNoteHeadShifted() const
+{
+ int ph = 10000;
+
+ for (unsigned int i = 0; i < size(); ++i) {
+ int h = getHeight((*this)[i]);
+ if (h == ph + 1)
+ return true;
+ ph = h;
+ }
+
+ return false;
+}
+
+bool
+NotationChord::isNoteHeadShifted(const Iterator &itr) const
+{
+ unsigned int i;
+ for (i = 0; i < size(); ++i) {
+ if ((*this)[i] == itr)
+ break;
+ }
+
+ if (i == size()) {
+ std::cerr << "NotationChord::isNoteHeadShifted: Warning: Unable to find note head " << getAsEvent(itr) << std::endl;
+ return false;
+ }
+
+ int h = getHeight((*this)[i]);
+
+ if (hasStemUp()) {
+ if ((i > 0) && (h == getHeight((*this)[i - 1]) + 1)) {
+ return (!isNoteHeadShifted((*this)[i - 1]));
+ }
+ } else {
+ if ((i < size() - 1) && (h == getHeight((*this)[i + 1]) - 1)) {
+ return (!isNoteHeadShifted((*this)[i + 1]));
+ }
+ }
+
+ return false;
+}
+
+void
+NotationChord::applyAccidentalShiftProperties()
+{
+ // Some rules:
+ //
+ // The top accidental always gets the minimum shift (i.e. normally
+ // right next to the note head or stem).
+ //
+ // The bottom accidental gets the next least: the same, if the
+ // interval is more than a sixth, or the next shift out otherwise.
+ //
+ // We then progress up from the bottom accidental upwards.
+ //
+ // These rules aren't really enough, but they might do for now!
+
+ //!!! Uh-oh... we have a catch-22 here. We can't determine the
+ // proper minimum shift until we know which way the stem goes,
+ // because if we have a shifted note head and the stem goes down,
+ // we need to shift one place further than otherwise. But we
+ // don't know for sure which way the stem goes until we've
+ // calculated the beam, and we don't do that until after we've
+ // worked out the x-coordinates based on (among other things) the
+ // accidental shift.
+
+ int minShift = 0;
+ bool extra = false;
+
+ if (!hasStemUp() && hasNoteHeadShifted()) {
+ minShift = 1; // lazy
+ extra = true;
+ }
+
+ int lastShift = minShift;
+ int lastHeight = 0, maxHeight = 999;
+ int lastWidth = 1;
+
+ for (iterator i = end(); i != begin(); ) {
+
+ --i;
+ Event *e = getAsEvent(*i);
+
+ Accidental acc;
+ if (e->get
+ <String>(m_properties.DISPLAY_ACCIDENTAL, acc) &&
+ acc != Accidentals::NoAccidental) {
+ e->setMaybe<Int>(m_properties.ACCIDENTAL_SHIFT, minShift);
+ e->setMaybe<Bool>(m_properties.ACCIDENTAL_EXTRA_SHIFT, extra);
+ maxHeight = lastHeight = getHeight(*i);
+ break;
+ }
+ }
+
+ if (maxHeight == 999) {
+ return ;
+ }
+
+ for (iterator i = begin(); i != end(); ++i) {
+
+ Event *e = getAsEvent(*i);
+ int height = getHeight(*i);
+
+ if (height == maxHeight && e->has(m_properties.ACCIDENTAL_SHIFT)) {
+ // finished -- we've come around to the highest one again
+ break;
+ }
+
+ Accidental acc;
+
+ if (e->get
+ <String>(m_properties.DISPLAY_ACCIDENTAL, acc) &&
+ acc != Accidentals::NoAccidental) {
+
+ int shift = lastShift;
+
+ if (height < lastHeight) { // lastHeight was the first, up top
+ if (lastHeight - height < 6) {
+ shift = lastShift + lastWidth;
+ }
+ } else {
+ if (height - lastHeight >= 6) {
+ if (maxHeight - height >= 6) {
+ shift = minShift;
+ } else {
+ shift = minShift + 1;
+ }
+ } else {
+ shift = lastShift + lastWidth;
+ }
+ }
+
+ e->setMaybe<Int>(m_properties.ACCIDENTAL_SHIFT, shift);
+
+ lastHeight = height;
+ lastShift = shift;
+
+ lastWidth = 1;
+ bool c = false;
+ if (e->get
+ <Bool>(m_properties.DISPLAY_ACCIDENTAL_IS_CAUTIONARY, c)
+ && c) {
+ lastWidth = 2;
+ }
+ }
+ }
+}
+
+int
+NotationChord::getMaxAccidentalShift(bool &extra) const
+{
+ int maxShift = 0;
+
+ for (const_iterator i = begin(); i != end(); ++i) {
+ Event *e = getAsEvent(*i);
+ if (e->has(m_properties.ACCIDENTAL_SHIFT)) {
+ int shift = e->get
+ <Int>(m_properties.ACCIDENTAL_SHIFT);
+ if (shift > maxShift) {
+ maxShift = shift;
+ e->get
+ <Bool>(m_properties.ACCIDENTAL_EXTRA_SHIFT, extra);
+ }
+ }
+ }
+
+ return maxShift;
+}
+
+int
+NotationChord::getAccidentalShift(const Iterator &i, bool &extra) const
+{
+ if (getAsEvent(i)->has(m_properties.ACCIDENTAL_SHIFT)) {
+ int shift = getAsEvent(i)->get
+ <Int>(m_properties.ACCIDENTAL_SHIFT);
+ getAsEvent(i)->get
+ <Bool>(m_properties.ACCIDENTAL_EXTRA_SHIFT, extra);
+ return shift;
+ } else {
+ return 0;
+ }
+}
+
+}
diff --git a/src/gui/editors/notation/NotationChord.h b/src/gui/editors/notation/NotationChord.h
new file mode 100644
index 0000000..7ce12fd
--- /dev/null
+++ b/src/gui/editors/notation/NotationChord.h
@@ -0,0 +1,90 @@
+
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
+
+/*
+ Rosegarden
+ A MIDI and audio sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_NOTATIONCHORD_H_
+#define _RG_NOTATIONCHORD_H_
+
+#include "base/NotationTypes.h"
+#include "base/Sets.h"
+#include "NotationElement.h"
+
+class Iterator;
+
+
+namespace Rosegarden
+{
+
+class Quantizer;
+class NotationProperties;
+
+
+class NotationChord : public GenericChord<NotationElement,
+ NotationElementList,
+ true>
+{
+public:
+ NotationChord(NotationElementList &c,
+ NotationElementList::iterator i,
+ const Quantizer *quantizer,
+ const NotationProperties &properties,
+ const Clef &clef = Clef::DefaultClef,
+ const Key &key = Key::DefaultKey);
+
+ virtual ~NotationChord() { }
+
+ virtual int getHighestNoteHeight() const {
+ return getHeight(getHighestNote());
+ }
+ virtual int getLowestNoteHeight() const {
+ return getHeight(getLowestNote());
+ }
+
+ virtual bool hasStem() const;
+ virtual bool hasStemUp() const;
+
+ virtual bool hasNoteHeadShifted() const;
+ virtual bool isNoteHeadShifted(const NotationElementList::iterator &itr)
+ const;
+
+ void applyAccidentalShiftProperties();
+
+ virtual int getMaxAccidentalShift(bool &extra) const;
+ virtual int getAccidentalShift(const NotationElementList::iterator &itr,
+ bool &extra) const;
+
+protected:
+ const NotationProperties &m_properties;
+ Clef m_clef;
+ Key m_key;
+
+
+ int getHeight(const Iterator&) const;
+};
+
+
+
+}
+
+#endif
diff --git a/src/gui/editors/notation/NotationElement.cpp b/src/gui/editors/notation/NotationElement.cpp
new file mode 100644
index 0000000..7df1cd5
--- /dev/null
+++ b/src/gui/editors/notation/NotationElement.cpp
@@ -0,0 +1,198 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
+
+/*
+ Rosegarden
+ A MIDI and audio sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "NotationElement.h"
+#include "misc/Debug.h"
+
+#include "base/BaseProperties.h"
+#include "base/Event.h"
+#include "base/Exception.h"
+#include "base/NotationTypes.h"
+#include "base/ViewElement.h"
+
+#include <qcanvas.h>
+
+namespace Rosegarden
+{
+
+NotationElement::NotationElement(Event *event)
+ : ViewElement(event),
+ m_recentlyRegenerated(false),
+ m_isColliding(false),
+ m_canvasItem(0),
+ m_extraItems(0)
+{
+ // NOTATION_DEBUG << "new NotationElement "
+ // << this << " wrapping " << event << endl;
+}
+
+NotationElement::~NotationElement()
+{
+ removeCanvasItem();
+}
+
+timeT
+NotationElement::getViewAbsoluteTime() const
+{
+ return event()->getNotationAbsoluteTime();
+}
+
+timeT
+NotationElement::getViewDuration() const
+{
+ return event()->getNotationDuration();
+}
+
+double
+NotationElement::getCanvasX()
+{
+ if (m_canvasItem)
+ return m_canvasItem->x();
+ else {
+ std::cerr << "ERROR: No canvas item for this notation element:";
+ event()->dump(std::cerr);
+ throw NoCanvasItem("No canvas item for notation element of type " +
+ event()->getType(), __FILE__, __LINE__);
+ }
+}
+
+double
+NotationElement::getCanvasY()
+{
+ if (m_canvasItem)
+ return m_canvasItem->y();
+ else {
+ std::cerr << "ERROR: No canvas item for this notation element:";
+ event()->dump(std::cerr);
+ throw NoCanvasItem("No canvas item for notation element of type " +
+ event()->getType(), __FILE__, __LINE__);
+ }
+}
+
+bool
+NotationElement::isRest() const
+{
+ return event()->isa(Note::EventRestType);
+}
+
+bool
+NotationElement::isNote() const
+{
+ return event()->isa(Note::EventType);
+}
+
+bool
+NotationElement::isTuplet() const
+{
+ return event()->has(BaseProperties::BEAMED_GROUP_TUPLET_BASE);
+}
+
+bool
+NotationElement::isGrace() const
+{
+ return event()->has(BaseProperties::IS_GRACE_NOTE) &&
+ event()->get
+ <Bool>(BaseProperties::IS_GRACE_NOTE);
+}
+
+void
+NotationElement::setCanvasItem(QCanvasItem *e, double canvasX, double canvasY)
+{
+ removeCanvasItem();
+ m_recentlyRegenerated = true;
+ m_canvasItem = e;
+ e->move(canvasX, canvasY);
+}
+
+void
+NotationElement::addCanvasItem(QCanvasItem *e, double canvasX, double canvasY)
+{
+ if (!m_canvasItem) {
+ std::cerr << "ERROR: Attempt to add extra canvas item to element without main canvas item:";
+ event()->dump(std::cerr);
+ throw NoCanvasItem("No canvas item for notation element of type " +
+ event()->getType(), __FILE__, __LINE__);
+ }
+ if (!m_extraItems) {
+ m_extraItems = new ItemList;
+ }
+ e->move(canvasX, canvasY);
+ m_extraItems->push_back(e);
+}
+
+void
+NotationElement::removeCanvasItem()
+{
+ m_recentlyRegenerated = false;
+
+ delete m_canvasItem;
+ m_canvasItem = 0;
+
+ if (m_extraItems) {
+
+ for (ItemList::iterator i = m_extraItems->begin();
+ i != m_extraItems->end(); ++i)
+ delete *i;
+ m_extraItems->clear();
+
+ delete m_extraItems;
+ m_extraItems = 0;
+ }
+}
+
+void
+NotationElement::reposition(double canvasX, double canvasY)
+{
+ m_recentlyRegenerated = false;
+ if (!m_canvasItem)
+ return ;
+
+ double dx = canvasX - m_canvasItem->x();
+ double dy = canvasY - m_canvasItem->y();
+ m_canvasItem->move(canvasX, canvasY);
+
+ if (m_extraItems) {
+ for (ItemList::iterator i = m_extraItems->begin();
+ i != m_extraItems->end(); ++i) {
+ (*i)->moveBy(dx, dy);
+ }
+ }
+}
+
+bool
+NotationElement::isSelected()
+{
+ return m_canvasItem ? m_canvasItem->selected() : false;
+}
+
+void
+NotationElement::setSelected(bool selected)
+{
+ m_recentlyRegenerated = false;
+ if (m_canvasItem)
+ m_canvasItem->setSelected(selected);
+}
+
+}
diff --git a/src/gui/editors/notation/NotationElement.h b/src/gui/editors/notation/NotationElement.h
new file mode 100644
index 0000000..c756641
--- /dev/null
+++ b/src/gui/editors/notation/NotationElement.h
@@ -0,0 +1,176 @@
+
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
+
+/*
+ Rosegarden
+ A MIDI and audio sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_NOTATIONELEMENT_H_
+#define _RG_NOTATIONELEMENT_H_
+
+#include "base/Exception.h"
+#include "base/ViewElement.h"
+#include <vector>
+#include "base/Event.h"
+
+
+class QCanvasItem;
+class ItemList;
+
+
+namespace Rosegarden
+{
+
+class Event;
+
+
+/**
+ * The Notation H and V layout is performed on a
+ * NotationElementList. Once this is done, each NotationElement is
+ * affected a QCanvasItem which is set at these coords.
+ *
+ * @see NotationView#showElements()
+ */
+
+class NotationElement : public ViewElement
+{
+public:
+ typedef Exception NoCanvasItem;
+
+ NotationElement(Event *event);
+
+ ~NotationElement();
+
+ virtual timeT getViewAbsoluteTime() const;
+ virtual timeT getViewDuration() const;
+
+ void getLayoutAirspace(double &x, double &width) {
+ x = m_airX;
+ width = m_airWidth;
+ }
+
+ void getCanvasAirspace(double &x, double &width) {
+ x = m_airX - getLayoutX() + getCanvasX();
+ width = m_airWidth;
+ }
+
+ /// returns the x pos of the associated canvas item
+ double getCanvasX();
+
+ /// returns the y pos of the associated canvas item
+ double getCanvasY();
+
+ /**
+ * Sets the X coordinate and width of the space "underneath"
+ * this element, i.e. the extents within which a mouse click
+ * or some such might be considered to be interested in this
+ * element as opposed to any other. These are layout coords
+ */
+ void setLayoutAirspace(double x, double width) {
+ m_airX = x; m_airWidth = width;
+ }
+
+ /// Returns true if the wrapped event is a rest
+ bool isRest() const;
+
+ /// Returns true if the wrapped event is a note
+ bool isNote() const;
+
+ /// Returns true if the wrapped event is a tuplet
+ bool isTuplet() const;
+
+ /// Returns true if the wrapped event is a grace note
+ bool isGrace() const;
+
+ /**
+ * Sets the canvas item representing this notation element on screen.
+ *
+ * NOTE: The object takes ownership of its canvas item.
+ */
+ void setCanvasItem(QCanvasItem *e, double canvasX, double canvasY);
+
+ /**
+ * Add an extra canvas item associated with this element, for
+ * example where an element has been split across two or more
+ * staff rows.
+ *
+ * The element will take ownership of these canvas items and
+ * delete them when it deletes the main canvas item.
+ */
+ void addCanvasItem(QCanvasItem *e, double canvasX, double canvasY);
+
+ /**
+ * Remove the main canvas item and any additional ones.
+ */
+ void removeCanvasItem();
+
+ /**
+ * Reset the position of the canvas item (which is assumed to
+ * exist already).
+ */
+ void reposition(double canvasX, double canvasY);
+
+ /**
+ * Return true if setCanvasItem has been called more recently
+ * than reposition. If true, any code that positions this
+ * element will probably not need to regenerate its sprite as
+ * well, even if other indications suggest otherwise.
+ */
+ bool isRecentlyRegenerated() { return m_recentlyRegenerated; }
+
+ bool isSelected();
+ void setSelected(bool selected);
+
+ /**
+ * Return true if the element is a note which lies at the exactly
+ * same place than another note.
+ * Only valid after NotationVLayout::scanStaff() call.
+ * Only a returned true is meaningful (when 2 notes are colliding, the
+ * first element returns false and the second one returns true).
+ */
+ bool isColliding() { return m_isColliding; }
+
+ void setIsColliding(bool value) { m_isColliding = value; }
+
+ /// Returns the associated canvas item
+ QCanvasItem* getCanvasItem() { return m_canvasItem; }
+
+protected:
+ //--------------- Data members ---------------------------------
+
+ double m_airX;
+ double m_airWidth;
+ bool m_recentlyRegenerated;
+ bool m_isColliding;
+
+ QCanvasItem *m_canvasItem;
+
+ typedef std::vector<QCanvasItem *> ItemList;
+ ItemList *m_extraItems;
+};
+
+typedef ViewElementList NotationElementList;
+
+
+
+}
+
+#endif
diff --git a/src/gui/editors/notation/NotationEraser.cpp b/src/gui/editors/notation/NotationEraser.cpp
new file mode 100644
index 0000000..4124e44
--- /dev/null
+++ b/src/gui/editors/notation/NotationEraser.cpp
@@ -0,0 +1,115 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
+
+/*
+ Rosegarden
+ A MIDI and audio sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "NotationEraser.h"
+#include <kapplication.h>
+
+#include <klocale.h>
+#include "document/ConfigGroups.h"
+#include "base/ViewElement.h"
+#include "commands/notation/EraseEventCommand.h"
+#include "gui/general/EditTool.h"
+#include "NotationTool.h"
+#include "NotationView.h"
+#include "NotePixmapFactory.h"
+#include <kaction.h>
+#include <kconfig.h>
+#include <qiconset.h>
+#include <qstring.h>
+
+
+namespace Rosegarden
+{
+
+NotationEraser::NotationEraser(NotationView* view)
+ : NotationTool("NotationEraser", view),
+ m_collapseRest(false)
+{
+ KConfig *config = kapp->config();
+ config->setGroup(NotationViewConfigGroup);
+ m_collapseRest = config->readBoolEntry("collapse", false);
+
+ new KToggleAction(i18n("Collapse rests after erase"), 0, this,
+ SLOT(slotToggleRestCollapse()), actionCollection(),
+ "toggle_rest_collapse");
+
+ QIconSet icon
+ (NotePixmapFactory::toQPixmap(NotePixmapFactory::
+ makeToolbarPixmap("crotchet")));
+ new KAction(i18n("Switch to Insert Tool"), icon, 0, this,
+ SLOT(slotInsertSelected()), actionCollection(),
+ "insert");
+
+ icon = QIconSet(NotePixmapFactory::toQPixmap(NotePixmapFactory::
+ makeToolbarPixmap("select")));
+ new KAction(i18n("Switch to Select Tool"), icon, 0, this,
+ SLOT(slotSelectSelected()), actionCollection(),
+ "select");
+
+ createMenu("notationeraser.rc");
+}
+
+void NotationEraser::ready()
+{
+ m_nParentView->setCanvasCursor(Qt::pointingHandCursor);
+ m_nParentView->setHeightTracking(false);
+}
+
+void NotationEraser::handleLeftButtonPress(timeT,
+ int,
+ int staffNo,
+ QMouseEvent*,
+ ViewElement* element)
+{
+ if (!element || staffNo < 0)
+ return ;
+
+ EraseEventCommand *command =
+ new EraseEventCommand(m_nParentView->getStaff(staffNo)->getSegment(),
+ element->event(),
+ m_collapseRest);
+
+ m_nParentView->addCommandToHistory(command);
+}
+
+void NotationEraser::slotToggleRestCollapse()
+{
+ m_collapseRest = !m_collapseRest;
+}
+
+void NotationEraser::slotInsertSelected()
+{
+ m_nParentView->slotLastNoteAction();
+}
+
+void NotationEraser::slotSelectSelected()
+{
+ m_parentView->actionCollection()->action("select")->activate();
+}
+
+const QString NotationEraser::ToolName = "notationeraser";
+
+}
+#include "NotationEraser.moc"
diff --git a/src/gui/editors/notation/NotationEraser.h b/src/gui/editors/notation/NotationEraser.h
new file mode 100644
index 0000000..9efdd13
--- /dev/null
+++ b/src/gui/editors/notation/NotationEraser.h
@@ -0,0 +1,81 @@
+
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
+
+/*
+ Rosegarden
+ A MIDI and audio sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_NOTATIONERASER_H_
+#define _RG_NOTATIONERASER_H_
+
+#include "NotationTool.h"
+#include <qstring.h>
+#include "base/Event.h"
+
+
+class QMouseEvent;
+
+
+namespace Rosegarden
+{
+
+class ViewElement;
+class NotationView;
+
+
+/**
+ * This tool will erase a note on mouse click events
+ */
+class NotationEraser : public NotationTool
+{
+ Q_OBJECT
+
+ friend class NotationToolBox;
+
+public:
+
+ virtual void ready();
+
+ virtual void handleLeftButtonPress(timeT,
+ int height,
+ int staffNo,
+ QMouseEvent*,
+ ViewElement* el);
+ static const QString ToolName;
+
+public slots:
+ void slotToggleRestCollapse();
+
+ void slotInsertSelected();
+ void slotSelectSelected();
+
+protected:
+ NotationEraser(NotationView*);
+
+ //--------------- Data members ---------------------------------
+
+ bool m_collapseRest;
+};
+
+
+}
+
+#endif
diff --git a/src/gui/editors/notation/NotationGroup.cpp b/src/gui/editors/notation/NotationGroup.cpp
new file mode 100644
index 0000000..78525d9
--- /dev/null
+++ b/src/gui/editors/notation/NotationGroup.cpp
@@ -0,0 +1,979 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
+
+/*
+ Rosegarden
+ A MIDI and audio sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "NotationGroup.h"
+#include "misc/Debug.h"
+
+#include "base/Equation.h"
+#include "base/Event.h"
+#include "base/NotationRules.h"
+#include "base/NotationTypes.h"
+#include "base/Quantizer.h"
+#include "NotationChord.h"
+#include "NotationElement.h"
+#include "NotationProperties.h"
+#include "NotationStaff.h"
+#include "NoteStyleFactory.h"
+#include "NotePixmapFactory.h"
+
+
+namespace Rosegarden
+{
+
+NotationGroup::NotationGroup(NotationElementList &nel,
+ NELIterator i, const Quantizer *q,
+ std::pair<timeT, timeT> barRange,
+ const NotationProperties &p,
+ const Clef &clef, const Key &key) :
+ AbstractSet<NotationElement, NotationElementList>(nel, i, q),
+ m_barRange(barRange),
+ //!!! What if the clef and/or key change in the course of the group?
+ m_clef(clef),
+ m_key(key),
+ m_weightAbove(0),
+ m_weightBelow(0),
+ m_userSamples(false),
+ m_type(Beamed),
+ m_properties(p)
+{
+ if (!(*i)->event()->get<Int>
+ (BaseProperties::BEAMED_GROUP_ID, m_groupNo)) m_groupNo = -1;
+
+ initialise();
+
+ /*
+ NOTATION_DEBUG << "NotationGroup::NotationGroup: id is " << m_groupNo << endl;
+ i = getInitialElement();
+ while (i != getContainer().end()) {
+ long gid = -1;
+ (*i)->event()->get<Int>(BEAMED_GROUP_ID, gid);
+ NOTATION_DEBUG << "Found element with group id "
+ << gid << endl;
+ if (i == getFinalElement()) break;
+ ++i;
+ }
+ */
+}
+
+NotationGroup::NotationGroup(NotationElementList &nel,
+ const Quantizer *q,
+ const NotationProperties &p,
+ const Clef &clef, const Key &key) :
+ AbstractSet<NotationElement, NotationElementList>(nel, nel.end(), q),
+ m_barRange(0, 0),
+ //!!! What if the clef and/or key change in the course of the group?
+ m_clef(clef),
+ m_key(key),
+ m_weightAbove(0),
+ m_weightBelow(0),
+ m_userSamples(true),
+ m_groupNo( -1),
+ m_type(Beamed),
+ m_properties(p)
+{
+ //...
+}
+
+NotationGroup::~NotationGroup()
+{}
+
+bool NotationGroup::test(const NELIterator &i)
+{
+ // An event is a candidate for being within the bounds of the
+ // set if it's simply within the same bar as the original event.
+ // (Groups may contain other groups, so our bounds may enclose
+ // events that aren't members of the group: we reject those in
+ // sample rather than test, so as to avoid erroneously ending
+ // the group too soon.)
+
+ return ((*i)->getViewAbsoluteTime() >= m_barRange.first &&
+ (*i)->getViewAbsoluteTime() < m_barRange.second);
+}
+
+bool
+NotationGroup::sample(const NELIterator &i, bool goingForwards)
+{
+ if (m_baseIterator == getContainer().end()) {
+ m_baseIterator = i;
+ if (m_userSamples)
+ m_initial = i;
+ }
+ if (m_userSamples)
+ m_final = i;
+
+ std::string t;
+ if (!(*i)->event()->get<String>(BaseProperties::BEAMED_GROUP_TYPE, t)) {
+ NOTATION_DEBUG << "NotationGroup::NotationGroup: Rejecting sample() for non-beamed element" << endl;
+ return false;
+ }
+
+ long n;
+ if (!(*i)->event()->get<Int>(BaseProperties::BEAMED_GROUP_ID, n)) return false;
+ if (m_groupNo == -1) {
+ m_groupNo = n;
+ } else if (n != m_groupNo) {
+ NOTATION_DEBUG << "NotationGroup::NotationGroup: Rejecting sample() for event with group id " << n << " (mine is " << m_groupNo << ")" << endl;
+ return false;
+ }
+
+ if (t == BaseProperties::GROUP_TYPE_BEAMED) {
+ m_type = Beamed;
+ } else if (t == BaseProperties::GROUP_TYPE_TUPLED) {
+ m_type = Tupled;
+ } else if (t == BaseProperties::GROUP_TYPE_GRACE) {
+ std::cerr << "NotationGroup::NotationGroup: WARNING: Obsolete group type Grace found" << std::endl;
+ return false;
+ } else {
+ NOTATION_DEBUG << "NotationGroup::NotationGroup: Warning: Rejecting sample() for unknown GroupType \"" << t << "\"" << endl;
+ return false;
+ }
+
+ NOTATION_DEBUG << "NotationGroup::sample: group id is " << m_groupNo << endl;
+
+ AbstractSet<NotationElement, NotationElementList>::sample
+ (i, goingForwards);
+
+ // If the sum of the distances from the middle line to the notes
+ // above the middle line exceeds the sum of the distances from the
+ // middle line to the notes below, then the beam goes below. We
+ // can calculate the weightings here, as we construct the group.
+
+ if (!static_cast<NotationElement*>(*i)->isNote())
+ return true;
+ if (m_userSamples) {
+ if (m_initialNote == getContainer().end()) m_initialNote = i;
+ m_finalNote = i;
+ }
+
+ // The code that uses the Group should not rely on the presence of
+ // e.g. BEAM_GRADIENT to indicate that a beam should be drawn;
+ // it's possible the gradient might be left over from a previous
+ // calculation and the group might have changed since. Instead it
+ // should test BEAMED, which may be false even if there is a
+ // gradient present.
+ (*i)->event()->setMaybe<Bool>(NotationProperties::BEAMED, false);
+
+ int h = height(i);
+ if (h > 4)
+ m_weightAbove += h - 4;
+ if (h < 4)
+ m_weightBelow += 4 - h;
+
+ return true;
+}
+
+bool
+NotationGroup::contains(const NELIterator &i) const
+{
+ NELIterator j(getInitialElement()),
+ k( getFinalElement());
+
+ for (;;) {
+ if (j == i)
+ return true;
+ if (j == k)
+ return false;
+ ++j;
+ }
+}
+
+int
+NotationGroup::height(const NELIterator &i) const
+{
+ long h = 0;
+ if ((*i)->event()->get<Int>(NotationProperties::HEIGHT_ON_STAFF, h)) {
+ return h;
+ }
+
+ //!!! int pitch = (*i)->event()->get<Int>(PITCH);
+ // NotationDisplayPitch p(pitch, m_clef, m_key);
+ // h = p.getHeightOnStaff();
+
+ try {
+ Pitch pitch(*getAsEvent(i));
+ h = pitch.getHeightOnStaff(m_clef, m_key);
+ } catch (...) {
+ // no pitch!
+ }
+
+ // not setMaybe, as we know the property is absent:
+ (*i)->event()->set<Int>(NotationProperties::HEIGHT_ON_STAFF, h, false);
+ return h;
+}
+
+void
+NotationGroup::applyStemProperties()
+{
+ NotationRules rules;
+
+ NELIterator
+ initialNote(getInitialNote()),
+ finalNote(getFinalNote());
+
+ if (initialNote == getContainer().end() ||
+ initialNote == finalNote) {
+ //!!! This is not true -- if initialNote == finalNote there is
+ // one note in the group, not none. But we still won't have a
+ // beam.
+ NOTATION_DEBUG << "NotationGroup::applyStemProperties: no notes in group"
+ << endl;
+ return; // no notes, no case to answer
+ }
+
+ if (getHighestNote() == getContainer().end()) {
+ std::cerr << "ERROR: NotationGroup::applyStemProperties: no highest note!" << std::endl;
+ abort();
+ }
+
+ if (getLowestNote() == getContainer().end()) {
+ std::cerr << "ERROR: NotationGroup::applyStemProperties: no lowest note!" << std::endl;
+ abort();
+ }
+
+ int up = 0, down = 0;
+
+ for (NELIterator i = initialNote; i != getContainer().end(); ++i) {
+ NotationElement* el = static_cast<NotationElement*>(*i);
+ if (el->isNote()) {
+ if (el->event()->has(NotationProperties::STEM_UP)) {
+ if (el->event()->get<Bool>(NotationProperties::STEM_UP)) ++up;
+ else ++down;
+ }
+ }
+
+ if (i == finalNote) break;
+ }
+
+ NOTATION_DEBUG << "NotationGroup::applyStemProperties: weightAbove "
+ << m_weightAbove << ", weightBelow " << m_weightBelow
+ << ", up " << up << ", down " << down << endl;
+
+ bool aboveNotes = rules.isBeamAbove(height(getHighestNote()),
+ height(getLowestNote()),
+ m_weightAbove,
+ m_weightBelow);
+ if (up != down) {
+ if (up > down) aboveNotes = true;
+ else aboveNotes = false;
+ }
+
+ NOTATION_DEBUG << "NotationGroup::applyStemProperties: hence aboveNotes "
+ << aboveNotes << endl;
+
+ /*!!!
+ if ((*initialNote)->event()->has(STEM_UP) &&
+ (*initialNote)->event()->isPersistent<Bool>(STEM_UP)) {
+ aboveNotes = (*initialNote)->event()->get<Bool>(STEM_UP);
+ }
+
+ if ((*initialNote)->event()->has(NotationProperties::BEAM_ABOVE) &&
+ (*initialNote)->event()->isPersistent<Bool>(NotationProperties::BEAM_ABOVE)) {
+ aboveNotes = (*initialNote)->event()->get<Bool>
+ (NotationProperties::BEAM_ABOVE);
+ }
+ */
+ for (NELIterator i = initialNote; i != getContainer().end(); ++i) {
+
+ NotationElement* el = static_cast<NotationElement*>(*i);
+
+ el->event()->setMaybe<Bool>(NotationProperties::BEAM_ABOVE, aboveNotes);
+
+ if (el->isNote() &&
+ el->event()->has(BaseProperties::NOTE_TYPE) &&
+ el->event()->get<Int>(BaseProperties::NOTE_TYPE) < Note::Crotchet &&
+ el->event()->has(BaseProperties::BEAMED_GROUP_ID) &&
+ el->event()->get<Int>(BaseProperties::BEAMED_GROUP_ID) == m_groupNo) {
+
+ el->event()->setMaybe<Bool>(NotationProperties::BEAMED, true);
+ // el->event()->setMaybe<Bool>(m_properties.VIEW_LOCAL_STEM_UP, aboveNotes);
+
+ } else if (el->isNote()) {
+
+ if (i == initialNote || i == finalNote) {
+ (*i)->event()->setMaybe<Bool>
+ (m_properties.VIEW_LOCAL_STEM_UP, aboveNotes);
+ } else {
+ (*i)->event()->setMaybe<Bool>
+ (m_properties.VIEW_LOCAL_STEM_UP, !aboveNotes);
+ }
+ }
+
+ if (i == finalNote) break;
+ }
+}
+
+bool
+NotationGroup::haveInternalRest()
+const
+{
+ bool inside = false;
+ bool found = false;
+
+ for (NELIterator i = getInitialNote(); i != getContainer().end(); ++i) {
+ NotationElement* el = static_cast<NotationElement*>(*i);
+
+ if (el->isNote() &&
+ el->event()->has(BaseProperties::NOTE_TYPE) &&
+ el->event()->get<Int>(BaseProperties::NOTE_TYPE) < Note::Crotchet &&
+ el->event()->has(BaseProperties::BEAMED_GROUP_ID) &&
+ el->event()->get<Int>(BaseProperties::BEAMED_GROUP_ID) == m_groupNo) {
+ if (found) return true; // a rest is wholly enclosed by beamed notes
+ inside = true;
+ }
+
+ if (el->isRest() && inside) found = true;
+
+ if (i == getFinalNote()) break;
+ }
+
+ return false;
+}
+
+NotationGroup::Beam
+NotationGroup::calculateBeam(NotationStaff &staff)
+{
+ NotationRules rules;
+
+ Beam beam;
+ beam.aboveNotes = true;
+ beam.startY = 0;
+ beam.gradient = 0;
+ beam.necessary = false;
+
+ NELIterator
+ initialNote(getInitialNote()),
+ finalNote(getFinalNote());
+
+ if (initialNote == getContainer().end() ||
+ initialNote == finalNote) {
+ return beam; // no notes, or at most one: no case to answer
+ }
+
+ beam.aboveNotes = rules.isBeamAbove(height(getHighestNote()),
+ height(getLowestNote()),
+ m_weightAbove,
+ m_weightBelow);
+
+ if ((*initialNote)->event()->has(NotationProperties::BEAM_ABOVE)) {
+ beam.aboveNotes = (*initialNote)->event()->get
+ <Bool>
+ (NotationProperties::BEAM_ABOVE);
+ }
+
+ timeT crotchet = Note(Note::Crotchet).getDuration();
+
+ beam.necessary =
+ (*initialNote)->getViewDuration() < crotchet &&
+ (*finalNote)->getViewDuration() < crotchet;
+
+ beam.necessary = beam.necessary &&
+ (((*finalNote)->getViewAbsoluteTime() >
+ (*initialNote)->getViewAbsoluteTime()) ||
+ (((*finalNote)->getViewAbsoluteTime() ==
+ (*initialNote)->getViewAbsoluteTime()) &&
+ ((*finalNote)->event()->getSubOrdering() >
+ (*initialNote)->event()->getSubOrdering())));
+
+ // We continue even if the beam is not necessary, because the
+ // same data is used to generate the tupling line in tupled
+ // groups that do not have beams
+
+ // if (!beam.necessary) return beam;
+
+ NOTATION_DEBUG << "NotationGroup::calculateBeam: beam necessariness: " << beam.necessary << endl;
+
+ NotationChord initialChord(getContainer(), initialNote, &getQuantizer(),
+ m_properties, m_clef, m_key),
+ finalChord(getContainer(), finalNote, &getQuantizer(),
+ m_properties, m_clef, m_key);
+
+ if (initialChord.getInitialElement() == finalChord.getInitialElement()) {
+ return beam;
+ }
+
+ bool isGrace =
+ (*initialNote)->event()->has(BaseProperties::IS_GRACE_NOTE) &&
+ (*initialNote)->event()->get<Bool>(BaseProperties::IS_GRACE_NOTE);
+
+ int initialHeight, finalHeight, extremeHeight;
+ NELIterator extremeNote;
+
+ if (beam.aboveNotes) {
+
+ initialHeight = height(initialChord.getHighestNote());
+ finalHeight = height( finalChord.getHighestNote());
+ extremeHeight = height( getHighestNote());
+ extremeNote = getHighestNote();
+
+ } else {
+ initialHeight = height(initialChord.getLowestNote());
+ finalHeight = height( finalChord.getLowestNote());
+ extremeHeight = height( getLowestNote());
+ extremeNote = getLowestNote();
+ }
+
+ int diff = initialHeight - finalHeight;
+ if (diff < 0) diff = -diff;
+
+ bool linear =
+ (beam.aboveNotes ?
+ (extremeHeight <= std::max(initialHeight, finalHeight)) :
+ (extremeHeight >= std::min(initialHeight, finalHeight)));
+
+ if (!linear) {
+ if (diff > 2)
+ diff = 1;
+ else
+ diff = 0;
+ }
+
+ // Now, we need to judge the height of the beam such that the
+ // nearest note of the whole group, the nearest note of the first
+ // chord and the nearest note of the final chord are all at least
+ // two note-body-heights away from it, and at least one of the
+ // start and end points is at least the usual note stem-length
+ // away from it. This is a straight-line equation y = mx + c,
+ // where we have m and two x,y pairs and need to find c.
+
+ //!!! If we find that making one extreme a sensible distance from
+ //the note head makes the other extreme way too far away from it
+ //in the direction of the gradient, then we should flatten the
+ //gradient. There may be a better heuristic for this.
+
+ int initialX = (int)(*initialNote)->getLayoutX();
+ int finalDX = (int) (*finalNote)->getLayoutX() - initialX;
+ int extremeDX = (int)(*extremeNote)->getLayoutX() - initialX;
+
+ int spacing = staff.getNotePixmapFactory(isGrace).getLineSpacing();
+
+ beam.gradient = 0;
+ if (finalDX > 0) {
+ do {
+ if (diff == 0)
+ break;
+ else if (diff > 3)
+ diff = 3;
+ else
+ --diff;
+ beam.gradient = (diff * spacing * 100) / (finalDX * 2);
+ } while (beam.gradient > 18);
+ } else {
+ beam.gradient = 0;
+ }
+ if (initialHeight < finalHeight)
+ beam.gradient = -beam.gradient;
+
+ int finalY = staff.getLayoutYForHeight(finalHeight);
+ int extremeY = staff.getLayoutYForHeight(extremeHeight);
+
+ int c0 = staff.getLayoutYForHeight(initialHeight), c1, c2;
+ double dgrad = (double)beam.gradient / 100.0;
+
+ Equation::solve(Equation::C, extremeY, dgrad, extremeDX, c1);
+ Equation::solve(Equation::C, finalY, dgrad, finalDX, c2);
+
+ using std::max;
+ using std::min;
+ long shortestNoteType = Note::Quaver;
+ if (!(*getShortestElement())->event()->get
+ <Int>(BaseProperties::NOTE_TYPE,
+ shortestNoteType)) {
+ NOTATION_DEBUG << "NotationGroup::calculateBeam: WARNING: Shortest element has no note-type; should this be possible?" << endl;
+ NOTATION_DEBUG << "(Event dump follows)" << endl;
+ (*getShortestElement())->event()->dump(std::cerr);
+ }
+
+ // minimal stem lengths at start, middle-extreme and end of beam
+ int sl = staff.getNotePixmapFactory(isGrace).getStemLength();
+ int ml = spacing * 2;
+ int el = sl;
+
+ NOTATION_DEBUG << "c0: " << c0 << ", c1: " << c1 << ", c2: " << c2 << endl;
+ NOTATION_DEBUG << "sl: " << sl << ", ml: " << ml << ", el: " << el << endl;
+
+ // If the stems are down, we will need to ensure they end at
+ // heights lower than 0 if there's an internal rest -- likewise
+ // with stems up and an internal rest we need to ensure they end
+ // at higher than 8.
+ // [Avoid doing expensive haveInternalRest() test where possible]
+
+ if (beam.aboveNotes) {
+
+ int topY = staff.getLayoutYForHeight(8);
+
+ if ((c0 - sl > topY) || (c1 - ml > topY) || (c2 - el > topY)) {
+ if (haveInternalRest()) {
+ if (c0 - sl > topY) sl = c0 - topY;
+ if (c1 - ml > topY) ml = c1 - topY;
+ if (c2 - el > topY) el = c2 - topY;
+ NOTATION_DEBUG << "made internal rest adjustment for above notes" << endl;
+ NOTATION_DEBUG << "sl: " << sl << ", ml: " << ml << ", el: " << el << endl;
+ }
+ }
+ } else {
+ int bottomY = staff.getLayoutYForHeight(0);
+
+ if ((c0 + sl < bottomY) || (c1 + ml < bottomY) || (c2 + el < bottomY)) {
+ if (haveInternalRest()) {
+ if (c0 + sl < bottomY) sl = bottomY - c0;
+ if (c1 + ml < bottomY) ml = bottomY - c1;
+ if (c2 + el < bottomY) el = bottomY - c2;
+ NOTATION_DEBUG << "made internal rest adjustment for below notes" << endl;
+ NOTATION_DEBUG << "sl: " << sl << ", ml: " << ml << ", el: " << el << endl;
+ }
+ }
+ }
+
+
+ if (shortestNoteType < Note::Semiquaver) {
+ int off = spacing * (Note::Semiquaver - shortestNoteType);
+ sl += off;
+ el += off;
+ }
+
+ if (shortestNoteType < Note::Quaver) {
+ int off = spacing * (Note::Quaver - shortestNoteType);
+ ml += off;
+ }
+
+
+ int midY = staff.getLayoutYForHeight(4);
+
+ // ensure extended to middle line if necessary, and assign suitable stem length
+ if (beam.aboveNotes) {
+ if (c0 - sl > midY) sl = c0 - midY;
+ if (c1 - ml > midY) ml = c1 - midY;
+ if (c2 - el > midY) el = c2 - midY;
+ if (extremeDX > 1.0 || extremeDX < -1.0) {
+ // beam.gradient = int(100 * double(c2 - c0) / double(extremeDX));
+ }
+ beam.startY = min(min(c0 - sl, c1 - ml), c2 - el);
+ } else {
+ if (c0 + sl < midY) sl = midY - c0;
+ if (c1 + ml < midY) ml = midY - c1;
+ if (c2 + el < midY) el = midY - c2;
+ if (extremeDX > 1.0 || extremeDX < -1.0) {
+ // beam.gradient = int(100 * double(c2 - c0) / double(extremeDX));
+ }
+ beam.startY = max(max(c0 + sl, c1 + ml), c2 + el);
+ }
+ /*
+ NOTATION_DEBUG << "NotationGroup::calculateBeam: beam data:" << endl
+ << "gradient: " << beam.gradient << endl
+ << "(c0 " << c0 << ", c2 " << c2 << ", extremeDX " << extremeDX << ")" << endl
+ << "startY: " << beam.startY << endl
+ << "aboveNotes: " << beam.aboveNotes << endl
+ << "shortestNoteType: " << shortestNoteType << endl
+ << "necessary: " << beam.necessary << endl;
+ */
+ return beam;
+}
+
+void
+NotationGroup::applyBeam(NotationStaff &staff)
+{
+ // NOTATION_DEBUG << "NotationGroup::applyBeam, group no is " << m_groupNo << endl;
+ /*
+ NOTATION_DEBUG << "\nNotationGroup::applyBeam" << endl;
+ NOTATION_DEBUG << "Group id: " << m_groupNo << ", type " << m_type << endl;
+ NOTATION_DEBUG << "Coverage:" << endl;
+ int i = 0;
+ for (NELIterator i = getInitialElement(); i != getContainer().end(); ++i) {
+ (*i)->event()->dump(cerr);
+ if (i == getFinalElement()) break;
+ }
+ {
+ NELIterator i(getInitialNote());
+ NOTATION_DEBUG << "Initial note: " << (i == getContainer().end() ? -1 : (*i)->event()->getAbsoluteTime()) << endl;
+ }
+ {
+ NELIterator i(getFinalNote());
+ NOTATION_DEBUG << "Final note: " << (i == getContainer().end() ? -1 : (*i)->event()->getAbsoluteTime()) << endl;
+ }
+ {
+ NELIterator i(getHighestNote());
+ NOTATION_DEBUG << "Highest note: " << (i == getContainer().end() ? -1 : (*i)->event()->getAbsoluteTime()) << endl;
+ }
+ {
+ NELIterator i(getLowestNote());
+ NOTATION_DEBUG << "Lowest note: " << (i == getContainer().end() ? -1 : (*i)->event()->getAbsoluteTime()) << endl;
+ }
+ */
+ Beam beam(calculateBeam(staff));
+ if (!beam.necessary) {
+ for (NELIterator i = getInitialNote(); i != getContainer().end(); ++i) {
+ (*i)->event()->unset(NotationProperties::BEAMED);
+ (*i)->event()->unset(m_properties.TUPLING_LINE_MY_Y);
+ if (i == getFinalNote())
+ break;
+ }
+ return ;
+ }
+
+ // NOTATION_DEBUG << "NotationGroup::applyBeam: Beam is necessary" << endl;
+
+ NELIterator initialNote(getInitialNote()),
+ finalNote( getFinalNote());
+ int initialX = (int)(*initialNote)->getLayoutX();
+ timeT finalTime = (*finalNote)->getViewAbsoluteTime();
+
+ // For each chord in the group, we nominate the note head furthest
+ // from the beam as the primary note, the one that "owns" the stem
+ // and the section of beam up to the following chord. For this
+ // note, we need to:
+ //
+ // * Set the start height, start x-coord and gradient of the beam
+ // (we can't set the stem length for this note directly, because
+ // we don't know its y-coordinate yet)
+ //
+ // * Set width of this section of beam
+ //
+ // * Set the number of beams required for the following note (one
+ // slight complication here: a beamed group in which the very
+ // first chord is shorter than the following one. Here the first
+ // chord needs to know it's the first, or else it can't draw the
+ // part-beams immediately to its right correctly.)
+ //
+ // For the rest of the notes in the chord, we just need to
+ // indicate that they aren't part of the beam-drawing process and
+ // don't need to draw a stem.
+
+ NELIterator prev = getContainer().end(), prevprev = getContainer().end();
+ double gradient = (double)beam.gradient / 100.0;
+
+ // NOTATION_DEBUG << "NotationGroup::applyBeam starting for group "<< this << endl;
+
+ for (NELIterator i = getInitialNote(); i != getContainer().end(); ++i) {
+ NotationElement* el = static_cast<NotationElement*>(*i);
+
+ // Clear tuplingness for all events in the group, to be
+ // reinstated by any subsequent call to applyTuplingLine. We
+ // do this because applyTuplingLine doesn't clear these
+ // properties from notes that don't need them; it only applies
+ // them to notes that do.
+ el->event()->unset(m_properties.TUPLING_LINE_MY_Y);
+
+ if (el->isNote() &&
+ el->event()->has(BaseProperties::NOTE_TYPE) &&
+ el->event()->get
+ <Int>(BaseProperties::NOTE_TYPE) < Note::Crotchet &&
+ el->event()->has(BaseProperties::BEAMED_GROUP_ID) &&
+ el->event()->get<Int>(BaseProperties::BEAMED_GROUP_ID) == m_groupNo) {
+
+ NotationChord chord(getContainer(), i, &getQuantizer(),
+ m_properties, m_clef, m_key);
+ unsigned int j;
+
+ // NOTATION_DEBUG << "NotationGroup::applyBeam: Found chord" << endl;
+
+ bool hasShifted = chord.hasNoteHeadShifted();
+
+ for (j = 0; j < chord.size(); ++j) {
+ NotationElement *el = static_cast<NotationElement*>(*chord[j]);
+
+ el->event()->setMaybe<Bool>
+ (m_properties.CHORD_PRIMARY_NOTE, false);
+
+ el->event()->setMaybe<Bool>
+ (m_properties.DRAW_FLAG, false);
+
+ el->event()->setMaybe<Bool>
+ (NotationProperties::BEAMED, true);
+
+ el->event()->setMaybe<Bool>
+ (NotationProperties::BEAM_ABOVE, beam.aboveNotes);
+
+ el->event()->setMaybe<Bool>
+ (m_properties.VIEW_LOCAL_STEM_UP, beam.aboveNotes);
+
+ bool shifted = chord.isNoteHeadShifted(chord[j]);
+ el->event()->setMaybe<Bool>
+ (m_properties.NOTE_HEAD_SHIFTED, shifted);
+
+ long dots = 0;
+ (void)el->event()->get
+ <Int>(BaseProperties::NOTE_DOTS, dots);
+
+ el->event()->setMaybe<Bool>
+ (m_properties.NOTE_DOT_SHIFTED, false);
+ if (hasShifted && beam.aboveNotes) {
+ long dots = 0;
+ (void)el->event()->get
+ <Int>(BaseProperties::NOTE_DOTS, dots);
+ if (dots > 0) {
+ el->event()->setMaybe<Bool>
+ (m_properties.NOTE_DOT_SHIFTED, true);
+ }
+ }
+
+ el->event()->setMaybe<Bool>
+ (m_properties.NEEDS_EXTRA_SHIFT_SPACE,
+ chord.hasNoteHeadShifted() && !beam.aboveNotes);
+ }
+
+ if (beam.aboveNotes)
+ j = 0;
+ else
+ j = chord.size() - 1;
+
+ NotationElement *el = static_cast<NotationElement*>(*chord[j]);
+ el->event()->setMaybe<Bool>(NotationProperties::BEAMED, false); // set later
+ el->event()->setMaybe<Bool>(m_properties.DRAW_FLAG, true); // set later
+
+ int x = (int)el->getLayoutX();
+ int myY = (int)(gradient * (x - initialX)) + beam.startY;
+
+ int beamCount =
+ NoteStyleFactory::getStyleForEvent(el->event())->
+ getFlagCount(el->event()->get
+ <Int>(BaseProperties::NOTE_TYPE));
+
+ // If THIS_PART_BEAMS is true, then when drawing the
+ // chord, if it requires more beams than the following
+ // chord then they should be added as partial beams to the
+ // right of the stem.
+
+ // If NEXT_PART_BEAMS is true, then when drawing the
+ // chord, if it requires fewer beams than the following
+ // chord then the difference should be added as partial
+ // beams to the left of the following chord's stem.
+
+ // Procedure for setting these: If we have more beams than
+ // the preceding chord, then the preceding chord should
+ // have NEXT_PART_BEAMS set, until possibly unset again on
+ // the next iteration. If we have at least as many beams
+ // as the preceding chord, then the preceding chord should
+ // have THIS_PART_BEAMS unset and the one before it should
+ // have NEXT_PART_BEAMS unset. The first chord should
+ // have THIS_PART_BEAMS set, until possibly unset again on
+ // the next iteration.
+
+ if (prev != getContainer().end()) {
+
+ NotationElement *prevEl = static_cast<NotationElement*>(*prev);
+ int secWidth = x - (int)prevEl->getLayoutX();
+
+ // prevEl->event()->setMaybe<Int>(BEAM_NEXT_Y, myY);
+
+ prevEl->event()->setMaybe<Int>
+ (m_properties.BEAM_SECTION_WIDTH, secWidth);
+ prevEl->event()->setMaybe<Int>
+ (m_properties.BEAM_NEXT_BEAM_COUNT, beamCount);
+
+ int prevBeamCount =
+ NoteStyleFactory::getStyleForEvent(prevEl->event())->
+ getFlagCount(prevEl->event()->get
+ <Int>(BaseProperties::NOTE_TYPE));
+
+ if ((beamCount > 0) && (prevBeamCount > 0)) {
+ el->event()->setMaybe<Bool>(m_properties.BEAMED, true);
+ el->event()->setMaybe<Bool>(m_properties.DRAW_FLAG, false);
+ prevEl->event()->setMaybe<Bool>(m_properties.BEAMED, true);
+ prevEl->event()->setMaybe<Bool>(m_properties.DRAW_FLAG, false);
+ }
+
+ if (beamCount >= prevBeamCount) {
+ prevEl->event()->setMaybe<Bool>
+ (m_properties.BEAM_THIS_PART_BEAMS, false);
+ if (prevprev != getContainer().end()) {
+ (*prevprev)->event()->setMaybe<Bool>
+ (m_properties.BEAM_NEXT_PART_BEAMS, false);
+ }
+ }
+
+ if (beamCount > prevBeamCount) {
+ prevEl->event()->setMaybe<Bool>
+ (m_properties.BEAM_NEXT_PART_BEAMS, true);
+ }
+
+ } else {
+ el->event()->setMaybe<Bool>(m_properties.BEAM_THIS_PART_BEAMS, true);
+ }
+
+ el->event()->setMaybe<Bool>(m_properties.CHORD_PRIMARY_NOTE, true);
+
+ el->event()->setMaybe<Int>(m_properties.BEAM_MY_Y, myY);
+ el->event()->setMaybe<Int>(m_properties.BEAM_GRADIENT, beam.gradient);
+
+ // until they're set next time around the loop, as (*prev)->...
+ // el->event()->setMaybe<Int>(m_properties.BEAM_NEXT_Y, myY);
+ el->event()->setMaybe<Int>(m_properties.BEAM_SECTION_WIDTH, 0);
+ el->event()->setMaybe<Int>(m_properties.BEAM_NEXT_BEAM_COUNT, 1);
+
+ prevprev = prev;
+ prev = chord[j];
+ i = chord.getFinalElement();
+
+ }
+ else if (el->isNote()) {
+
+ //!!! should we really be setting these here as well as in
+ // applyStemProperties?
+ /*
+ if (i == initialNote || i == finalNote) {
+ (*i)->event()->setMaybe<Bool>(m_properties.VIEW_LOCAL_STEM_UP, beam.aboveNotes);
+ } else {
+ (*i)->event()->setMaybe<Bool>(m_properties.VIEW_LOCAL_STEM_UP, !beam.aboveNotes);
+ }
+ */
+ }
+
+ if (i == finalNote || el->getViewAbsoluteTime() > finalTime) break;
+ }
+}
+
+void
+NotationGroup::applyTuplingLine(NotationStaff &staff)
+{
+ // NOTATION_DEBUG << "NotationGroup::applyTuplingLine, group no is " << m_groupNo << ", group type is " << m_type << endl;
+
+ if (m_type != Tupled)
+ return ;
+
+ // NOTATION_DEBUG << "NotationGroup::applyTuplingLine: line is necessary" << endl;
+
+ Beam beam(calculateBeam(staff));
+
+ NELIterator
+ initialNote(getInitialNote()),
+ finalNote(getFinalNote()),
+ initialElement(getInitialElement()),
+ finalElement(getFinalElement());
+
+ NELIterator initialNoteOrRest(initialElement);
+ NotationElement* initialNoteOrRestEl = static_cast<NotationElement*>(*initialNoteOrRest);
+
+ while (initialNoteOrRest != finalElement &&
+ !(initialNoteOrRestEl->isNote() ||
+ initialNoteOrRestEl->isRest())) {
+ ++initialNoteOrRest;
+ initialNoteOrRestEl = static_cast<NotationElement*>(*initialNoteOrRest);
+ }
+
+ if (!initialNoteOrRestEl->isRest()) {
+ initialNoteOrRest = initialNote;
+ initialNoteOrRestEl = static_cast<NotationElement*>(*initialNoteOrRest);
+ }
+
+ if (initialNoteOrRest == staff.getViewElementList()->end()) return;
+
+ bool isGrace = false;
+ if (initialNote != staff.getViewElementList()->end()) {
+ isGrace =
+ (*initialNote)->event()->has(BaseProperties::IS_GRACE_NOTE) &&
+ (*initialNote)->event()->get<Bool>(BaseProperties::IS_GRACE_NOTE);
+ }
+
+ // NOTATION_DEBUG << "NotationGroup::applyTuplingLine: first element is " << (initialNoteOrRestEl->isNote() ? "Note" : "Non-Note") << ", last is " << (static_cast<NotationElement*>(*finalElement)->isNote() ? "Note" : "Non-Note") << endl;
+
+ int initialX = (int)(*initialNoteOrRest)->getLayoutX();
+ int finalX = (int)(*finalElement)->getLayoutX();
+
+ if (initialNote == staff.getViewElementList()->end() &&
+ finalNote == staff.getViewElementList()->end()) {
+
+ Event *e = (*initialNoteOrRest)->event();
+ e->setMaybe<Int>(m_properties.TUPLING_LINE_MY_Y,
+ staff.getLayoutYForHeight(12));
+ e->setMaybe<Int>(m_properties.TUPLING_LINE_WIDTH, finalX - initialX);
+ e->setMaybe<Int>(m_properties.TUPLING_LINE_GRADIENT, 0);
+
+ } else {
+
+ // only notes have height
+ int initialY = staff.getLayoutYForHeight(height(initialNote));
+ int finalY = staff.getLayoutYForHeight(height( finalNote));
+
+ // if we have a beam and both end-points of it are notes,
+ // place the tupling number over it (that is, make the tupling
+ // line follow the beam and say so); otherwise make the line
+ // follow the gradient a beam would have, but on the other
+ // side of the notes
+ bool followBeam =
+ (beam.necessary &&
+ (*initialNoteOrRest)->event()->isa(Note::EventType) &&
+ (finalNote == finalElement));
+
+ int startY = (followBeam ? beam.startY :
+ initialY - (beam.startY - initialY));
+
+ int endY = startY + (int)((finalX - initialX) *
+ ((double)beam.gradient / 100.0));
+
+ // NOTATION_DEBUG << "applyTuplingLine: beam.startY is " << beam.startY << ", initialY is " << initialY << " so my startY is " << startY << ", endY " << endY << ", beam.gradient " << beam.gradient << endl;
+
+ int nh = staff.getNotePixmapFactory(isGrace).getNoteBodyHeight();
+
+ if (followBeam) { // adjust to move text slightly away from beam
+
+ int maxEndBeamCount = 1;
+ long bc;
+ if ((*initialNoteOrRest)->event()->get<Int>
+ (m_properties.BEAM_NEXT_BEAM_COUNT, bc)) {
+ if (bc > maxEndBeamCount)
+ maxEndBeamCount = bc;
+ }
+ if ((*finalNote)->event()->get<Int>
+ (m_properties.BEAM_NEXT_BEAM_COUNT, bc)) {
+ if (bc > maxEndBeamCount)
+ maxEndBeamCount = bc;
+ }
+
+ int extraBeamSpace = maxEndBeamCount * nh + nh / 2;
+
+ if (beam.aboveNotes) {
+ startY -= extraBeamSpace;
+ endY -= extraBeamSpace;
+ finalX += nh;
+ } else {
+ startY += extraBeamSpace;
+ endY += extraBeamSpace;
+ finalX -= nh;
+ }
+
+ } else { // adjust to place close to note heads
+
+ if (startY < initialY) {
+ if (initialY - startY > nh * 3)
+ startY = initialY - nh * 3;
+ if ( finalY - endY < nh * 2)
+ startY -= nh * 2 - (finalY - endY);
+ } else {
+ if (startY - initialY > nh * 3)
+ startY = initialY + nh * 3;
+ if ( endY - finalY < nh * 2)
+ startY += nh * 2 - (endY - finalY);
+ }
+ }
+
+ Event *e = (*initialNoteOrRest)->event();
+ e->setMaybe<Int>(m_properties.TUPLING_LINE_MY_Y, startY);
+ e->setMaybe<Int>(m_properties.TUPLING_LINE_WIDTH, finalX - initialX);
+ e->setMaybe<Int>(m_properties.TUPLING_LINE_GRADIENT, beam.gradient);
+ e->setMaybe<Bool>(m_properties.TUPLING_LINE_FOLLOWS_BEAM, followBeam);
+ }
+}
+
+}
diff --git a/src/gui/editors/notation/NotationGroup.h b/src/gui/editors/notation/NotationGroup.h
new file mode 100644
index 0000000..c7b1134
--- /dev/null
+++ b/src/gui/editors/notation/NotationGroup.h
@@ -0,0 +1,133 @@
+
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
+
+/*
+ Rosegarden
+ A MIDI and audio sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_NOTATIONGROUP_H_
+#define _RG_NOTATIONGROUP_H_
+
+#include "base/Sets.h"
+#include <utility>
+#include "base/Event.h"
+#include "NotationElement.h"
+
+
+
+
+namespace Rosegarden
+{
+
+class Quantizer;
+class NotationStaff;
+class NotationProperties;
+class Key;
+class Clef;
+
+
+/// Several sorts of "Beamed Group"
+
+class NotationGroup : public AbstractSet<NotationElement,
+ NotationElementList>
+{
+public:
+ typedef NotationElementList::iterator NELIterator;
+
+ enum Type { Beamed, Tupled };
+
+ /// Group contents will be sampled from elements surrounding elementInGroup
+ NotationGroup(NotationElementList &nel, NELIterator elementInGroup,
+ const Quantizer *,
+ std::pair<timeT, timeT> barRange,
+ const NotationProperties &properties,
+ const Clef &clef, const Key &key);
+
+ /// Caller intends to call sample() for each item in the group, _in order_
+ NotationGroup(NotationElementList &nel,
+ const Quantizer *,
+ const NotationProperties &properties,
+ const Clef &clef, const Key &key);
+
+ virtual ~NotationGroup();
+
+ Type getGroupType() const { return m_type; }
+
+ /**
+ * Writes the BEAMED, BEAM_ABOVE, and STEM_UP properties into the
+ * notes in the group, as appropriate. Does not require layout x
+ * coordinates to have been set.
+ */
+ void applyStemProperties();
+
+ /**
+ * Writes beam data into each note in the group. Notes' layout x
+ * coordinates must already have been set. Does not require
+ * applyStemProperties to have already been called.
+ */
+ void applyBeam(NotationStaff &);
+
+ /**
+ * Writes tupling line data into each note in the group. Notes'
+ * layout x coordinates must already have been set. Does nothing
+ * if this is not a tupled group.
+ */
+ void applyTuplingLine(NotationStaff &);
+
+ virtual bool contains(const NELIterator &) const;
+
+ virtual bool sample(const NELIterator &i, bool goingForwards);
+
+protected:
+ virtual bool test(const NELIterator &i);
+
+private:
+ struct Beam
+ { // if a beam has a line equation y = mx + c,
+ int gradient; // -- then this is m*100 (i.e. a percentage)
+ int startY; // -- and this is c
+ bool aboveNotes;
+ bool necessary;
+ };
+
+ Beam calculateBeam(NotationStaff &);
+
+ int height(const NELIterator&) const;
+
+ bool haveInternalRest() const;
+
+ //--------------- Data members ---------------------------------
+
+ std::pair<timeT, timeT> m_barRange;
+ const Clef &m_clef;
+ const Key &m_key;
+ int m_weightAbove, m_weightBelow;
+ bool m_userSamples;
+ long m_groupNo;
+ Type m_type;
+
+ const NotationProperties &m_properties;
+};
+
+
+}
+
+#endif
diff --git a/src/gui/editors/notation/NotationHLayout.cpp b/src/gui/editors/notation/NotationHLayout.cpp
new file mode 100644
index 0000000..1b13765
--- /dev/null
+++ b/src/gui/editors/notation/NotationHLayout.cpp
@@ -0,0 +1,2110 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
+
+/*
+ Rosegarden
+ A MIDI and audio sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "NotationHLayout.h"
+#include "misc/Debug.h"
+#include <kapplication.h>
+
+#include "base/Composition.h"
+#include "base/LayoutEngine.h"
+#include "base/NotationTypes.h"
+#include "base/Profiler.h"
+#include "base/NotationQuantizer.h"
+#include "base/RulerScale.h"
+#include "base/Segment.h"
+#include "base/SegmentNotationHelper.h"
+#include "base/Staff.h"
+#include "base/ViewElement.h"
+#include "gui/editors/guitar/Chord.h"
+#include "gui/general/ProgressReporter.h"
+#include "gui/widgets/ProgressDialog.h"
+#include "NotationChord.h"
+#include "NotationElement.h"
+#include "NotationGroup.h"
+#include "NotationProperties.h"
+#include "NotationStaff.h"
+#include "NotePixmapFactory.h"
+#include <kconfig.h>
+#include <qobject.h>
+#include <cmath>
+
+namespace Rosegarden
+{
+
+using namespace BaseProperties;
+
+
+NotationHLayout::NotationHLayout(Composition *c, NotePixmapFactory *npf,
+ const NotationProperties &properties,
+ QObject* parent, const char* name) :
+ ProgressReporter(parent, name),
+ HorizontalLayoutEngine(c),
+ m_totalWidth(0.),
+ m_pageMode(false),
+ m_pageWidth(0.),
+ m_spacing(100),
+ m_proportion(60),
+ m_keySigCancelMode(1),
+ m_npf(npf),
+ m_notationQuantizer(c->getNotationQuantizer()),
+ m_properties(properties),
+ m_timePerProgressIncrement(0),
+ m_staffCount(0)
+{
+ // NOTATION_DEBUG << "NotationHLayout::NotationHLayout()" << endl;
+
+ KConfig *config = kapp->config();
+ config->setGroup("Notation Options");
+ m_keySigCancelMode = config->readNumEntry("keysigcancelmode", 1);
+}
+
+NotationHLayout::~NotationHLayout()
+{
+ // empty
+}
+
+std::vector<int>
+NotationHLayout::getAvailableSpacings()
+{
+ if (m_availableSpacings.size() == 0) {
+ m_availableSpacings.push_back(30);
+ m_availableSpacings.push_back(60);
+ m_availableSpacings.push_back(85);
+ m_availableSpacings.push_back(100);
+ m_availableSpacings.push_back(130);
+ m_availableSpacings.push_back(170);
+ m_availableSpacings.push_back(220);
+ }
+ return m_availableSpacings;
+}
+
+std::vector<int>
+NotationHLayout::getAvailableProportions()
+{
+ if (m_availableProportions.size() == 0) {
+ m_availableProportions.push_back(0);
+ m_availableProportions.push_back(20);
+ m_availableProportions.push_back(40);
+ m_availableProportions.push_back(60);
+ m_availableProportions.push_back(80);
+ m_availableProportions.push_back(100);
+ }
+ return m_availableProportions;
+}
+
+NotationHLayout::BarDataList &
+
+NotationHLayout::getBarData(Staff &staff)
+{
+ BarDataMap::iterator i = m_barData.find(&staff);
+ if (i == m_barData.end()) {
+ m_barData[&staff] = BarDataList();
+ }
+
+ return m_barData[&staff];
+}
+
+const NotationHLayout::BarDataList &
+
+NotationHLayout::getBarData(Staff &staff) const
+{
+ return ((NotationHLayout *)this)->getBarData(staff);
+}
+
+NotationElementList::iterator
+NotationHLayout::getStartOfQuantizedSlice(NotationElementList *notes,
+ timeT t)
+const
+{
+ NotationElementList::iterator i = notes->findTime(t);
+ NotationElementList::iterator j(i);
+
+ while (true) {
+ if (i == notes->begin())
+ return i;
+ --j;
+ if ((*j)->getViewAbsoluteTime() < t)
+ return i;
+ i = j;
+ }
+}
+
+NotePixmapFactory *
+NotationHLayout::getNotePixmapFactory(Staff &staff)
+{
+ NotationStaff *ns = dynamic_cast<NotationStaff *>(&staff);
+ if (ns) return &ns->getNotePixmapFactory(false);
+ else return 0;
+}
+
+NotePixmapFactory *
+NotationHLayout::getGraceNotePixmapFactory(Staff &staff)
+{
+ NotationStaff *ns = dynamic_cast<NotationStaff *>(&staff);
+ if (ns) return &ns->getNotePixmapFactory(true);
+ else return 0;
+}
+
+void
+NotationHLayout::scanStaff(Staff &staff, timeT startTime, timeT endTime)
+{
+ throwIfCancelled();
+ Profiler profiler("NotationHLayout::scanStaff");
+
+ Segment &segment(staff.getSegment());
+ bool isFullScan = (startTime == endTime);
+ int startBarOfStaff = getComposition()->getBarNumber(segment.getStartTime());
+
+ if (isFullScan) {
+ clearBarList(staff);
+ startTime = segment.getStartTime();
+ endTime = segment.getEndMarkerTime();
+ } else {
+ startTime = getComposition()->getBarStartForTime(startTime);
+ endTime = getComposition()->getBarEndForTime(endTime);
+ }
+
+ NotationElementList *notes = staff.getViewElementList();
+ BarDataList &barList(getBarData(staff));
+
+ NotePixmapFactory *npf = getNotePixmapFactory(staff);
+
+ int startBarNo = getComposition()->getBarNumber(startTime);
+ int endBarNo = getComposition()->getBarNumber(endTime);
+ /*
+ if (endBarNo > startBarNo &&
+ getComposition()->getBarStart(endBarNo) == segment.getEndMarkerTime()) {
+ --endBarNo;
+ }
+ */
+ std::string name =
+ segment.getComposition()->
+ getTrackById(segment.getTrack())->getLabel();
+ m_staffNameWidths[&staff] =
+ npf->getNoteBodyWidth() * 2 +
+ npf->getTextWidth(Text(name, Text::StaffName));
+
+ NOTATION_DEBUG << "NotationHLayout::scanStaff: full scan " << isFullScan << ", times " << startTime << "->" << endTime << ", bars " << startBarNo << "->" << endBarNo << ", staff name \"" << segment.getLabel() << "\", width " << m_staffNameWidths[&staff] << endl;
+
+ SegmentNotationHelper helper(segment);
+ if (isFullScan) {
+ helper.setNotationProperties();
+ } else {
+ helper.setNotationProperties(startTime, endTime);
+ }
+
+ ::Rosegarden::Key key = segment.getKeyAtTime(startTime);
+ Clef clef = segment.getClefAtTime(startTime);
+ TimeSignature timeSignature =
+ segment.getComposition()->getTimeSignatureAt(startTime);
+ bool barCorrect = true;
+
+ int ottavaShift = 0;
+ timeT ottavaEnd = segment.getEndMarkerTime();
+
+ if (isFullScan) {
+
+ NOTATION_DEBUG << "full scan: setting haveOttava false" << endl;
+
+ m_haveOttavaSomewhere[&staff] = false;
+
+ } else if (m_haveOttavaSomewhere[&staff]) {
+
+ NOTATION_DEBUG << "not full scan but ottava is listed" << endl;
+
+ Segment::iterator i = segment.findTime(startTime);
+ while (1) {
+ if ((*i)->isa(Indication::EventType)) {
+ try {
+ Indication indication(**i);
+ if (indication.isOttavaType()) {
+ ottavaShift = indication.getOttavaShift();
+ ottavaEnd = (*i)->getAbsoluteTime() +
+ indication.getIndicationDuration();
+ break;
+ }
+ } catch (...) { }
+ }
+ if (i == segment.begin())
+ break;
+ --i;
+ }
+ }
+
+ NOTATION_DEBUG << "ottava shift at start:" << ottavaShift << ", ottavaEnd " << ottavaEnd << endl;
+
+ KConfig *config = kapp->config();
+ config->setGroup("Notation Options");
+
+ int accOctaveMode = config->readNumEntry("accidentaloctavemode", 1);
+ AccidentalTable::OctaveType octaveType =
+ (accOctaveMode == 0 ? AccidentalTable::OctavesIndependent :
+ accOctaveMode == 1 ? AccidentalTable::OctavesCautionary :
+ AccidentalTable::OctavesEquivalent);
+
+ int accBarMode = config->readNumEntry("accidentalbarmode", 0);
+ AccidentalTable::BarResetType barResetType =
+ (accBarMode == 0 ? AccidentalTable::BarResetNone :
+ accBarMode == 1 ? AccidentalTable::BarResetCautionary :
+ AccidentalTable::BarResetExplicit);
+
+ bool showInvisibles = config->readBoolEntry("showinvisibles", true);
+
+ if (barResetType != AccidentalTable::BarResetNone) {
+ //!!! very crude and expensive way of making sure we see the
+ // accidentals from previous bar:
+ if (startBarNo > segment.getComposition()->getBarNumber(segment.getStartTime())) {
+ --startBarNo;
+ }
+ }
+
+ AccidentalTable accTable(key, clef, octaveType, barResetType);
+
+ for (int barNo = startBarNo; barNo <= endBarNo; ++barNo) {
+
+ std::pair<timeT, timeT> barTimes =
+ getComposition()->getBarRange(barNo);
+
+ if (barTimes.first >= segment.getEndMarkerTime()) {
+ // clear data if we have any old stuff
+ BarDataList::iterator i(barList.find(barNo));
+ if (i != barList.end()) {
+ barList.erase(i);
+ }
+ continue; // so as to erase any further bars next time around
+ }
+
+ NotationElementList::iterator from =
+ getStartOfQuantizedSlice(notes, barTimes.first);
+
+ NOTATION_DEBUG << "getStartOfQuantizedSlice returned " <<
+ (from != notes->end() ? (*from)->getViewAbsoluteTime() : -1)
+ << " from " << barTimes.first << endl;
+
+ NotationElementList::iterator to =
+ getStartOfQuantizedSlice(notes, barTimes.second);
+
+ if (barTimes.second >= segment.getEndMarkerTime()) {
+ to = notes->end();
+ }
+
+ bool newTimeSig = false;
+ timeSignature = getComposition()->getTimeSignatureInBar
+ (barNo, newTimeSig);
+ NOTATION_DEBUG << "bar " << barNo << ", startBarOfStaff " << startBarOfStaff
+ << ", newTimeSig " << newTimeSig << endl;
+
+ float fixedWidth = 0.0;
+ if (newTimeSig && !timeSignature.isHidden()) {
+ fixedWidth += npf->getNoteBodyWidth() +
+ npf->getTimeSigWidth(timeSignature);
+ }
+
+ setBarBasicData(staff, barNo, from, barCorrect, timeSignature, newTimeSig);
+ BarDataList::iterator bdli(barList.find(barNo));
+ bdli->second.layoutData.needsLayout = true;
+
+ ChunkList &chunks = bdli->second.chunks;
+ chunks.clear();
+
+ float lyricWidth = 0;
+ int graceCount = 0;
+
+ typedef std::set
+ <long> GroupIdSet;
+ GroupIdSet groupIds;
+
+ NOTATION_DEBUG << "NotationHLayout::scanStaff: bar " << barNo << ", from " << barTimes.first << ", to " << barTimes.second << " (end " << segment.getEndMarkerTime() << "); from is at " << (from == notes->end() ? -1 : (*from)->getViewAbsoluteTime()) << ", to is at " << (to == notes->end() ? -1 : (*to)->getViewAbsoluteTime()) << endl;
+
+ timeT actualBarEnd = barTimes.first;
+
+ accTable.newBar();
+
+ for (NotationElementList::iterator itr = from; itr != to; ++itr) {
+
+ NotationElement *el = static_cast<NotationElement*>((*itr));
+ NOTATION_DEBUG << "element is a " << el->event()->getType() << endl;
+
+ if (ottavaShift != 0) {
+ if (el->event()->getAbsoluteTime() >= ottavaEnd) {
+ NOTATION_DEBUG << "reached end of ottava" << endl;
+ ottavaShift = 0;
+ }
+ }
+
+ bool invisible = false;
+ if (el->event()->get<Bool>(INVISIBLE, invisible) && invisible) {
+ if (!showInvisibles)
+ continue;
+ }
+
+ if (el->event()->has(BEAMED_GROUP_ID)) {
+ NOTATION_DEBUG << "element is beamed" << endl;
+ long groupId = el->event()->get<Int>(BEAMED_GROUP_ID);
+ if (groupIds.find(groupId) == groupIds.end()) {
+ NOTATION_DEBUG << "it's a new beamed group, applying stem properties" << endl;
+ NotationGroup group(*staff.getViewElementList(),
+ itr,
+ m_notationQuantizer,
+ barTimes,
+ m_properties,
+ clef, key);
+ group.applyStemProperties();
+ groupIds.insert(groupId);
+ }
+ }
+
+ if (el->event()->isa(Clef::EventType)) {
+
+ // NOTATION_DEBUG << "Found clef" << endl;
+ chunks.push_back(Chunk(el->event()->getSubOrdering(),
+ getLayoutWidth(*el, npf, key)));
+
+ clef = Clef(*el->event());
+ accTable.newClef(clef);
+
+ } else if (el->event()->isa(::Rosegarden::Key::EventType)) {
+
+ // NOTATION_DEBUG << "Found key" << endl;
+ chunks.push_back(Chunk(el->event()->getSubOrdering(),
+ getLayoutWidth(*el, npf, key)));
+
+ key = ::Rosegarden::Key(*el->event());
+
+ accTable = AccidentalTable
+ (key, clef, octaveType, barResetType);
+
+ } else if (el->event()->isa(Text::EventType)) {
+
+ // the only text events of interest are lyrics, which
+ // contribute to a fixed area following the next chord
+
+ if (el->event()->has(Text::TextTypePropertyName) &&
+ el->event()->get<String>(Text::TextTypePropertyName) ==
+ Text::Lyric) {
+ lyricWidth = std::max
+ (lyricWidth, float(npf->getTextWidth(Text(*el->event()))));
+ NOTATION_DEBUG << "Setting lyric width to " << lyricWidth
+ << " for text " << el->event()->get<String>(Text::TextPropertyName) << endl;
+ }
+ chunks.push_back(Chunk(el->event()->getSubOrdering(), 0));
+
+ } else if (el->isNote()) {
+
+ NotePixmapFactory *cnpf = npf;
+ if (el->isGrace()) cnpf = getGraceNotePixmapFactory(staff);
+
+ scanChord(notes, itr, clef, key, accTable,
+ lyricWidth, chunks, cnpf, ottavaShift, to);
+
+ } else if (el->isRest()) {
+
+ chunks.push_back(Chunk(el->getViewDuration(),
+ el->event()->getSubOrdering(),
+ 0,
+ getLayoutWidth(*el, npf, key)));
+
+ } else if (el->event()->isa(Indication::EventType)) {
+
+ // NOTATION_DEBUG << "Found indication" << endl;
+
+ chunks.push_back(Chunk(el->event()->getSubOrdering(), 0));
+
+ try {
+ Indication indication(*el->event());
+ if (indication.isOttavaType()) {
+ ottavaShift = indication.getOttavaShift();
+ ottavaEnd = el->event()->getAbsoluteTime() +
+ indication.getIndicationDuration();
+ m_haveOttavaSomewhere[&staff] = true;
+ }
+ } catch (...) {
+ NOTATION_DEBUG << "Bad indication!" << endl;
+ }
+
+ } else {
+
+// NOTATION_DEBUG << "Found something I don't know about (type is " << el->event()->getType() << ")" << endl;
+ chunks.push_back(Chunk(el->event()->getSubOrdering(),
+ getLayoutWidth(*el, npf, key)));
+ }
+
+ actualBarEnd = el->getViewAbsoluteTime() + el->getViewDuration();
+ }
+
+ if (actualBarEnd == barTimes.first)
+ actualBarEnd = barTimes.second;
+ barCorrect = (actualBarEnd == barTimes.second);
+
+ setBarSizeData(staff, barNo, fixedWidth,
+ actualBarEnd - barTimes.first);
+
+ if ((endTime > startTime) && (barNo % 20 == 0)) {
+ emit setProgress((barTimes.second - startTime) * 95 /
+ (endTime - startTime));
+ ProgressDialog::processEvents();
+ }
+
+ throwIfCancelled();
+ }
+ /*
+ BarDataList::iterator ei(barList.end());
+ while (ei != barList.begin() && (--ei)->first > endBarNo) {
+ barList.erase(ei);
+ ei = barList.end();
+ }
+ */
+}
+
+void
+NotationHLayout::clearBarList(Staff &staff)
+{
+ BarDataList &bdl = m_barData[&staff];
+ bdl.clear();
+}
+
+void
+NotationHLayout::setBarBasicData(Staff &staff,
+ int barNo,
+ NotationElementList::iterator start,
+ bool correct,
+ TimeSignature timeSig,
+ bool newTimeSig)
+{
+ // NOTATION_DEBUG << "setBarBasicData for " << barNo << endl;
+
+ BarDataList &bdl(m_barData[&staff]);
+
+ BarDataList::iterator i(bdl.find(barNo));
+ if (i == bdl.end()) {
+ NotationElementList::iterator endi = staff.getViewElementList()->end();
+ bdl.insert(BarDataPair(barNo, BarData(endi, true,
+ TimeSignature(), false)));
+ i = bdl.find(barNo);
+ }
+
+ i->second.basicData.start = start;
+ i->second.basicData.correct = correct;
+ i->second.basicData.timeSignature = timeSig;
+ i->second.basicData.newTimeSig = newTimeSig;
+}
+
+void
+NotationHLayout::setBarSizeData(Staff &staff,
+ int barNo,
+ float fixedWidth,
+ timeT actualDuration)
+{
+ // NOTATION_DEBUG << "setBarSizeData for " << barNo << endl;
+
+ BarDataList &bdl(m_barData[&staff]);
+
+ BarDataList::iterator i(bdl.find(barNo));
+ if (i == bdl.end()) {
+ NotationElementList::iterator endi = staff.getViewElementList()->end();
+ bdl.insert(BarDataPair(barNo, BarData(endi, true,
+ TimeSignature(), false)));
+ i = bdl.find(barNo);
+ }
+
+ i->second.sizeData.actualDuration = actualDuration;
+ i->second.sizeData.idealWidth = 0.0;
+ i->second.sizeData.reconciledWidth = 0.0;
+ i->second.sizeData.clefKeyWidth = 0;
+ i->second.sizeData.fixedWidth = fixedWidth;
+}
+
+void
+NotationHLayout::scanChord(NotationElementList *notes,
+ NotationElementList::iterator &itr,
+ const Clef &clef,
+ const ::Rosegarden::Key &key,
+ AccidentalTable &accTable,
+ float &lyricWidth,
+ ChunkList &chunks,
+ NotePixmapFactory *npf,
+ int ottavaShift,
+ NotationElementList::iterator &to)
+{
+ NotationChord chord(*notes, itr, m_notationQuantizer, m_properties);
+ Accidental someAccidental = Accidentals::NoAccidental;
+ bool someCautionary = false;
+ bool barEndsInChord = false;
+ bool grace = false;
+
+// std::cerr << "NotationHLayout::scanChord: "
+// << chord.size() << "-voice chord at "
+// << (*itr)->event()->getAbsoluteTime()
+// << " unquantized, "
+// << (*itr)->getViewAbsoluteTime()
+// << " quantized" << std::endl;
+
+// NOTATION_DEBUG << "Contents:" << endl;
+
+ /*
+ for (NotationElementList::iterator i = chord.getInitialElement();
+ i != notes->end(); ++i) {
+ (*i)->event()->dump(std::cerr);
+ if (i == chord.getFinalElement()) break;
+ }
+ */
+ // We don't need to get the chord's notes in pitch order here,
+ // but we do need to ensure we see any random non-note events
+ // that may crop up in the middle of it.
+
+ for (NotationElementList::iterator i = chord.getInitialElement();
+ i != notes->end(); ++i) {
+
+ NotationElement *el = static_cast<NotationElement*>(*i);
+ if (el->isRest()) {
+ el->event()->setMaybe<Bool>(m_properties.REST_TOO_SHORT, true);
+ if (i == chord.getFinalElement())
+ break;
+ continue;
+ }
+
+ if (el->isGrace()) {
+ grace = true;
+ }
+
+ long pitch = 64;
+ if (!el->event()->get<Int>(PITCH, pitch)) {
+ NOTATION_DEBUG <<
+ "WARNING: NotationHLayout::scanChord: couldn't get pitch for element, using default pitch of " << pitch << endl;
+ }
+
+ Accidental explicitAccidental = Accidentals::NoAccidental;
+ (void)el->event()->get<String>(ACCIDENTAL, explicitAccidental);
+
+ Pitch p(pitch, explicitAccidental);
+ int h = p.getHeightOnStaff(clef, key);
+ Accidental acc = p.getDisplayAccidental(key);
+
+ h -= 7 * ottavaShift;
+
+ el->event()->setMaybe<Int>(NotationProperties::OTTAVA_SHIFT, ottavaShift);
+ el->event()->setMaybe<Int>(NotationProperties::HEIGHT_ON_STAFF, h);
+ el->event()->setMaybe<String>(m_properties.CALCULATED_ACCIDENTAL, acc);
+
+ // update display acc for note according to the accTable
+ // (accidentals in force when the last chord ended) and tell
+ // accTable about accidentals from this note.
+
+ bool cautionary = false;
+ if (el->event()->has(m_properties.USE_CAUTIONARY_ACCIDENTAL)) {
+ cautionary = el->event()->get<Bool>(m_properties.USE_CAUTIONARY_ACCIDENTAL);
+ }
+ Accidental dacc = accTable.processDisplayAccidental(acc, h, cautionary);
+ el->event()->setMaybe<String>(m_properties.DISPLAY_ACCIDENTAL, dacc);
+ el->event()->setMaybe<Bool>(m_properties.DISPLAY_ACCIDENTAL_IS_CAUTIONARY,
+ cautionary);
+ if (cautionary) {
+ someCautionary = true;
+ }
+
+ if (someAccidental == Accidentals::NoAccidental)
+ someAccidental = dacc;
+
+ if (i == to)
+ barEndsInChord = true;
+
+ if (i == chord.getFinalElement())
+ break;
+ }
+
+ // tell accTable the chord has ended, so to bring its accidentals
+ // into force for future chords
+ accTable.update();
+
+ chord.applyAccidentalShiftProperties();
+
+ float extraWidth = 0;
+
+ if (someAccidental != Accidentals::NoAccidental) {
+ bool extraShift = false;
+ int shift = chord.getMaxAccidentalShift(extraShift);
+ int e = npf->getAccidentalWidth(someAccidental, shift, extraShift);
+ if (someAccidental != Accidentals::Sharp) {
+ e = std::max(e, npf->getAccidentalWidth(Accidentals::Sharp, shift, extraShift));
+ }
+ if (someCautionary) {
+ e += npf->getNoteBodyWidth();
+ }
+ extraWidth += e;
+ }
+
+ float layoutExtra = 0;
+ if (chord.hasNoteHeadShifted()) {
+ if (chord.hasStemUp()) {
+ layoutExtra += npf->getNoteBodyWidth();
+ } else {
+ extraWidth = std::max(extraWidth, float(npf->getNoteBodyWidth()));
+ }
+ }
+/*!!!
+ if (grace) {
+ std::cerr << "Grace note: subordering " << chord.getSubOrdering() << std::endl;
+ chunks.push_back(Chunk(-10 + graceCount,
+ extraWidth + npf->getNoteBodyWidth()));
+ if (graceCount < 9) ++graceCount;
+ return;
+ } else {
+ std::cerr << "Non-grace note (grace count was " << graceCount << ")" << std::endl;
+ graceCount = 0;
+ }
+*/
+ NotationElementList::iterator myLongest = chord.getLongestElement();
+ if (myLongest == notes->end()) {
+ NOTATION_DEBUG << "WARNING: NotationHLayout::scanChord: No longest element in chord!" << endl;
+ }
+
+ timeT d = (*myLongest)->getViewDuration();
+
+ NOTATION_DEBUG << "Lyric width is " << lyricWidth << endl;
+
+ if (grace) {
+ chunks.push_back(Chunk(d, chord.getSubOrdering(),
+ extraWidth + layoutExtra
+ + getLayoutWidth(**myLongest, npf, key)
+ - npf->getNoteBodyWidth(), // tighten up
+ 0));
+ } else {
+ chunks.push_back(Chunk(d, 0, extraWidth,
+ std::max(layoutExtra +
+ getLayoutWidth(**myLongest, npf, key),
+ lyricWidth)));
+ lyricWidth = 0;
+ }
+
+ itr = chord.getFinalElement();
+ if (barEndsInChord) {
+ to = itr;
+ ++to;
+ }
+}
+
+struct ChunkLocation {
+ timeT time;
+ short subordering;
+ ChunkLocation(timeT t, short s) : time(t), subordering(s) { }
+};
+
+bool operator<(const ChunkLocation &l0, const ChunkLocation &l1) {
+ return ((l0.time < l1.time) ||
+ ((l0.time == l1.time) && (l0.subordering < l1.subordering)));
+}
+
+
+
+void
+NotationHLayout::preSquishBar(int barNo)
+{
+ typedef std::vector<Chunk *> ChunkRefList;
+ typedef std::map<ChunkLocation, ChunkRefList> ColumnMap;
+ static ColumnMap columns;
+ bool haveSomething = false;
+
+ columns.clear();
+
+ for (BarDataMap::iterator mi = m_barData.begin();
+ mi != m_barData.end(); ++mi) {
+
+ BarDataList &bdl = mi->second;
+ BarDataList::iterator bdli = bdl.find(barNo);
+
+ if (bdli != bdl.end()) {
+
+ haveSomething = true;
+ ChunkList &cl(bdli->second.chunks);
+ timeT aggregateTime = 0;
+
+ for (ChunkList::iterator cli = cl.begin(); cli != cl.end(); ++cli) {
+
+ // Subordering is typically zero for notes, positive
+ // for rests and negative for other stuff. We want to
+ // handle notes and rests together, but not the others.
+
+ int subordering = cli->subordering;
+ if (subordering > 0)
+ subordering = 0;
+
+ columns[ChunkLocation(aggregateTime, subordering)].push_back(&(*cli));
+
+ aggregateTime += cli->duration;
+ }
+ }
+ }
+
+ if (!haveSomething)
+ return ;
+
+ // now modify chunks in-place
+
+ // What we want to do here is idle along the whole set of chunk
+ // lists, inspecting all the chunks that occur at each moment in
+ // turn and choosing a "rate" from the "slowest" of these
+ // (i.e. most space per time)
+
+ float x = 0.0;
+ timeT prevTime = 0;
+ double prevRate = 0.0;
+ float maxStretchy = 0.0;
+
+ NOTATION_DEBUG << "NotationHLayout::preSquishBar(" << barNo << "): have "
+ << columns.size() << " columns" << endl;
+
+ for (ColumnMap::iterator i = columns.begin(); i != columns.end(); ++i) {
+
+ timeT time = i->first.time;
+ ChunkRefList &list = i->second;
+
+ NOTATION_DEBUG << "NotationHLayout::preSquishBar: "
+ << "column at " << time << " : " << i->first.subordering << endl;
+
+
+ double minRate = -1.0;
+ float totalFixed = 0.0;
+ maxStretchy = 0.0;
+
+ for (ChunkRefList::iterator j = list.begin(); j != list.end(); ++j) {
+ if ((*j)->stretchy > 0.0) {
+ double rate = (*j)->duration / (*j)->stretchy; // time per px
+ NOTATION_DEBUG << "NotationHLayout::preSquishBar: rate " << rate << endl;
+ if (minRate < 0.0 || rate < minRate)
+ minRate = rate;
+ } else {
+ NOTATION_DEBUG << "NotationHLayout::preSquishBar: not stretchy" << endl;
+ }
+
+ maxStretchy = std::max(maxStretchy, (*j)->stretchy);
+ totalFixed = std::max(totalFixed, (*j)->fixed);
+ }
+
+ NOTATION_DEBUG << "NotationHLayout::preSquishBar: minRate " << minRate << ", maxStretchy " << maxStretchy << ", totalFixed " << totalFixed << endl;
+
+ // we have the rate from this point, but we want to assign
+ // these elements an x coord based on the rate and distance
+ // from the previous point, plus fixed space for this point
+ // if it's a note (otherwise fixed space goes afterwards)
+
+ if (i->first.subordering == 0)
+ x += totalFixed;
+ if (prevRate > 0.0)
+ x += (time - prevTime) / prevRate;
+
+ for (ChunkRefList::iterator j = list.begin(); j != list.end(); ++j) {
+ NOTATION_DEBUG << "Setting x for time " << time << " to " << x << " in chunk at " << *j << endl;
+ (*j)->x = x;
+ }
+
+ if (i->first.subordering != 0)
+ x += totalFixed;
+
+ prevTime = time;
+ prevRate = minRate;
+ }
+
+ x += maxStretchy;
+
+ for (BarDataMap::iterator mi = m_barData.begin();
+ mi != m_barData.end(); ++mi) {
+
+ BarDataList &bdl = mi->second;
+ BarDataList::iterator bdli = bdl.find(barNo);
+ if (bdli != bdl.end()) {
+
+ bdli->second.sizeData.idealWidth =
+ bdli->second.sizeData.fixedWidth + x;
+
+ if (!bdli->second.basicData.timeSignature.hasHiddenBars()) {
+ bdli->second.sizeData.idealWidth += getBarMargin();
+ } else if (bdli->second.basicData.newTimeSig) {
+ bdli->second.sizeData.idealWidth += getPostBarMargin();
+ }
+
+ bdli->second.sizeData.reconciledWidth =
+ bdli->second.sizeData.idealWidth;
+
+ bdli->second.layoutData.needsLayout = true;
+ }
+ }
+}
+
+Staff *
+NotationHLayout::getStaffWithWidestBar(int barNo)
+{
+ float maxWidth = -1;
+ Staff *widest = 0;
+
+ for (BarDataMap::iterator mi = m_barData.begin();
+ mi != m_barData.end(); ++mi) {
+
+ BarDataList &list = mi->second;
+ BarDataList::iterator li = list.find(barNo);
+ if (li != list.end()) {
+
+ NOTATION_DEBUG << "getStaffWithWidestBar: idealWidth is " << li->second.sizeData.idealWidth << endl;
+
+ if (li->second.sizeData.idealWidth == 0.0) {
+ NOTATION_DEBUG << "getStaffWithWidestBar(" << barNo << "): found idealWidth of zero, presquishing" << endl;
+ preSquishBar(barNo);
+ }
+
+ if (li->second.sizeData.idealWidth > maxWidth) {
+ maxWidth = li->second.sizeData.idealWidth;
+ widest = mi->first;
+ }
+ }
+ }
+
+ return widest;
+}
+
+int
+NotationHLayout::getMaxRepeatedClefAndKeyWidth(int barNo)
+{
+ int max = 0;
+
+ timeT barStart = 0;
+
+ for (BarDataMap::iterator mi = m_barData.begin();
+ mi != m_barData.end(); ++mi) {
+
+ Staff *staff = mi->first;
+ if (mi == m_barData.begin()) {
+ barStart = staff->getSegment().getComposition()->getBarStart(barNo);
+ }
+
+ timeT t;
+ int w = 0;
+
+ Clef clef = staff->getSegment().getClefAtTime(barStart, t);
+ if (t < barStart)
+ w += m_npf->getClefWidth(clef);
+
+ ::Rosegarden::Key key = staff->getSegment().getKeyAtTime(barStart, t);
+ if (t < barStart)
+ w += m_npf->getKeyWidth(key);
+
+ if (w > max)
+ max = w;
+ }
+
+ NOTATION_DEBUG << "getMaxRepeatedClefAndKeyWidth(" << barNo << "): " << max
+ << endl;
+
+ if (max > 0)
+ return max + getFixedItemSpacing() * 2;
+ else
+ return 0;
+}
+
+void
+NotationHLayout::reconcileBarsLinear()
+{
+ Profiler profiler("NotationHLayout::reconcileBarsLinear");
+
+ // Ensure that concurrent bars on all staffs have the same width,
+ // which for now we make the maximum width required for this bar
+ // on any staff. These days getStaffWithWidestBar actually does
+ // most of the work in its call to preSquishBar, but this function
+ // still sets the bar line positions etc.
+
+ int barNo = getFirstVisibleBar();
+
+ m_totalWidth = 0.0;
+ for (StaffIntMap::iterator i = m_staffNameWidths.begin();
+ i != m_staffNameWidths.end(); ++i) {
+ if (i->second > m_totalWidth)
+ m_totalWidth = double(i->second);
+ }
+
+ for (;;) {
+
+ Staff *widest = getStaffWithWidestBar(barNo);
+
+ if (!widest) {
+ // have we reached the end of the piece?
+ if (barNo >= getLastVisibleBar()) { // yes
+ break;
+ } else {
+ m_totalWidth += m_spacing / 3;
+ NOTATION_DEBUG << "Setting bar position for degenerate bar "
+ << barNo << " to " << m_totalWidth << endl;
+
+ m_barPositions[barNo] = m_totalWidth;
+ ++barNo;
+ continue;
+ }
+ }
+
+ float maxWidth = m_barData[widest].find(barNo)->second.sizeData.idealWidth;
+ if (m_pageWidth > 0.1 && maxWidth > m_pageWidth) {
+ maxWidth = m_pageWidth;
+ }
+
+ NOTATION_DEBUG << "Setting bar position for bar " << barNo
+ << " to " << m_totalWidth << endl;
+
+ m_barPositions[barNo] = m_totalWidth;
+ m_totalWidth += maxWidth;
+
+ // Now apply width to this bar on all staffs
+
+ for (BarDataMap::iterator i = m_barData.begin();
+ i != m_barData.end(); ++i) {
+
+ BarDataList &list = i->second;
+ BarDataList::iterator bdli = list.find(barNo);
+ if (bdli != list.end()) {
+
+ BarData::SizeData &bd(bdli->second.sizeData);
+
+ NOTATION_DEBUG << "Changing width from " << bd.reconciledWidth << " to " << maxWidth << endl;
+
+ double diff = maxWidth - bd.reconciledWidth;
+ if (diff < -0.1 || diff > 0.1) {
+ NOTATION_DEBUG << "(So needsLayout becomes true)" << endl;
+ bdli->second.layoutData.needsLayout = true;
+ }
+ bd.reconciledWidth = maxWidth;
+ }
+ }
+
+ ++barNo;
+ }
+
+ NOTATION_DEBUG << "Setting bar position for bar " << barNo
+ << " to " << m_totalWidth << endl;
+
+ m_barPositions[barNo] = m_totalWidth;
+}
+
+void
+NotationHLayout::reconcileBarsPage()
+{
+ Profiler profiler("NotationHLayout::reconcileBarsPage");
+
+ int barNo = getFirstVisibleBar();
+ int barNoThisRow = 0;
+
+ // pair of the recommended number of bars with those bars'
+ // original total width, for each row
+ std::vector<std::pair<int, double> > rowData;
+
+ double stretchFactor = 10.0;
+ double maxStaffNameWidth = 0.0;
+
+ for (StaffIntMap::iterator i = m_staffNameWidths.begin();
+ i != m_staffNameWidths.end(); ++i) {
+ if (i->second > maxStaffNameWidth) {
+ maxStaffNameWidth = double(i->second);
+ }
+ }
+
+ double pageWidthSoFar = maxStaffNameWidth;
+ m_totalWidth = maxStaffNameWidth + getPreBarMargin();
+
+ NOTATION_DEBUG << "NotationHLayout::reconcileBarsPage: pageWidthSoFar is " << pageWidthSoFar << endl;
+
+ for (;;) {
+
+ Staff *widest = getStaffWithWidestBar(barNo);
+ double maxWidth = m_spacing / 3;
+
+ if (!widest) {
+ // have we reached the end of the piece?
+ if (barNo >= getLastVisibleBar())
+ break; // yes
+ } else {
+ maxWidth =
+ m_barData[widest].find(barNo)->second.sizeData.idealWidth;
+ }
+
+ // Work on the assumption that this bar is the last in the
+ // row. How would that make things look?
+
+ double nextPageWidth = pageWidthSoFar + maxWidth;
+ double nextStretchFactor = m_pageWidth / nextPageWidth;
+
+ NOTATION_DEBUG << "barNo is " << barNo << ", maxWidth " << maxWidth << ", nextPageWidth " << nextPageWidth << ", nextStretchFactor " << nextStretchFactor << ", m_pageWidth " << m_pageWidth << endl;
+
+ // We have to have at least one bar per row
+
+ bool tooFar = false;
+
+ if (barNoThisRow >= 1) {
+
+ // If this stretch factor is "worse" than the previous
+ // one, we've come too far and have too many bars
+
+ if (fabs(1.0 - nextStretchFactor) > fabs(1.0 - stretchFactor)) {
+ tooFar = true;
+ }
+
+ // If the next stretch factor is less than 1 and would
+ // make this bar on any of the staffs narrower than it can
+ // afford to be, then we've got too many bars
+ //!!! rework this -- we have no concept of "too narrow"
+ // any more but we can declare we don't want it any
+ // narrower than e.g. 90% or something based on the spacing
+ /*!!!
+ if (!tooFar && (nextStretchFactor < 1.0)) {
+
+ for (BarDataMap::iterator i = m_barData.begin();
+ i != m_barData.end(); ++i) {
+
+ BarDataList &list = i->second;
+ BarDataList::iterator bdli = list.find(barNo);
+ if (bdli != list.end()) {
+ BarData::SizeData &bd(bdli->second.sizeData);
+ if ((nextStretchFactor * bd.idealWidth) <
+ (double)(bd.fixedWidth + bd.baseWidth)) {
+ tooFar = true;
+ break;
+ }
+ }
+ }
+ }
+ */
+ }
+
+ if (tooFar) {
+ rowData.push_back(std::pair<int, double>(barNoThisRow,
+ pageWidthSoFar));
+ barNoThisRow = 1;
+
+ // When we start a new row, we always need to allow for the
+ // repeated clef and key at the start of it.
+ int maxClefKeyWidth = getMaxRepeatedClefAndKeyWidth(barNo);
+
+ for (BarDataMap::iterator i = m_barData.begin();
+ i != m_barData.end(); ++i) {
+
+ BarDataList &list = i->second;
+ BarDataList::iterator bdli = list.find(barNo);
+
+ if (bdli != list.end()) {
+ bdli->second.sizeData.clefKeyWidth = maxClefKeyWidth;
+ }
+ }
+
+ pageWidthSoFar = maxWidth + maxClefKeyWidth;
+ stretchFactor = m_pageWidth / pageWidthSoFar;
+ } else {
+ ++barNoThisRow;
+ pageWidthSoFar = nextPageWidth;
+ stretchFactor = nextStretchFactor;
+ }
+
+ ++barNo;
+ }
+
+ if (barNoThisRow > 0) {
+ rowData.push_back(std::pair<int, double>(barNoThisRow,
+ pageWidthSoFar));
+ }
+
+ // Now we need to actually apply the widths
+
+ barNo = getFirstVisibleBar();
+
+ for (unsigned int row = 0; row < rowData.size(); ++row) {
+
+ barNoThisRow = barNo;
+ int finalBarThisRow = barNo + rowData[row].first - 1;
+
+ pageWidthSoFar = (row > 0 ? 0 : maxStaffNameWidth + getPreBarMargin());
+ stretchFactor = m_pageWidth / rowData[row].second;
+
+ for (; barNoThisRow <= finalBarThisRow; ++barNoThisRow, ++barNo) {
+
+ bool finalRow = (row == rowData.size() - 1);
+
+ Staff *widest = getStaffWithWidestBar(barNo);
+ if (finalRow && (stretchFactor > 1.0))
+ stretchFactor = 1.0;
+ double maxWidth = 0.0;
+
+ if (!widest) {
+ // have we reached the end of the piece? (shouldn't happen)
+ if (barNo >= getLastVisibleBar())
+ break; // yes
+ else
+ maxWidth = stretchFactor * (m_spacing / 3);
+ } else {
+ BarData &bd = m_barData[widest].find(barNo)->second;
+ maxWidth = (stretchFactor * bd.sizeData.idealWidth) +
+ bd.sizeData.clefKeyWidth;
+ NOTATION_DEBUG << "setting maxWidth to " << (stretchFactor * bd.sizeData.idealWidth) << " + " << bd.sizeData.clefKeyWidth << " = " << maxWidth << endl;
+ }
+
+ if (barNoThisRow == finalBarThisRow) {
+ if (!finalRow ||
+ (maxWidth > (m_pageWidth - pageWidthSoFar))) {
+ maxWidth = m_pageWidth - pageWidthSoFar;
+ NOTATION_DEBUG << "reset maxWidth to " << m_pageWidth << " - " << pageWidthSoFar << " = " << maxWidth << endl;
+ }
+ }
+
+ m_barPositions[barNo] = m_totalWidth;
+ m_totalWidth += maxWidth;
+
+ for (BarDataMap::iterator i = m_barData.begin();
+ i != m_barData.end(); ++i) {
+
+ BarDataList &list = i->second;
+ BarDataList::iterator bdli = list.find(barNo);
+ if (bdli != list.end()) {
+ BarData::SizeData &bd(bdli->second.sizeData);
+ double diff = maxWidth - bd.reconciledWidth;
+ if (diff < -0.1 || diff > 0.1) {
+ bdli->second.layoutData.needsLayout = true;
+ }
+ bd.reconciledWidth = maxWidth;
+ }
+ }
+
+ pageWidthSoFar += maxWidth;
+ }
+ }
+
+ m_barPositions[barNo] = m_totalWidth;
+}
+
+void
+NotationHLayout::finishLayout(timeT startTime, timeT endTime)
+{
+ Profiler profiler("NotationHLayout::finishLayout");
+ m_barPositions.clear();
+
+ bool isFullLayout = (startTime == endTime);
+ if (m_pageMode && (m_pageWidth > 0.1))
+ reconcileBarsPage();
+ else
+ reconcileBarsLinear();
+
+ int staffNo = 0;
+
+ for (BarDataMap::iterator i(m_barData.begin());
+ i != m_barData.end(); ++i) {
+
+ emit setProgress(100 * staffNo / m_barData.size());
+ ProgressDialog::processEvents();
+
+ throwIfCancelled();
+
+ timeT timeCovered = endTime - startTime;
+
+ if (isFullLayout) {
+ NotationElementList *notes = i->first->getViewElementList();
+ if (notes->begin() != notes->end()) {
+ NotationElementList::iterator j(notes->end());
+ timeCovered =
+ (*--j)->getViewAbsoluteTime() -
+ (*notes->begin())->getViewAbsoluteTime();
+ }
+ }
+
+ m_timePerProgressIncrement = timeCovered / (100 / m_barData.size());
+
+ layout(i, startTime, endTime);
+ ++staffNo;
+ }
+}
+
+void
+NotationHLayout::layout(BarDataMap::iterator i, timeT startTime, timeT endTime)
+{
+ Profiler profiler("NotationHLayout::layout");
+
+ Staff &staff = *(i->first);
+ NotationElementList *notes = staff.getViewElementList();
+ BarDataList &barList(getBarData(staff));
+ NotationStaff &notationStaff = dynamic_cast<NotationStaff &>(staff);
+
+ bool isFullLayout = (startTime == endTime);
+
+ // these two are for partial layouts:
+ // bool haveSimpleOffset = false;
+ // double simpleOffset = 0;
+
+ NOTATION_DEBUG << "NotationHLayout::layout: full layout " << isFullLayout << ", times " << startTime << "->" << endTime << endl;
+
+ double x = 0, barX = 0;
+ TieMap tieMap;
+
+ timeT lastIncrement =
+ (isFullLayout && (notes->begin() != notes->end())) ?
+ (*notes->begin())->getViewAbsoluteTime() : startTime;
+
+ ::Rosegarden::Key key = notationStaff.getSegment().getKeyAtTime(lastIncrement);
+ Clef clef = notationStaff.getSegment().getClefAtTime(lastIncrement);
+ TimeSignature timeSignature;
+
+ int startBar = getComposition()->getBarNumber(startTime);
+
+ KConfig *config = kapp->config();
+ config->setGroup("Notation Options");
+ bool showInvisibles = config->readBoolEntry("showinvisibles", true);
+
+ for (BarPositionList::iterator bpi = m_barPositions.begin();
+ bpi != m_barPositions.end(); ++bpi) {
+
+ int barNo = bpi->first;
+ if (!isFullLayout && barNo < startBar)
+ continue;
+
+ NOTATION_DEBUG << "NotationHLayout::looking for bar "
+ << bpi->first << endl;
+ BarDataList::iterator bdi = barList.find(barNo);
+ if (bdi == barList.end())
+ continue;
+ barX = bpi->second;
+
+ NotationElementList::iterator from = bdi->second.basicData.start;
+ NotationElementList::iterator to;
+
+ NOTATION_DEBUG << "NotationHLayout::layout(): starting bar " << barNo << ", x = " << barX << ", width = " << bdi->second.sizeData.idealWidth << ", time = " << (from == notes->end() ? -1 : (*from)->getViewAbsoluteTime()) << endl;
+
+ BarDataList::iterator nbdi(bdi);
+ if (++nbdi == barList.end()) {
+ to = notes->end();
+ } else {
+ to = nbdi->second.basicData.start;
+ }
+
+ if (from == notes->end()) {
+ NOTATION_DEBUG << "Start is end" << endl;
+ }
+ if (from == to) {
+ NOTATION_DEBUG << "Start is to" << endl;
+ }
+
+ if (!bdi->second.layoutData.needsLayout) {
+
+ double offset = barX - bdi->second.layoutData.x;
+
+ NOTATION_DEBUG << "NotationHLayout::layout(): bar " << barNo << " has needsLayout false and offset of " << offset << endl;
+
+ if (offset > -0.1 && offset < 0.1) {
+ NOTATION_DEBUG << "NotationHLayout::layout(): no offset, ignoring" << endl;
+ continue;
+ }
+
+ bdi->second.layoutData.x += offset;
+
+ if (bdi->second.basicData.newTimeSig)
+ bdi->second.layoutData.timeSigX += (int)offset;
+
+ for (NotationElementList::iterator it = from;
+ it != to && it != notes->end(); ++it) {
+
+ NotationElement* nel = static_cast<NotationElement*>(*it);
+ NOTATION_DEBUG << "NotationHLayout::layout(): shifting element's x to " << ((*it)->getLayoutX() + offset) << " (was " << (*it)->getLayoutX() << ")" << endl;
+ nel->setLayoutX((*it)->getLayoutX() + offset);
+ double airX, airWidth;
+ nel->getLayoutAirspace(airX, airWidth);
+ nel->setLayoutAirspace(airX + offset, airWidth);
+ }
+
+ continue;
+ }
+
+ bdi->second.layoutData.x = barX;
+ // x = barX + getPostBarMargin();
+
+ bool timeSigToPlace = false;
+ if (bdi->second.basicData.newTimeSig) {
+ timeSignature = bdi->second.basicData.timeSignature;
+ timeSigToPlace = !bdi->second.basicData.timeSignature.isHidden();
+ }
+ if (timeSigToPlace) {
+ NOTATION_DEBUG << "NotationHLayout::layout(): there's a time sig in this bar" << endl;
+ }
+
+ bool repeatClefAndKey = false;
+ if (bdi->second.sizeData.clefKeyWidth > 0) {
+ repeatClefAndKey = true;
+ }
+ if (repeatClefAndKey) {
+ NOTATION_DEBUG << "NotationHLayout::layout(): need to repeat clef & key in this bar" << endl;
+ }
+
+ double barInset = notationStaff.getBarInset(barNo, repeatClefAndKey);
+
+ NotationElement *lastDynamicText = 0;
+ int fretboardCount = 0;
+ int count = 0;
+
+ double offset = 0.0;
+ double reconciledWidth = bdi->second.sizeData.reconciledWidth;
+
+ if (repeatClefAndKey) {
+ offset = bdi->second.sizeData.clefKeyWidth;
+ reconciledWidth -= offset;
+ }
+
+ if (bdi->second.basicData.newTimeSig ||
+ !bdi->second.basicData.timeSignature.hasHiddenBars()) {
+ offset += getPostBarMargin();
+ }
+
+ ChunkList &chunks = bdi->second.chunks;
+ ChunkList::iterator chunkitr = chunks.begin();
+ double reconcileRatio = 1.0;
+ if (bdi->second.sizeData.idealWidth > 0.0) {
+ reconcileRatio = reconciledWidth / bdi->second.sizeData.idealWidth;
+ }
+
+ NOTATION_DEBUG << "have " << chunks.size() << " chunks, reconciledWidth " << bdi->second.sizeData.reconciledWidth << ", idealWidth " << bdi->second.sizeData.idealWidth << ", ratio " << reconcileRatio << endl;
+
+ double delta = 0;
+ float sigx = 0.f;
+
+ for (NotationElementList::iterator it = from; it != to; ++it) {
+
+ NotationElement *el = static_cast<NotationElement*>(*it);
+ delta = 0;
+ float fixed = 0;
+
+ if (el->event()->isa(Note::EventType)) {
+ long pitch = 0;
+ el->event()->get<Int>(PITCH, pitch);
+ NOTATION_DEBUG << "element is a " << el->event()->getType() << " (pitch " << pitch << ")" << endl;
+ } else {
+ NOTATION_DEBUG << "element is a " << el->event()->getType() << endl;
+ }
+
+ bool invisible = false;
+ if (el->event()->get<Bool>(INVISIBLE, invisible) && invisible) {
+ if (!showInvisibles)
+ continue;
+ }
+
+// float sigx = 0;
+
+ if (chunkitr != chunks.end()) {
+ NOTATION_DEBUG << "new chunk: addr " << &(*chunkitr) << " duration=" << (*chunkitr).duration << " subordering=" << (*chunkitr).subordering << " fixed=" << (*chunkitr).fixed << " stretchy=" << (*chunkitr).stretchy << " x=" << (*chunkitr).x << endl;
+ x = barX + offset + reconcileRatio * (*chunkitr).x;
+ fixed = (*chunkitr).fixed;
+// sigx = barX + offset - fixed;
+// sigx = x - fixed;
+ NOTATION_DEBUG << "adjusted x is " << x << ", fixed is " << fixed << endl;
+
+ if (timeSigToPlace) {
+ if (el->event()->isa(Clef::EventType) ||
+ el->event()->isa(Rosegarden::Key::EventType)) {
+ sigx = x + (*chunkitr).fixed + (*chunkitr).stretchy;
+ }
+ }
+
+ ChunkList::iterator chunkscooter(chunkitr);
+ if (++chunkscooter != chunks.end()) {
+ delta = (*chunkscooter).x - (*chunkitr).x;
+ } else {
+ delta = reconciledWidth -
+ bdi->second.sizeData.fixedWidth - (*chunkitr).x;
+ }
+ delta *= reconcileRatio;
+
+ ++chunkitr;
+ } else {
+ x = barX + reconciledWidth - getPreBarMargin();
+// sigx = x;
+ delta = 0;
+ }
+
+ if (timeSigToPlace &&
+ !el->event()->isa(Clef::EventType) &&
+ !el->event()->isa(::Rosegarden::Key::EventType)) {
+
+ if (sigx == 0.f) {
+ sigx = barX + offset;
+ }
+
+// NOTATION_DEBUG << "Placing timesig at " << (x - fixed) << endl;
+// bdi->second.layoutData.timeSigX = (int)(x - fixed);
+ NOTATION_DEBUG << "Placing timesig at " << sigx << " (would previously have been " << int(x-fixed) << "?)" << endl;
+ bdi->second.layoutData.timeSigX = (int)sigx;
+ double shift = getFixedItemSpacing() +
+ m_npf->getTimeSigWidth(timeSignature);
+ offset += shift;
+ x += shift;
+ NOTATION_DEBUG << "and moving next elt to " << x << endl;
+ timeSigToPlace = false;
+ }
+
+ if (barInset >= 1.0) {
+ if (el->event()->isa(Clef::EventType) ||
+ el->event()->isa(::Rosegarden::Key::EventType)) {
+ NOTATION_DEBUG << "Pulling clef/key back by " << getPreBarMargin() << endl;
+ x -= getPostBarMargin() * 2 / 3;
+ } else {
+ barInset = 0.0;
+ }
+ }
+
+ NOTATION_DEBUG << "NotationHLayout::layout(): setting element's x to " << x << " (was " << el->getLayoutX() << ")" << endl;
+
+ double displacedX = 0.0;
+ long dxRaw = 0;
+ el->event()->get<Int>(DISPLACED_X, dxRaw);
+ displacedX = double(dxRaw * m_npf->getNoteBodyWidth()) / 1000.0;
+
+ el->setLayoutX(x + displacedX);
+ el->setLayoutAirspace(x, int(delta));
+
+ // #704958 (multiple tuplet spanners created when entering
+ // triplet chord) -- only do this here for non-notes,
+ // notes get it from positionChord
+ if (!el->isNote()) {
+ sampleGroupElement(staff, clef, key, it);
+ }
+
+ if (el->isNote()) {
+
+ // This modifies "it" and "tieMap"
+ positionChord(staff, it, clef, key, tieMap, to);
+
+ } else if (el->isRest()) {
+
+ // nothing to do
+
+ } else if (el->event()->isa(Clef::EventType)) {
+
+ clef = Clef(*el->event());
+
+ } else if (el->event()->isa(::Rosegarden::Key::EventType)) {
+
+ key = ::Rosegarden::Key(*el->event());
+
+ } else if (el->event()->isa(Text::EventType)) {
+
+ // if it's a dynamic, make a note of it in case a
+ // hairpin immediately follows it
+
+ if (el->event()->has(Text::TextTypePropertyName) &&
+ el->event()->get<String>(Text::TextTypePropertyName) ==
+ Text::Dynamic) {
+ lastDynamicText = el;
+ }
+
+ } else if (el->event()->isa(Indication::EventType)) {
+
+ std::string type;
+ double ix = x;
+
+ // Check for a dynamic text at the same time as the
+ // indication and if found, move the indication to the
+ // right to make room. We know the text should have
+ // preceded the indication in the staff because it has
+ // a smaller subordering
+
+ if (el->event()->get<String>
+ (Indication::IndicationTypePropertyName, type) &&
+ (type == Indication::Crescendo ||
+ type == Indication::Decrescendo) &&
+ lastDynamicText &&
+ lastDynamicText->getViewAbsoluteTime() ==
+ el->getViewAbsoluteTime()) {
+
+ ix = x + m_npf->getTextWidth
+ (Text(*lastDynamicText->event())) +
+ m_npf->getNoteBodyWidth() / 4;
+ }
+
+ el->setLayoutX(ix + displacedX);
+ el->setLayoutAirspace(ix, delta - (ix - x));
+
+ } else if (el->event()->isa(Guitar::Chord::EventType)) {
+
+ int guitarChordWidth = m_npf->getLineSpacing() * 6;
+ el->setLayoutX(x - (guitarChordWidth / 2)
+ + fretboardCount * (guitarChordWidth +
+ m_npf->getNoteBodyWidth()/2)
+ + displacedX);
+ ++fretboardCount;
+
+ } else {
+
+ // nothing else
+ }
+
+ if (m_timePerProgressIncrement > 0 && (++count == 100)) {
+ count = 0;
+ timeT sinceIncrement = el->getViewAbsoluteTime() - lastIncrement;
+ if (sinceIncrement > m_timePerProgressIncrement) {
+ emit incrementProgress
+ (sinceIncrement / m_timePerProgressIncrement);
+ lastIncrement +=
+ (sinceIncrement / m_timePerProgressIncrement)
+ * m_timePerProgressIncrement;
+ throwIfCancelled();
+ }
+ }
+ }
+
+ if (timeSigToPlace) {
+ // no other events in this bar, so we never managed to place it
+ x = barX + offset;
+ NOTATION_DEBUG << "Placing timesig reluctantly at " << x << endl;
+ bdi->second.layoutData.timeSigX = (int)(x);
+ timeSigToPlace = false;
+ }
+
+ for (NotationGroupMap::iterator mi = m_groupsExtant.begin();
+ mi != m_groupsExtant.end(); ++mi) {
+ mi->second->applyBeam(notationStaff);
+ mi->second->applyTuplingLine(notationStaff);
+ delete mi->second;
+ }
+ m_groupsExtant.clear();
+
+ bdi->second.layoutData.needsLayout = false;
+ }
+}
+
+void
+NotationHLayout::sampleGroupElement(Staff &staff,
+ const Clef &clef,
+ const ::Rosegarden::Key &key,
+ const NotationElementList::iterator &itr)
+{
+ NotationElement *el = static_cast<NotationElement *>(*itr);
+
+ if (el->event()->has(BEAMED_GROUP_ID)) {
+
+ //!!! Gosh. We need some clever logic to establish whether
+ // one group is happening while another has not yet ended --
+ // perhaps we decide one has ended if we see another, and then
+ // re-open the case of the first if we meet another note that
+ // claims to be in it. Then we need to hint to both of the
+ // groups that they should choose appropriate stem directions
+ // -- we could just use HEIGHT_ON_STAFF of their first notes
+ // to determine this, as if that doesn't work, nothing will
+
+ long groupId = el->event()->get<Int>(BEAMED_GROUP_ID);
+ NOTATION_DEBUG << "group id: " << groupId << endl;
+ if (m_groupsExtant.find(groupId) == m_groupsExtant.end()) {
+ NOTATION_DEBUG << "(new group)" << endl;
+ m_groupsExtant[groupId] =
+ new NotationGroup(*staff.getViewElementList(),
+ m_notationQuantizer,
+ m_properties, clef, key);
+ }
+ m_groupsExtant[groupId]->sample(itr, true);
+ }
+}
+
+timeT
+NotationHLayout::getSpacingDuration(Staff &staff,
+ const NotationElementList::iterator &i)
+{
+ SegmentNotationHelper helper(staff.getSegment());
+ timeT t((*i)->getViewAbsoluteTime());
+ timeT d((*i)->getViewDuration());
+
+ if (d > 0 && (*i)->event()->getDuration() == 0) return d; // grace note
+
+ NotationElementList::iterator j(i), e(staff.getViewElementList()->end());
+ while (j != e && ((*j)->getViewAbsoluteTime() == t ||
+ (*j)->getViewDuration() == 0)) {
+ ++j;
+ }
+ if (j == e) {
+ return d;
+ } else {
+ return (*j)->getViewAbsoluteTime() - (*i)->getViewAbsoluteTime();
+ }
+}
+
+timeT
+NotationHLayout::getSpacingDuration(Staff &staff,
+ const NotationChord &chord)
+{
+ SegmentNotationHelper helper(staff.getSegment());
+
+ NotationElementList::iterator i = chord.getShortestElement();
+ timeT d((*i)->getViewDuration());
+
+ if (d > 0 && (*i)->event()->getDuration() == 0) return d; // grace note
+
+ NotationElementList::iterator j(i), e(staff.getViewElementList()->end());
+ while (j != e && (chord.contains(j) || (*j)->getViewDuration() == 0))
+ ++j;
+
+ if (j != e) {
+ d = (*j)->getViewAbsoluteTime() - (*i)->getViewAbsoluteTime();
+ }
+
+ return d;
+}
+
+void
+NotationHLayout::positionChord(Staff &staff,
+ NotationElementList::iterator &itr,
+ const Clef &clef, const ::Rosegarden::Key &key,
+ TieMap &tieMap,
+ NotationElementList::iterator &to)
+{
+ NotationChord chord(*staff.getViewElementList(), itr, m_notationQuantizer,
+ m_properties, clef, key);
+ double baseX, delta;
+ (static_cast<NotationElement *>(*itr))->getLayoutAirspace(baseX, delta);
+
+ bool barEndsInChord = false;
+
+ NOTATION_DEBUG << "NotationHLayout::positionChord: x = " << baseX << endl;
+
+ // #938545 (Broken notation: Duplicated note can float outside
+ // stave) -- We need to iterate over all elements in the chord
+ // range here, not just the ordered set of notes actually in the
+ // chord. They all have the same x-coord, so there's no
+ // particular complication here.
+
+ for (NotationElementList::iterator citr = chord.getInitialElement();
+ citr != staff.getViewElementList()->end(); ++citr) {
+
+ if (citr == to)
+ barEndsInChord = true;
+
+ // #704958 (multiple tuplet spanners created when entering
+ // triplet chord) -- layout() updates the beamed group data
+ // for non-notes, but we have to do it for notes so as to
+ // ensure every note in the chord is accounted for
+ sampleGroupElement(staff, clef, key, citr);
+
+ NotationElement *elt = static_cast<NotationElement*>(*citr);
+
+ double displacedX = 0.0;
+ long dxRaw = 0;
+ elt->event()->get<Int>(DISPLACED_X, dxRaw);
+ displacedX = double(dxRaw * m_npf->getNoteBodyWidth()) / 1000.0;
+
+ elt->setLayoutX(baseX + displacedX);
+ elt->setLayoutAirspace(baseX, delta);
+
+ NOTATION_DEBUG << "NotationHLayout::positionChord: assigned x to elt at " << elt->getViewAbsoluteTime() << endl;
+
+ if (citr == chord.getFinalElement())
+ break;
+ }
+
+ // Check for any ties going back, and if so work out how long they
+ // must have been and assign accordingly.
+
+ for (NotationElementList::iterator citr = chord.getInitialElement();
+ citr != staff.getViewElementList()->end(); ++citr) {
+
+ NotationElement *note = static_cast<NotationElement*>(*citr);
+ if (!note->isNote()) {
+ if (citr == chord.getFinalElement())
+ break;
+ continue;
+ }
+
+ bool tiedForwards = false;
+ bool tiedBack = false;
+
+ note->event()->get<Bool>(TIED_FORWARD, tiedForwards);
+ note->event()->get<Bool>(TIED_BACKWARD, tiedBack);
+
+ if (!note->event()->has(PITCH))
+ continue;
+ int pitch = note->event()->get<Int>(PITCH);
+
+ if (tiedBack) {
+ TieMap::iterator ti(tieMap.find(pitch));
+
+ if (ti != tieMap.end()) {
+ NotationElementList::iterator otherItr(ti->second);
+
+ if ((*otherItr)->getViewAbsoluteTime() +
+ (*otherItr)->getViewDuration() ==
+ note->getViewAbsoluteTime()) {
+
+ NOTATION_DEBUG << "Second note in tie at " << note->getViewAbsoluteTime() << ": found first note, it matches" << endl;
+
+ (*otherItr)->event()->setMaybe<Int>
+ (m_properties.TIE_LENGTH,
+ (int)(baseX - (*otherItr)->getLayoutX()));
+
+ } else {
+ NOTATION_DEBUG << "Second note in tie at " << note->getViewAbsoluteTime() << ": found first note but it ends at " << ((*otherItr)->getViewAbsoluteTime() + (*otherItr)->getViewDuration()) << endl;
+
+ tieMap.erase(pitch);
+ }
+ }
+ }
+
+ if (tiedForwards) {
+ note->event()->setMaybe<Int>(m_properties.TIE_LENGTH, 0);
+ tieMap[pitch] = citr;
+ } else {
+ note->event()->unset(m_properties.TIE_LENGTH);
+ }
+
+ if (citr == chord.getFinalElement())
+ break;
+ }
+
+ itr = chord.getFinalElement();
+ if (barEndsInChord) {
+ to = itr;
+ ++to;
+ }
+}
+
+float
+NotationHLayout::getLayoutWidth(ViewElement &ve,
+ NotePixmapFactory *npf,
+ const ::Rosegarden::Key &previousKey) const
+{
+ NotationElement& e = static_cast<NotationElement&>(ve);
+
+ if ((e.isNote() || e.isRest()) && e.event()->has(NOTE_TYPE)) {
+
+ long noteType = e.event()->get<Int>(NOTE_TYPE);
+ long dots = 0;
+ (void)e.event()->get<Int>(NOTE_DOTS, dots);
+
+ double bw = 0;
+
+ if (e.isNote()) {
+ bw = m_npf->getNoteBodyWidth(noteType)
+ + m_npf->getDotWidth() * dots;
+ } else {
+ bw = m_npf->getRestWidth(Note(noteType, dots));
+ }
+
+ double multiplier = double(Note(noteType, dots).getDuration()) /
+ double(Note(Note::Quaver).getDuration());
+ multiplier -= 1.0;
+ multiplier *= m_proportion / 100.0;
+ multiplier += 1.0;
+
+ double gap = m_npf->getNoteBodyWidth(noteType) * multiplier;
+
+ NOTATION_DEBUG << "note type " << noteType << ", isNote " << e.isNote() << ", dots " << dots << ", multiplier " << multiplier << ", gap " << gap << ", result " << (bw + gap * m_spacing / 100.0) << endl;
+
+ gap = gap * m_spacing / 100.0;
+ return bw + gap;
+
+ } else {
+
+ double w = getFixedItemSpacing();
+
+ if (e.event()->isa(Clef::EventType)) {
+
+ w += m_npf->getClefWidth(Clef(*e.event()));
+
+ } else if (e.event()->isa(::Rosegarden::Key::EventType)) {
+
+ ::Rosegarden::Key key(*e.event());
+
+ ::Rosegarden::Key cancelKey = previousKey;
+
+ if (m_keySigCancelMode == 0) { // only when entering C maj / A min
+
+ if (key.getAccidentalCount() != 0)
+ cancelKey = ::Rosegarden::Key();
+
+ } else if (m_keySigCancelMode == 1) { // only when reducing acc count
+
+ if (!(key.isSharp() == cancelKey.isSharp() &&
+ key.getAccidentalCount() < cancelKey.getAccidentalCount())) {
+ cancelKey = ::Rosegarden::Key();
+ }
+ }
+
+ w += m_npf->getKeyWidth(key, cancelKey);
+
+ } else if (e.event()->isa(Indication::EventType) ||
+ e.event()->isa(Text::EventType)) {
+
+ w = 0;
+
+ } else {
+ // NOTATION_DEBUG << "NotationHLayout::getLayoutWidth(): no case for event type " << e.event()->getType() << endl;
+ // w += 24;
+ w = 0;
+ }
+
+ return w;
+ }
+}
+
+int NotationHLayout::getBarMargin() const
+{
+ return (int)(m_npf->getBarMargin() * m_spacing / 100.0);
+}
+
+int NotationHLayout::getPreBarMargin() const
+{
+ return getBarMargin() / 3;
+}
+
+int NotationHLayout::getPostBarMargin() const
+{
+ return getBarMargin() - getPreBarMargin();
+}
+
+int NotationHLayout::getFixedItemSpacing() const
+{
+ return (int)((m_npf->getNoteBodyWidth() * 2.0 / 3.0) * m_spacing / 100.0);
+}
+
+void
+NotationHLayout::reset()
+{
+ for (BarDataMap::iterator i = m_barData.begin();
+ i != m_barData.end(); ++i) {
+ clearBarList(*i->first);
+ }
+
+ m_barData.clear();
+ m_barPositions.clear();
+ m_totalWidth = 0;
+}
+
+void
+NotationHLayout::resetStaff(Staff &staff, timeT startTime, timeT endTime)
+{
+ if (startTime == endTime) {
+ getBarData(staff).clear();
+ m_totalWidth = 0;
+ }
+}
+
+int
+NotationHLayout::getFirstVisibleBar() const
+{
+ int bar = 0;
+ bool haveBar = false;
+ for (BarDataMap::const_iterator i = m_barData.begin(); i != m_barData.end(); ++i) {
+ if (i->second.begin() == i->second.end())
+ continue;
+ int barHere = i->second.begin()->first;
+ if (barHere < bar || !haveBar) {
+ bar = barHere;
+ haveBar = true;
+ }
+ }
+
+ // NOTATION_DEBUG << "NotationHLayout::getFirstVisibleBar: returning " << bar << endl;
+
+ return bar;
+}
+
+int
+NotationHLayout::getFirstVisibleBarOnStaff(Staff &staff)
+{
+ BarDataList &bdl(getBarData(staff));
+
+ int bar = 0;
+ if (bdl.begin() != bdl.end())
+ bar = bdl.begin()->first;
+
+ // NOTATION_DEBUG << "NotationHLayout::getFirstVisibleBarOnStaff: returning " << bar << endl;
+
+ return bar;
+}
+
+int
+NotationHLayout::getLastVisibleBar() const
+{
+ int bar = 0;
+ bool haveBar = false;
+ for (BarDataMap::const_iterator i = m_barData.begin();
+ i != m_barData.end(); ++i) {
+ if (i->second.begin() == i->second.end())
+ continue;
+ int barHere = getLastVisibleBarOnStaff(*i->first);
+ if (barHere > bar || !haveBar) {
+ bar = barHere;
+ haveBar = true;
+ }
+ }
+
+ // NOTATION_DEBUG << "NotationHLayout::getLastVisibleBar: returning " << bar << endl;
+
+ return bar;
+}
+
+int
+NotationHLayout::getLastVisibleBarOnStaff(Staff &staff) const
+{
+ const BarDataList &bdl(getBarData(staff));
+ int bar = 0;
+
+ if (bdl.begin() != bdl.end()) {
+ BarDataList::const_iterator i = bdl.end();
+ bar = ((--i)->first) + 1; // last visible bar_line_
+ }
+
+ // NOTATION_DEBUG << "NotationHLayout::getLastVisibleBarOnStaff: returning " << bar << endl;
+
+ return bar;
+}
+
+double
+NotationHLayout::getBarPosition(int bar) const
+{
+ double position = 0.0;
+
+ BarPositionList::const_iterator i = m_barPositions.find(bar);
+
+ if (i != m_barPositions.end()) {
+
+ position = i->second;
+
+ } else {
+
+ i = m_barPositions.begin();
+ if (i != m_barPositions.end()) {
+ if (bar < i->first)
+ position = i->second;
+ else {
+ i = m_barPositions.end();
+ --i;
+ if (bar > i->first)
+ position = i->second;
+ }
+ }
+ }
+
+ // NOTATION_DEBUG << "NotationHLayout::getBarPosition: returning " << position << " for bar " << bar << endl;
+
+ return position;
+}
+
+bool
+NotationHLayout::isBarCorrectOnStaff(Staff &staff, int i)
+{
+ BarDataList &bdl(getBarData(staff));
+ ++i;
+
+ BarDataList::iterator bdli(bdl.find(i));
+ if (bdli != bdl.end())
+ return bdli->second.basicData.correct;
+ else
+ return true;
+}
+
+bool NotationHLayout::getTimeSignaturePosition(Staff &staff,
+ int i,
+ TimeSignature &timeSig,
+ double &timeSigX)
+{
+ BarDataList &bdl(getBarData(staff));
+
+ BarDataList::iterator bdli(bdl.find(i));
+ if (bdli != bdl.end()) {
+ timeSig = bdli->second.basicData.timeSignature;
+ timeSigX = (double)(bdli->second.layoutData.timeSigX);
+ return bdli->second.basicData.newTimeSig;
+ } else
+ return 0;
+}
+
+timeT
+NotationHLayout::getTimeForX(double x) const
+{
+ return RulerScale::getTimeForX(x);
+}
+
+double
+NotationHLayout::getXForTime(timeT t) const
+{
+ return RulerScale::getXForTime(t);
+}
+
+double
+NotationHLayout::getXForTimeByEvent(timeT time) const
+{
+ // NOTATION_DEBUG << "NotationHLayout::getXForTime(" << time << ")" << endl;
+
+ for (BarDataMap::const_iterator i = m_barData.begin(); i != m_barData.end(); ++i) {
+
+ Staff *staff = i->first;
+
+ if (staff->getSegment().getStartTime() <= time &&
+ staff->getSegment().getEndMarkerTime() > time) {
+
+ ViewElementList::iterator vli =
+ staff->getViewElementList()->findNearestTime(time);
+
+ bool found = false;
+ double x = 0.0, dx = 0.0;
+ timeT t = 0, dt = 0;
+
+ while (!found) {
+ if (vli == staff->getViewElementList()->end())
+ break;
+ NotationElement *element = static_cast<NotationElement *>(*vli);
+ if (element->getCanvasItem()) {
+ x = element->getLayoutX();
+ double temp;
+ element->getLayoutAirspace(temp, dx);
+ t = element->event()->getNotationAbsoluteTime();
+ dt = element->event()->getNotationDuration();
+ found = true;
+ break;
+ }
+ ++vli;
+ }
+
+ if (found) {
+ if (time > t) {
+
+ while (vli != staff->getViewElementList()->end() &&
+ ((*vli)->event()->getNotationAbsoluteTime() < time ||
+ !((static_cast<NotationElement *>(*vli))->getCanvasItem())))
+ ++vli;
+
+ if (vli != staff->getViewElementList()->end()) {
+ NotationElement *element = static_cast<NotationElement *>(*vli);
+ dx = element->getLayoutX() - x;
+ dt = element->event()->getNotationAbsoluteTime() - t;
+ }
+
+ if (dt > 0 && dx > 0) {
+ return x + dx * (time - t) / dt;
+ }
+ }
+
+ return x - 3;
+ }
+ }
+ }
+
+ return RulerScale::getXForTime(time);
+}
+
+std::vector<int> NotationHLayout::m_availableSpacings;
+std::vector<int> NotationHLayout::m_availableProportions;
+
+}
diff --git a/src/gui/editors/notation/NotationHLayout.h b/src/gui/editors/notation/NotationHLayout.h
new file mode 100644
index 0000000..9d7366b
--- /dev/null
+++ b/src/gui/editors/notation/NotationHLayout.h
@@ -0,0 +1,446 @@
+
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
+
+/*
+ Rosegarden
+ A MIDI and audio sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_NOTATIONHLAYOUT_H_
+#define _RG_NOTATIONHLAYOUT_H_
+
+#include "base/LayoutEngine.h"
+#include "base/NotationTypes.h"
+#include "NotationElement.h"
+#include "gui/general/ProgressReporter.h"
+#include <map>
+#include <vector>
+#include "base/Event.h"
+
+
+class TieMap;
+class QObject;
+
+
+namespace Rosegarden
+{
+
+class ViewElement;
+class Staff;
+class Quantizer;
+class NotePixmapFactory;
+class NotationProperties;
+class NotationGroup;
+class NotationChord;
+class Key;
+class Composition;
+class Clef;
+class AccidentalTable;
+
+
+/**
+ * Horizontal notation layout
+ *
+ * computes the X coordinates of notation elements
+ */
+
+class NotationHLayout : public ProgressReporter,
+ public HorizontalLayoutEngine
+{
+public:
+ NotationHLayout(Composition *c,
+ NotePixmapFactory *npf,
+ const NotationProperties &properties,
+ QObject* parent, const char* name = 0);
+
+ virtual ~NotationHLayout();
+
+ void setNotePixmapFactory(NotePixmapFactory *npf) {
+ m_npf = npf;
+ }
+
+ /**
+ * Precomputes layout data for a single staff. The resulting data
+ * is stored in the BarDataMap, keyed from the staff reference;
+ * the entire map is then used by reconcileBars() and layout().
+ * The map should be cleared (by calling reset()) before a full
+ * set of staffs is preparsed.
+ */
+ virtual void scanStaff(Staff &staff,
+ timeT startTime = 0,
+ timeT endTime = 0);
+
+ /**
+ * Resets internal data stores, notably the BarDataMap that is
+ * used to retain the data computed by scanStaff().
+ */
+ virtual void reset();
+
+ /**
+ * Resets internal data stores, notably the given staff's entry
+ * in the BarDataMap used to retain the data computed by scanStaff().
+ */
+ virtual void resetStaff(Staff &staff,
+ timeT startTime = 0,
+ timeT endTime = 0);
+
+ /**
+ * Lays out all staffs that have been scanned
+ */
+ virtual void finishLayout(timeT startTime = 0,
+ timeT endTime = 0);
+
+ /**
+ * Set page mode
+ */
+ virtual void setPageMode(bool pageMode) { m_pageMode = pageMode; }
+
+ /**
+ * Get the page mode setting
+ */
+ virtual bool isPageMode() { return m_pageMode; }
+
+ /**
+ * Set a page width
+ */
+ virtual void setPageWidth(double pageWidth) { m_pageWidth = pageWidth; }
+
+ /**
+ * Get the page width
+ */
+ virtual double getPageWidth() { return m_pageWidth; }
+
+ /**
+ * Gets the current spacing factor (100 == "normal" spacing)
+ */
+ int getSpacing() const { return m_spacing; }
+
+ /**
+ * Sets the current spacing factor (100 == "normal" spacing)
+ */
+ void setSpacing(int spacing) { m_spacing = spacing; }
+
+ /**
+ * Gets the range of "standard" spacing factors (you can
+ * setSpacing() to anything you want, but it makes sense to
+ * have a standard list for GUI use). The only guaranteed
+ * property of the returned list is that 100 will be in it.
+ */
+ static std::vector<int> getAvailableSpacings();
+
+ /**
+ * Gets the current proportion (100 == spaces proportional to
+ * durations, 0 == equal spacings)
+ */
+ int getProportion() const { return m_proportion; }
+
+ /**
+ * Sets the current proportion (100 == spaces proportional to
+ * durations, 0 == equal spacings)
+ */
+ void setProportion(int proportion) { m_proportion = proportion; }
+
+ /**
+ * Gets the range of "standard" proportion factors (you can
+ * setProportion() to anything you want, but it makes sense to
+ * have a standard list for GUI use). The only guaranteed
+ * property of the returned list is that 0, 100, and whatever the
+ * default proportion is will be in it.
+ */
+ static std::vector<int> getAvailableProportions();
+
+ /**
+ * Returns the total length of all elements once layout is done
+ * This is the x-coord of the end of the last element on the longest
+ * staff, plus the space allocated to that element
+ */
+ virtual double getTotalWidth() const { return m_totalWidth; }
+
+ /**
+ * Returns the number of the first visible bar line on the given
+ * staff
+ */
+ virtual int getFirstVisibleBarOnStaff(Staff &staff);
+
+ /**
+ * Returns the number of the first visible bar line on any
+ * staff
+ */
+ virtual int getFirstVisibleBar() const;
+
+ /**
+ * Returns the number of the last visible bar line on the given
+ * staff
+ */
+ virtual int getLastVisibleBarOnStaff(Staff &staff) const;
+
+ /**
+ * Returns the number of the first visible bar line on any
+ * staff
+ */
+ virtual int getLastVisibleBar() const;
+
+ /**
+ * Returns the x-coordinate of the given bar number
+ */
+ virtual double getBarPosition(int barNo) const;
+
+ /**
+ * Returns the nearest time value to the given X coord.
+ */
+ virtual timeT getTimeForX(double x) const;
+
+ /**
+ * Returns the X coord corresponding to the given time value.
+ * This RulerScale method works by interpolating between bar lines
+ * (the inverse of the way getTimeForX works), and should be used
+ * for any rulers associated with the layout.
+ */
+ virtual double getXForTime(timeT time) const;
+
+ /**
+ * Returns the X coord corresponding to the given time value.
+ * This method works by interpolating between event positions, and
+ * should be used for position pointer tracking during playback.
+ */
+ virtual double getXForTimeByEvent(timeT time) const;
+
+ /**
+ * Returns true if the specified bar has the correct length
+ */
+ virtual bool isBarCorrectOnStaff(Staff &staff, int barNo);
+
+ /**
+ * Returns true if there is a new time signature in the given bar,
+ * setting timeSignature appropriately and setting timeSigX to its
+ * x-coord
+ */
+ virtual bool getTimeSignaturePosition
+ (Staff &staff, int barNo,
+ TimeSignature &timeSig, double &timeSigX);
+
+ /// purely optional, used only for progress reporting
+ void setStaffCount(int staffCount) {
+ m_staffCount = staffCount;
+ }
+
+protected:
+
+ struct Chunk {
+ timeT duration;
+ short subordering;
+ float fixed;
+ float stretchy;
+ float x;
+
+ Chunk(timeT d, short sub, float f, float s) :
+ duration(d), subordering(sub), fixed(f), stretchy(s), x(0) { }
+ Chunk(short sub, float f) :
+ duration(0), subordering(sub), fixed(f), stretchy(0), x(0) { }
+ };
+ typedef std::vector<Chunk> ChunkList;
+
+ /**
+ * Inner class for bar data, used by scanStaff()
+ */
+ struct BarData
+ {
+ ChunkList chunks;
+
+ struct BasicData
+ { // slots that can be filled at construction time
+
+ NotationElementList::iterator start; // i.e. event following barline
+ bool correct; // bar preceding barline has correct duration
+ TimeSignature timeSignature;
+ bool newTimeSig;
+
+ } basicData;
+
+ struct SizeData
+ { // slots that can be filled when the following bar has been scanned
+
+ float idealWidth; // theoretical width of bar following barline
+ float reconciledWidth;
+ float fixedWidth; // width of non-chunk items in bar
+ int clefKeyWidth;
+ timeT actualDuration; // may exceed nominal duration
+
+ } sizeData;
+
+ struct LayoutData
+ { // slots either assumed, or only known at layout time
+ bool needsLayout;
+ double x; // coordinate for display of barline
+ int timeSigX;
+
+ } layoutData;
+
+ BarData(NotationElementList::iterator i,
+ bool correct, TimeSignature timeSig, bool newTimeSig) {
+ basicData.start = i;
+ basicData.correct = correct;
+ basicData.timeSignature = timeSig;
+ basicData.newTimeSig = newTimeSig;
+ sizeData.idealWidth = 0;
+ sizeData.reconciledWidth = 0;
+ sizeData.fixedWidth = 0;
+ sizeData.clefKeyWidth = 0;
+ sizeData.actualDuration = 0;
+ layoutData.needsLayout = true;
+ layoutData.x = -1;
+ layoutData.timeSigX = -1;
+ }
+ };
+
+ typedef std::map<int, BarData> BarDataList;
+ typedef BarDataList::value_type BarDataPair;
+ typedef std::map<Staff *, BarDataList> BarDataMap;
+ typedef std::map<int, double> BarPositionList;
+
+ typedef std::map<Staff *, int> StaffIntMap;
+ typedef std::map<long, NotationGroup *> NotationGroupMap;
+
+ void clearBarList(Staff &);
+
+
+ /**
+ * Set the basic data for the given barNo. If barNo is
+ * beyond the end of the existing bar data list, create new
+ * records and/or fill with empty ones as appropriate.
+ */
+ void setBarBasicData(Staff &staff, int barNo,
+ NotationElementList::iterator start, bool correct,
+ TimeSignature timeSig, bool newTimeSig);
+
+ /**
+ * Set the size data for the given barNo. If barNo is
+ * beyond the end of the existing bar data list, create new
+ * records and/or fill with empty ones as appropriate.
+ */
+ void setBarSizeData(Staff &staff, int barNo,
+ float fixedWidth, timeT actualDuration);
+
+ /**
+ * Returns the bar positions for a given staff, provided that
+ * staff has been preparsed since the last reset
+ */
+ BarDataList& getBarData(Staff &staff);
+ const BarDataList& getBarData(Staff &staff) const;
+
+ /// Find the staff in which bar "barNo" is widest
+ Staff *getStaffWithWidestBar(int barNo);
+
+ /// Find width of clef+key in the staff in which they're widest in this bar
+ int getMaxRepeatedClefAndKeyWidth(int barNo);
+
+ /// For a single bar, makes sure synchronisation points align in all staves
+ void preSquishBar(int barNo);
+
+ /// Tries to harmonize the bar positions for all the staves (linear mode)
+ void reconcileBarsLinear();
+
+ /// Tries to harmonize the bar positions for all the staves (page mode)
+ void reconcileBarsPage();
+
+ void layout(BarDataMap::iterator,
+ timeT startTime,
+ timeT endTime);
+
+ /// Find earliest element with quantized time of t or greater
+ NotationElementList::iterator getStartOfQuantizedSlice
+ (NotationElementList *, timeT t) const;
+
+ void scanChord
+ (NotationElementList *notes, NotationElementList::iterator &i,
+ const Clef &, const ::Rosegarden::Key &,
+ AccidentalTable &, float &lyricWidth,
+ ChunkList &chunks, NotePixmapFactory *, int ottavaShift,
+ NotationElementList::iterator &to);
+
+ typedef std::map<int, NotationElementList::iterator> TieMap;
+
+ // This modifies the NotationElementList::iterator passed to it,
+ // moving it on to the last note in the chord; updates the TieMap;
+ // and may modify the to-iterator if it turns out to point at a
+ // note within the chord
+ void positionChord
+ (Staff &staff,
+ NotationElementList::iterator &, const Clef &clef,
+ const ::Rosegarden::Key &key, TieMap &, NotationElementList::iterator &to);
+
+ void sampleGroupElement
+ (Staff &staff, const Clef &clef,
+ const ::Rosegarden::Key &key, const NotationElementList::iterator &);
+
+ /// Difference between absolute time of next event and of this
+ timeT getSpacingDuration
+ (Staff &staff, const NotationElementList::iterator &);
+
+ /// Difference between absolute time of chord and of first event not in it
+ timeT getSpacingDuration
+ (Staff &staff, const NotationChord &);
+
+ float getLayoutWidth(ViewElement &,
+ NotePixmapFactory *,
+ const ::Rosegarden::Key &) const;
+
+ int getBarMargin() const;
+ int getPreBarMargin() const;
+ int getPostBarMargin() const;
+ int getFixedItemSpacing() const;
+
+ NotePixmapFactory *getNotePixmapFactory(Staff &);
+ NotePixmapFactory *getGraceNotePixmapFactory(Staff &);
+
+ //--------------- Data members ---------------------------------
+
+ BarDataMap m_barData;
+ StaffIntMap m_staffNameWidths;
+ BarPositionList m_barPositions;
+ NotationGroupMap m_groupsExtant;
+
+ double m_totalWidth;
+ bool m_pageMode;
+ double m_pageWidth;
+ int m_spacing;
+ int m_proportion;
+ int m_keySigCancelMode;
+
+ //!!! This should not be here -- different staffs may have
+ //different sizes in principle, so we should always be referring
+ //to the npf of a particular staff
+ NotePixmapFactory *m_npf;
+
+ static std::vector<int> m_availableSpacings;
+ static std::vector<int> m_availableProportions;
+
+ const Quantizer *m_notationQuantizer;
+ const NotationProperties &m_properties;
+
+ int m_timePerProgressIncrement;
+ std::map<Staff *, bool> m_haveOttavaSomewhere;
+ int m_staffCount; // purely for progress reporting
+};
+
+
+}
+
+#endif
diff --git a/src/gui/editors/notation/NotationProperties.cpp b/src/gui/editors/notation/NotationProperties.cpp
new file mode 100644
index 0000000..8c87cc3
--- /dev/null
+++ b/src/gui/editors/notation/NotationProperties.cpp
@@ -0,0 +1,85 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
+
+/*
+ Rosegarden
+ A MIDI and audio sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_NOTATIONPROPERTIES_H_
+#define _RG_NOTATIONPROPERTIES_H_
+
+#include "base/PropertyName.h"
+#include <string>
+
+
+
+
+namespace Rosegarden
+{
+
+
+
+/**
+ * Property names for properties that are computed and cached within
+ * the notation module, but that need not necessarily be saved with
+ * the file.
+ *
+ * If you add something here, remember to add the definition to
+ * notationproperties.cpp as well...
+ */
+
+class NotationProperties
+{
+public:
+ NotationProperties(const std::string &prefix);
+
+ // These are only of interest to notation views, but are the
+ // same across all notation views.
+
+ static const PropertyName HEIGHT_ON_STAFF;
+ static const PropertyName NOTE_STYLE;
+ static const PropertyName BEAMED;
+ static const PropertyName BEAM_ABOVE;
+ static const PropertyName SLASHES;
+ static const PropertyName STEM_UP;
+ static const PropertyName USE_CAUTIONARY_ACCIDENTAL;
+ static const PropertyName OTTAVA_SHIFT;
+ static const PropertyName SLUR_ABOVE;
+
+ // The rest are, or may be, view-local
+
+ const PropertyName VIEW_LOCAL_STEM_UP;
+ const PropertyName MIN_WIDTH;
+ const PropertyName CALCULATED_ACCIDENTAL;
+ const PropertyName DISPLAY_ACCIDENTAL;
+ const PropertyName DISPLAY_ACCIDENTAL_IS_CAUTIONARY;
+ const PropertyName ACCIDENTAL_SHIFT;
+ const PropertyName ACCIDENTAL_EXTRA_SHIFT;
+ const PropertyName UNBEAMED_STEM_LENGTH;
+ const PropertyName DRAW_FLAG;
+ const PropertyName NOTE_HEAD_SHIFTED;
+ const PropertyName NEEDS_EXTRA_SHIFT_SPACE;
+ const PropertyName NOTE_DOT_SHIFTED;
+ const PropertyName CHORD_PRIMARY_NOTE;
+ const PropertyName CHORD_MARK_COUNT;
+ const PropertyName TIE_LENGTH;
+ const PropertyName SLUR_Y_DELTA;
+ const PropertyName SLUR_LENGTH;
+ const PropertyName LYRIC_EXTRA_WIDTH;
+ const PropertyName REST_TOO_SHORT;
+ const PropertyName REST_OUTSIDE_STAVE;
+
+ // Set in applyBeam in notationsets.cpp:
+
+ const PropertyName BEAM_GRADIENT;
+ const PropertyName BEAM_SECTION_WIDTH;
+ const PropertyName BEAM_NEXT_BEAM_COUNT;
+ const PropertyName BEAM_NEXT_PART_BEAMS;
+ const PropertyName BEAM_THIS_PART_BEAMS;
+ const PropertyName BEAM_MY_Y;
+ const PropertyName TUPLING_LINE_MY_Y;
+ const PropertyName TUPLING_LINE_WIDTH;
+ const PropertyName TUPLING_LINE_GRADIENT;
+ const PropertyName TUPLING_LINE_FOLLOWS_BEAM;
+
+};
+
+
+}
+
+#endif
diff --git a/src/gui/editors/notation/NotationSelectionPaster.cpp b/src/gui/editors/notation/NotationSelectionPaster.cpp
new file mode 100644
index 0000000..3b008f2
--- /dev/null
+++ b/src/gui/editors/notation/NotationSelectionPaster.cpp
@@ -0,0 +1,89 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
+
+/*
+ Rosegarden
+ A MIDI and audio sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "NotationSelectionPaster.h"
+
+#include <klocale.h>
+#include "base/Event.h"
+#include "base/Selection.h"
+#include "base/ViewElement.h"
+#include "commands/edit/PasteEventsCommand.h"
+#include "gui/general/EditTool.h"
+#include "gui/general/LinedStaff.h"
+#include "document/RosegardenGUIDoc.h"
+#include "NotationTool.h"
+#include "NotationView.h"
+#include "NotationElement.h"
+
+
+namespace Rosegarden
+{
+
+NotationSelectionPaster::NotationSelectionPaster(EventSelection& es,
+ NotationView* view)
+ : NotationTool("NotationPaster", view),
+ m_selection(es)
+{
+ m_nParentView->setCanvasCursor(Qt::crossCursor);
+}
+
+NotationSelectionPaster::~NotationSelectionPaster()
+{}
+
+void NotationSelectionPaster::handleLeftButtonPress(timeT,
+ int,
+ int staffNo,
+ QMouseEvent* e,
+ ViewElement*)
+{
+ if (staffNo < 0)
+ return ;
+ Event *clef = 0, *key = 0;
+
+ LinedStaff *staff = m_nParentView->getLinedStaff(staffNo);
+
+ NotationElementList::iterator closestElement =
+ staff->getClosestElementToCanvasCoords(e->x(), (int)e->y(),
+ clef, key, false, -1);
+
+ if (closestElement == staff->getViewElementList()->end())
+ return ;
+
+ timeT time = (*closestElement)->getViewAbsoluteTime();
+
+ Segment& segment = staff->getSegment();
+ PasteEventsCommand *command = new PasteEventsCommand
+ (segment, m_parentView->getDocument()->getClipboard(), time,
+ PasteEventsCommand::Restricted);
+
+ if (!command->isPossible()) {
+ m_parentView->slotStatusHelpMsg(i18n("Couldn't paste at this point"));
+ } else {
+ m_parentView->addCommandToHistory(command);
+ m_parentView->slotStatusHelpMsg(i18n("Ready."));
+ }
+}
+
+}
diff --git a/src/gui/editors/notation/NotationSelectionPaster.h b/src/gui/editors/notation/NotationSelectionPaster.h
new file mode 100644
index 0000000..e6a80dd
--- /dev/null
+++ b/src/gui/editors/notation/NotationSelectionPaster.h
@@ -0,0 +1,72 @@
+
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
+
+/*
+ Rosegarden
+ A MIDI and audio sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "NotationSelector.h"
+#include "misc/Debug.h"
+
+#include <klocale.h>
+#include "base/Event.h"
+#include "base/NotationTypes.h"
+#include "base/PropertyName.h"
+#include "base/Selection.h"
+#include "base/ViewElement.h"
+#include "base/BaseProperties.h"
+#include "commands/edit/MoveAcrossSegmentsCommand.h"
+#include "commands/edit/MoveCommand.h"
+#include "commands/edit/TransposeCommand.h"
+#include "commands/notation/IncrementDisplacementsCommand.h"
+#include "gui/general/EditTool.h"
+#include "gui/general/GUIPalette.h"
+#include "gui/general/LinedStaff.h"
+#include "gui/general/RosegardenCanvasView.h"
+#include "gui/kdeext/QCanvasSimpleSprite.h"
+#include "NotationElement.h"
+#include "NotationProperties.h"
+#include "NotationStaff.h"
+#include "NotationTool.h"
+#include "NotationView.h"
+#include "NotePixmapFactory.h"
+#include <kaction.h>
+#include <qapplication.h>
+#include <qiconset.h>
+#include <qrect.h>
+#include <qstring.h>
+#include <qtimer.h>
+
+
+namespace Rosegarden
+{
+
+using namespace BaseProperties;
+
+NotationSelector::NotationSelector(NotationView* view)
+ : NotationTool("NotationSelector", view),
+ m_selectionRect(0),
+ m_updateRect(false),
+ m_selectedStaff(0),
+ m_clickedElement(0),
+ m_selectionToMerge(0),
+ m_justSelectedBar(false),
+ m_wholeStaffSelectionComplete(false)
+{
+ connect(m_parentView, SIGNAL(usedSelection()),
+ this, SLOT(slotHideSelection()));
+
+ connect(this, SIGNAL(editElement(NotationStaff *, NotationElement *, bool)),
+ m_parentView, SLOT(slotEditElement(NotationStaff *, NotationElement *, bool)));
+
+ QIconSet icon
+ (NotePixmapFactory::toQPixmap(NotePixmapFactory::
+ makeToolbarPixmap("crotchet")));
+ new KToggleAction(i18n("Switch to Insert Tool"), icon, 0, this,
+ SLOT(slotInsertSelected()), actionCollection(),
+ "insert");
+
+ new KAction(i18n("Switch to Erase Tool"), "eraser", 0, this,
+ SLOT(slotEraseSelected()), actionCollection(),
+ "erase");
+
+ // (this crashed, and it might be superfluous with ^N anyway, so I'm
+ // commenting it out, but leaving it here in case I change my mind about
+ // fooling with it.) (DMM)
+ // new KAction(i18n("Normalize Rests"), 0, 0, this,
+ // SLOT(slotCollapseRests()), actionCollection(),
+ // "collapse_rests");
+
+ new KAction(i18n("Collapse Rests"), 0, 0, this,
+ SLOT(slotCollapseRestsHard()), actionCollection(),
+ "collapse_rests_aggressively");
+
+ new KAction(i18n("Respell as Flat"), 0, 0, this,
+ SLOT(slotRespellFlat()), actionCollection(),
+ "respell_flat");
+
+ new KAction(i18n("Respell as Sharp"), 0, 0, this,
+ SLOT(slotRespellSharp()), actionCollection(),
+ "respell_sharp");
+
+ new KAction(i18n("Respell as Natural"), 0, 0, this,
+ SLOT(slotRespellNatural()), actionCollection(),
+ "respell_natural");
+
+ new KAction(i18n("Collapse Notes"), 0, 0, this,
+ SLOT(slotCollapseNotes()), actionCollection(),
+ "collapse_notes");
+
+ new KAction(i18n("Interpret"), 0, 0, this,
+ SLOT(slotInterpret()), actionCollection(),
+ "interpret");
+
+ new KAction(i18n("Move to Staff Above"), 0, 0, this,
+ SLOT(slotStaffAbove()), actionCollection(),
+ "move_events_up_staff");
+
+ new KAction(i18n("Move to Staff Below"), 0, 0, this,
+ SLOT(slotStaffBelow()), actionCollection(),
+ "move_events_down_staff");
+
+ new KAction(i18n("Make Invisible"), 0, 0, this,
+ SLOT(slotMakeInvisible()), actionCollection(),
+ "make_invisible");
+
+ new KAction(i18n("Make Visible"), 0, 0, this,
+ SLOT(slotMakeVisible()), actionCollection(),
+ "make_visible");
+
+ createMenu("notationselector.rc");
+}
+
+NotationSelector::~NotationSelector()
+{
+ delete m_selectionToMerge;
+}
+
+void NotationSelector::handleLeftButtonPress(timeT t,
+ int height,
+ int staffNo,
+ QMouseEvent* e,
+ ViewElement *element)
+{
+ std::cerr << "NotationSelector::handleMousePress: time is " << t << ", staffNo is "
+ << staffNo << ", e and element are " << e << " and " << element << std::endl;
+
+ if (m_justSelectedBar) {
+ handleMouseTripleClick(t, height, staffNo, e, element);
+ m_justSelectedBar = false;
+ return ;
+ }
+
+ m_wholeStaffSelectionComplete = false;
+
+ delete m_selectionToMerge;
+ const EventSelection *selectionToMerge = 0;
+ if (e->state() & ShiftButton) {
+ m_clickedShift = true;
+ selectionToMerge = m_nParentView->getCurrentSelection();
+ } else {
+ m_clickedShift = false;
+ }
+ m_selectionToMerge =
+ (selectionToMerge ? new EventSelection(*selectionToMerge) : 0);
+
+ m_clickedElement = dynamic_cast<NotationElement*>(element);
+ if (m_clickedElement) {
+ m_selectedStaff = getStaffForElement(m_clickedElement);
+ m_lastDragPitch = -400;
+ m_lastDragTime = m_clickedElement->event()->getNotationAbsoluteTime();
+ } else {
+ m_selectedStaff = 0; // don't know yet; wait until we have an element
+ }
+
+ m_selectionRect->setX(e->x());
+ m_selectionRect->setY(e->y());
+ m_selectionRect->setSize(0, 0);
+
+ m_selectionRect->show();
+ m_updateRect = true;
+ m_startedFineDrag = false;
+
+ //m_parentView->setCursorPosition(p.x());
+}
+
+void NotationSelector::handleRightButtonPress(timeT t,
+ int height,
+ int staffNo,
+ QMouseEvent* e,
+ ViewElement *element)
+{
+ std::cerr << "NotationSelector::handleRightButtonPress" << std::endl;
+
+ const EventSelection *sel = m_nParentView->getCurrentSelection();
+
+ if (!sel || sel->getSegmentEvents().empty()) {
+
+ // if nothing selected, permit the possibility of selecting
+ // something before showing the menu
+
+ if (element) {
+ m_clickedElement = dynamic_cast<NotationElement*>(element);
+ m_selectedStaff = getStaffForElement(m_clickedElement);
+ m_nParentView->setSingleSelectedEvent
+ (m_selectedStaff->getId(), m_clickedElement->event(),
+ true, true);
+ }
+
+ handleLeftButtonPress(t, height, staffNo, e, element);
+ }
+
+ EditTool::handleRightButtonPress(t, height, staffNo, e, element);
+}
+
+void NotationSelector::slotClickTimeout()
+{
+ m_justSelectedBar = false;
+}
+
+void NotationSelector::handleMouseDoubleClick(timeT,
+ int,
+ int staffNo,
+ QMouseEvent* e,
+ ViewElement *element)
+{
+ NOTATION_DEBUG << "NotationSelector::handleMouseDoubleClick" << endl;
+ m_clickedElement = dynamic_cast<NotationElement*>(element);
+
+ NotationStaff *staff = m_nParentView->getNotationStaff(staffNo);
+ if (!staff)
+ return ;
+ m_selectedStaff = staff;
+
+ bool advanced = (e->state() & ShiftButton);
+
+ if (m_clickedElement) {
+
+ emit editElement(staff, m_clickedElement, advanced);
+
+ } else {
+
+ QRect rect = staff->getBarExtents(e->x(), e->y());
+
+ m_selectionRect->setX(rect.x() + 1);
+ m_selectionRect->setY(rect.y());
+ m_selectionRect->setSize(rect.width() - 1, rect.height());
+
+ m_selectionRect->show();
+ m_updateRect = false;
+
+ m_justSelectedBar = true;
+ QTimer::singleShot(QApplication::doubleClickInterval(), this,
+ SLOT(slotClickTimeout()));
+ }
+
+ return ;
+}
+
+void NotationSelector::handleMouseTripleClick(timeT t,
+ int height,
+ int staffNo,
+ QMouseEvent* e,
+ ViewElement *element)
+{
+ if (!m_justSelectedBar)
+ return ;
+ m_justSelectedBar = false;
+
+ NOTATION_DEBUG << "NotationSelector::handleMouseTripleClick" << endl;
+ m_clickedElement = dynamic_cast<NotationElement*>(element);
+
+ NotationStaff *staff = m_nParentView->getNotationStaff(staffNo);
+ if (!staff)
+ return ;
+ m_selectedStaff = staff;
+
+ if (m_clickedElement) {
+
+ // should be safe, as we've already set m_justSelectedBar false
+ handleLeftButtonPress(t, height, staffNo, e, element);
+ return ;
+
+ } else {
+
+ m_selectionRect->setX(staff->getX());
+ m_selectionRect->setY(staff->getY());
+ m_selectionRect->setSize(int(staff->getTotalWidth()) - 1,
+ staff->getTotalHeight() - 1);
+
+ m_selectionRect->show();
+ m_updateRect = false;
+ }
+
+ m_wholeStaffSelectionComplete = true;
+
+ return ;
+}
+
+int NotationSelector::handleMouseMove(timeT, int,
+ QMouseEvent* e)
+{
+ if (!m_updateRect)
+ return RosegardenCanvasView::NoFollow;
+
+ int w = int(e->x() - m_selectionRect->x());
+ int h = int(e->y() - m_selectionRect->y());
+
+ if (m_clickedElement /* && !m_clickedElement->isRest() */) {
+
+ if (m_startedFineDrag) {
+ dragFine(e->x(), e->y(), false);
+ } else if (m_clickedShift) {
+ if (w > 2 || w < -2 || h > 2 || h < -2) {
+ dragFine(e->x(), e->y(), false);
+ }
+ } else if (w > 3 || w < -3 || h > 3 || h < -3) {
+ drag(e->x(), e->y(), false);
+ }
+
+ } else {
+
+ // Qt rectangle dimensions appear to be 1-based
+ if (w > 0)
+ ++w;
+ else
+ --w;
+ if (h > 0)
+ ++h;
+ else
+ --h;
+
+ m_selectionRect->setSize(w, h);
+ setViewCurrentSelection(true);
+ m_nParentView->canvas()->update();
+ }
+
+ return RosegardenCanvasView::FollowHorizontal | RosegardenCanvasView::FollowVertical;
+}
+
+void NotationSelector::handleMouseRelease(timeT, int, QMouseEvent *e)
+{
+ NOTATION_DEBUG << "NotationSelector::handleMouseRelease" << endl;
+ m_updateRect = false;
+
+ NOTATION_DEBUG << "selectionRect width, height: " << m_selectionRect->width()
+ << ", " << m_selectionRect->height() << endl;
+
+ // Test how far we've moved from the original click position -- not
+ // how big the rectangle is (if we were dragging an event, the
+ // rectangle size will still be zero).
+
+ if (((e->x() - m_selectionRect->x()) > -3 &&
+ (e->x() - m_selectionRect->x()) < 3 &&
+ (e->y() - m_selectionRect->y()) > -3 &&
+ (e->y() - m_selectionRect->y()) < 3) &&
+ !m_startedFineDrag) {
+
+ if (m_clickedElement != 0 && m_selectedStaff) {
+
+ // If we didn't drag out a meaningful area, but _did_
+ // click on an individual event, then select just that
+ // event
+
+ if (m_selectionToMerge &&
+ m_selectionToMerge->getSegment() ==
+ m_selectedStaff->getSegment()) {
+
+ // if the event was already part of the selection, we want to
+ // remove it
+ if (m_selectionToMerge->contains(m_clickedElement->event())) {
+ m_selectionToMerge->removeEvent(m_clickedElement->event());
+ } else {
+ m_selectionToMerge->addEvent(m_clickedElement->event());
+ }
+
+ m_nParentView->setCurrentSelection(m_selectionToMerge,
+ true, true);
+ m_selectionToMerge = 0;
+
+ } else {
+
+ m_nParentView->setSingleSelectedEvent
+ (m_selectedStaff->getId(), m_clickedElement->event(),
+ true, true);
+ }
+ /*
+ } else if (m_selectedStaff) {
+
+ // If we clicked on no event but on a staff, move the
+ // insertion cursor to the point where we clicked.
+ // Actually we only really want this to happen if
+ // we aren't double-clicking -- consider using a timer
+ // to establish whether a double-click is going to happen
+
+ m_nParentView->slotSetInsertCursorPosition(e->x(), (int)e->y());
+ */
+ } else {
+ setViewCurrentSelection(false);
+ }
+
+ } else {
+
+ if (m_startedFineDrag) {
+ dragFine(e->x(), e->y(), true);
+ } else if (m_clickedElement /* && !m_clickedElement->isRest() */) {
+ drag(e->x(), e->y(), true);
+ } else {
+ setViewCurrentSelection(false);
+ }
+ }
+
+ m_clickedElement = 0;
+ m_selectionRect->hide();
+ m_wholeStaffSelectionComplete = false;
+ m_nParentView->canvas()->update();
+}
+
+void NotationSelector::drag(int x, int y, bool final)
+{
+ NOTATION_DEBUG << "NotationSelector::drag " << x << ", " << y << endl;
+
+ if (!m_clickedElement || !m_selectedStaff)
+ return ;
+
+ EventSelection *selection = m_nParentView->getCurrentSelection();
+ if (!selection || !selection->contains(m_clickedElement->event())) {
+ selection = new EventSelection(m_selectedStaff->getSegment());
+ selection->addEvent(m_clickedElement->event());
+ }
+ m_nParentView->setCurrentSelection(selection);
+
+ LinedStaff *targetStaff = m_nParentView->getStaffForCanvasCoords(x, y);
+ if (!targetStaff)
+ targetStaff = m_selectedStaff;
+
+ // Calculate time and height
+
+ timeT clickedTime = m_clickedElement->event()->getNotationAbsoluteTime();
+
+ Accidental clickedAccidental = Accidentals::NoAccidental;
+ (void)m_clickedElement->event()->get<String>(ACCIDENTAL, clickedAccidental);
+
+ long clickedPitch = 0;
+ (void)m_clickedElement->event()->get<Int>(PITCH, clickedPitch);
+
+ long clickedHeight = 0;
+ (void)m_clickedElement->event()->get<Int>
+ (NotationProperties::HEIGHT_ON_STAFF, clickedHeight);
+
+ Event *clefEvt = 0, *keyEvt = 0;
+ Clef clef;
+ ::Rosegarden::Key key;
+
+ timeT dragTime = clickedTime;
+ double layoutX = m_clickedElement->getLayoutX();
+ timeT duration = m_clickedElement->getViewDuration();
+
+ NotationElementList::iterator itr =
+ targetStaff->getElementUnderCanvasCoords(x, y, clefEvt, keyEvt);
+
+ if (itr != targetStaff->getViewElementList()->end()) {
+
+ NotationElement *elt = dynamic_cast<NotationElement *>(*itr);
+ dragTime = elt->getViewAbsoluteTime();
+ layoutX = elt->getLayoutX();
+
+ if (elt->isRest() && duration > 0 && elt->getCanvasItem()) {
+
+ double restX = 0, restWidth = 0;
+ elt->getCanvasAirspace(restX, restWidth);
+
+ timeT restDuration = elt->getViewDuration();
+
+ if (restWidth > 0 &&
+ restDuration >= duration * 2) {
+
+ int parts = restDuration / duration;
+ double encroachment = x - restX;
+ NOTATION_DEBUG << "encroachment is " << encroachment << ", restWidth is " << restWidth << endl;
+ int part = (int)((encroachment / restWidth) * parts);
+ if (part >= parts)
+ part = parts - 1;
+
+ dragTime += part * restDuration / parts;
+ layoutX += part * restWidth / parts +
+ (restX - elt->getCanvasX());
+ }
+ }
+ }
+
+ if (clefEvt)
+ clef = Clef(*clefEvt);
+ if (keyEvt)
+ key = ::Rosegarden::Key(*keyEvt);
+
+ int height = targetStaff->getHeightAtCanvasCoords(x, y);
+ int pitch = clickedPitch;
+
+ if (height != clickedHeight)
+ pitch =
+ Pitch
+ (height, clef, key, clickedAccidental).getPerformancePitch();
+
+ if (pitch < clickedPitch) {
+ if (height < -10) {
+ height = -10;
+ pitch = Pitch
+ (height, clef, key, clickedAccidental).getPerformancePitch();
+ }
+ } else if (pitch > clickedPitch) {
+ if (height > 18) {
+ height = 18;
+ pitch = Pitch
+ (height, clef, key, clickedAccidental).getPerformancePitch();
+ }
+ }
+
+ bool singleNonNotePreview = !m_clickedElement->isNote() &&
+ selection->getSegmentEvents().size() == 1;
+
+ if (!final && !singleNonNotePreview) {
+
+ if ((pitch != m_lastDragPitch || dragTime != m_lastDragTime) &&
+ m_clickedElement->isNote()) {
+
+ m_nParentView->showPreviewNote(targetStaff->getId(),
+ layoutX, pitch, height,
+ Note::getNearestNote(duration),
+ m_clickedElement->isGrace());
+ m_lastDragPitch = pitch;
+ m_lastDragTime = dragTime;
+ }
+
+ } else {
+
+ m_nParentView->clearPreviewNote();
+
+ KMacroCommand *command = new KMacroCommand(MoveCommand::getGlobalName());
+ bool haveSomething = false;
+
+ MoveCommand *mc = 0;
+ Event *lastInsertedEvent = 0;
+
+ if (pitch != clickedPitch && m_clickedElement->isNote()) {
+ command->addCommand(new TransposeCommand(pitch - clickedPitch,
+ *selection));
+ haveSomething = true;
+ }
+
+ if (targetStaff != m_selectedStaff) {
+ command->addCommand(new MoveAcrossSegmentsCommand
+ (m_selectedStaff->getSegment(),
+ targetStaff->getSegment(),
+ dragTime - clickedTime + selection->getStartTime(),
+ true,
+ *selection));
+ haveSomething = true;
+ } else {
+ if (dragTime != clickedTime) {
+ mc = new MoveCommand
+ (m_selectedStaff->getSegment(), //!!!sort
+ dragTime - clickedTime, true, *selection);
+ command->addCommand(mc);
+ haveSomething = true;
+ }
+ }
+
+ if (haveSomething) {
+
+ m_nParentView->addCommandToHistory(command);
+
+ if (mc && singleNonNotePreview) {
+
+ lastInsertedEvent = mc->getLastInsertedEvent();
+
+ if (lastInsertedEvent) {
+ m_nParentView->setSingleSelectedEvent(targetStaff->getId(),
+ lastInsertedEvent);
+
+ ViewElementList::iterator vli =
+ targetStaff->findEvent(lastInsertedEvent);
+
+ if (vli != targetStaff->getViewElementList()->end()) {
+ m_clickedElement = dynamic_cast<NotationElement *>(*vli);
+ } else {
+ m_clickedElement = 0;
+ }
+
+ m_selectionRect->setX(x);
+ m_selectionRect->setY(y);
+ }
+ }
+ } else {
+ delete command;
+ }
+ }
+}
+
+void NotationSelector::dragFine(int x, int y, bool final)
+{
+ NOTATION_DEBUG << "NotationSelector::drag " << x << ", " << y << endl;
+
+ if (!m_clickedElement || !m_selectedStaff)
+ return ;
+
+ EventSelection *selection = m_nParentView->getCurrentSelection();
+ if (!selection)
+ selection = new EventSelection(m_selectedStaff->getSegment());
+ if (!selection->contains(m_clickedElement->event()))
+ selection->addEvent(m_clickedElement->event());
+ m_nParentView->setCurrentSelection(selection);
+
+ // Fine drag modifies the DISPLACED_X and DISPLACED_Y properties on
+ // each event. The modifications have to be relative to the previous
+ // values of these properties, not to zero, so for each event we need
+ // to store the previous value at the time the drag starts.
+
+ static PropertyName xProperty("temporary-displaced-x");
+ static PropertyName yProperty("temporary-displaced-y");
+
+ if (!m_startedFineDrag) {
+ // back up original properties
+
+ for (EventSelection::eventcontainer::iterator i =
+ selection->getSegmentEvents().begin();
+ i != selection->getSegmentEvents().end(); ++i) {
+ long prevX = 0, prevY = 0;
+ (*i)->get
+ <Int>(DISPLACED_X, prevX);
+ (*i)->get
+ <Int>(DISPLACED_Y, prevY);
+ (*i)->setMaybe<Int>(xProperty, prevX);
+ (*i)->setMaybe<Int>(yProperty, prevY);
+ }
+
+ m_startedFineDrag = true;
+ }
+
+ // We want the displacements in 1/1000ths of a staff space
+
+ double dx = x - m_selectionRect->x();
+ double dy = y - m_selectionRect->y();
+
+ double noteBodyWidth = m_nParentView->getNotePixmapFactory()->getNoteBodyWidth();
+ double lineSpacing = m_nParentView->getNotePixmapFactory()->getLineSpacing();
+ dx = (1000.0 * dx) / noteBodyWidth;
+ dy = (1000.0 * dy) / lineSpacing;
+
+ if (final) {
+
+ // reset original values (and remove backup values) before
+ // applying command
+
+ for (EventSelection::eventcontainer::iterator i =
+ selection->getSegmentEvents().begin();
+ i != selection->getSegmentEvents().end(); ++i) {
+ long prevX = 0, prevY = 0;
+ (*i)->get
+ <Int>(xProperty, prevX);
+ (*i)->get
+ <Int>(yProperty, prevY);
+ (*i)->setMaybe<Int>(DISPLACED_X, prevX);
+ (*i)->setMaybe<Int>(DISPLACED_Y, prevY);
+ (*i)->unset(xProperty);
+ (*i)->unset(yProperty);
+ }
+
+ IncrementDisplacementsCommand *command = new IncrementDisplacementsCommand
+ (*selection, long(dx), long(dy));
+ m_nParentView->addCommandToHistory(command);
+
+ } else {
+
+ timeT startTime = 0, endTime = 0;
+
+ for (EventSelection::eventcontainer::iterator i =
+ selection->getSegmentEvents().begin();
+ i != selection->getSegmentEvents().end(); ++i) {
+ long prevX = 0, prevY = 0;
+ (*i)->get
+ <Int>(xProperty, prevX);
+ (*i)->get
+ <Int>(yProperty, prevY);
+ (*i)->setMaybe<Int>(DISPLACED_X, prevX + long(dx));
+ (*i)->setMaybe<Int>(DISPLACED_Y, prevY + long(dy));
+ if (i == selection->getSegmentEvents().begin()) {
+ startTime = (*i)->getAbsoluteTime();
+ }
+ endTime = (*i)->getAbsoluteTime() + (*i)->getDuration();
+ }
+
+ if (startTime == endTime)
+ ++endTime;
+ selection->getSegment().updateRefreshStatuses(startTime, endTime);
+ m_nParentView->update();
+ }
+}
+
+void NotationSelector::ready()
+{
+ m_selectionRect = new QCanvasRectangle(m_nParentView->canvas());
+
+ m_selectionRect->hide();
+ m_selectionRect->setPen(GUIPalette::getColour(GUIPalette::SelectionRectangle));
+
+ m_nParentView->setCanvasCursor(Qt::arrowCursor);
+ m_nParentView->setHeightTracking(false);
+}
+
+void NotationSelector::stow()
+{
+ delete m_selectionRect;
+ m_selectionRect = 0;
+ m_nParentView->canvas()->update();
+}
+
+void NotationSelector::slotHideSelection()
+{
+ if (!m_selectionRect)
+ return ;
+ m_selectionRect->hide();
+ m_selectionRect->setSize(0, 0);
+ m_nParentView->canvas()->update();
+}
+
+void NotationSelector::slotInsertSelected()
+{
+ m_nParentView->slotLastNoteAction();
+}
+
+void NotationSelector::slotEraseSelected()
+{
+ m_parentView->actionCollection()->action("erase")->activate();
+}
+
+void NotationSelector::slotCollapseRestsHard()
+{
+ m_parentView->actionCollection()->action("collapse_rests_aggressively")->activate();
+}
+
+void NotationSelector::slotRespellFlat()
+{
+ m_parentView->actionCollection()->action("respell_flat")->activate();
+}
+
+void NotationSelector::slotRespellSharp()
+{
+ m_parentView->actionCollection()->action("respell_sharp")->activate();
+}
+
+void NotationSelector::slotRespellNatural()
+{
+ m_parentView->actionCollection()->action("respell_natural")->activate();
+}
+
+void NotationSelector::slotCollapseNotes()
+{
+ m_parentView->actionCollection()->action("collapse_notes")->activate();
+}
+
+void NotationSelector::slotInterpret()
+{
+ m_parentView->actionCollection()->action("interpret")->activate();
+}
+
+void NotationSelector::slotStaffAbove()
+{
+ m_parentView->actionCollection()->action("move_events_up_staff")->activate();
+}
+
+void NotationSelector::slotStaffBelow()
+{
+ m_parentView->actionCollection()->action("move_events_down_staff")->activate();
+}
+
+void NotationSelector::slotMakeInvisible()
+{
+ m_parentView->actionCollection()->action("make_invisible")->activate();
+}
+
+void NotationSelector::slotMakeVisible()
+{
+ m_parentView->actionCollection()->action("make_visible")->activate();
+}
+
+void NotationSelector::setViewCurrentSelection(bool preview)
+{
+ EventSelection *selection = getSelection();
+
+ if (m_selectionToMerge) {
+ if (selection &&
+ m_selectionToMerge->getSegment() == selection->getSegment()) {
+ selection->addFromSelection(m_selectionToMerge);
+ } else {
+ return ;
+ }
+ }
+
+ m_nParentView->setCurrentSelection(selection, preview, true);
+}
+
+NotationStaff *
+NotationSelector::getStaffForElement(NotationElement *elt)
+{
+ for (int i = 0; i < m_nParentView->getStaffCount(); ++i) {
+ NotationStaff *staff = m_nParentView->getNotationStaff(i);
+ if (staff->getSegment().findSingle(elt->event()) !=
+ staff->getSegment().end())
+ return staff;
+ }
+ return 0;
+}
+
+EventSelection* NotationSelector::getSelection()
+{
+ // If selection rect is not visible or too small,
+ // return 0
+ //
+ if (!m_selectionRect->visible()) return 0;
+
+ // NOTATION_DEBUG << "Selection x,y: " << m_selectionRect->x() << ","
+ // << m_selectionRect->y() << "; w,h: " << m_selectionRect->width() << "," << m_selectionRect->height() << endl;
+
+ if (m_selectionRect->width() > -3 &&
+ m_selectionRect->width() < 3 &&
+ m_selectionRect->height() > -3 &&
+ m_selectionRect->height() < 3) return 0;
+
+ QCanvasItemList itemList = m_selectionRect->collisions(false);
+ QCanvasItemList::Iterator it;
+
+ QRect rect = m_selectionRect->rect().normalize();
+ QCanvasNotationSprite *sprite = 0;
+
+ if (!m_selectedStaff) {
+
+ // Scan the list of collisions, looking for a valid notation
+ // element; if we find one, initialise m_selectedStaff from it.
+ // If we don't find one, we have no selection. This is a little
+ // inefficient but we only do it for the first event in the
+ // selection.
+
+ for (it = itemList.begin(); it != itemList.end(); ++it) {
+
+ if ((sprite = dynamic_cast<QCanvasNotationSprite*>(*it))) {
+
+ NotationElement &el = sprite->getNotationElement();
+
+ NotationStaff *staff = getStaffForElement(&el);
+ if (!staff) continue;
+
+ int x = (int)(*it)->x();
+ bool shifted = false;
+ int nbw = staff->getNotePixmapFactory(false).getNoteBodyWidth();
+
+
+ // #957364 (Notation: Hard to select upper note in
+ // chords of seconds) -- adjust x-coord for shifted
+ // note head
+ if (el.event()->get<Rosegarden::Bool>
+ (staff->getProperties().NOTE_HEAD_SHIFTED, shifted) && shifted) {
+ x += nbw;
+ }
+
+ if (!rect.contains(x, int((*it)->y()), true)) {
+ // #988217 (Notation: Special column of pixels
+ // prevents sweep selection) -- for notes, test
+ // again with centred x-coord
+ if (!el.isNote() || !rect.contains(x + nbw/2, int((*it)->y()), true)) {
+ continue;
+ }
+ }
+
+ m_selectedStaff = staff;
+ break;
+ }
+ }
+ }
+
+ if (!m_selectedStaff) return 0;
+ Segment& originalSegment = m_selectedStaff->getSegment();
+
+ // If we selected the whole staff, force that to happen explicitly
+ // rather than relying on collisions with the rectangle -- because
+ // events way off the currently visible area might not even have
+ // been drawn yet, and so will not appear in the collision list.
+ // (We did still need the collision list to determine which staff
+ // to use though.)
+
+ if (m_wholeStaffSelectionComplete) {
+ EventSelection *selection = new EventSelection(originalSegment,
+ originalSegment.getStartTime(),
+ originalSegment.getEndMarkerTime());
+ return selection;
+ }
+
+ EventSelection* selection = new EventSelection(originalSegment);
+
+ for (it = itemList.begin(); it != itemList.end(); ++it) {
+
+ if ((sprite = dynamic_cast<QCanvasNotationSprite*>(*it))) {
+
+ NotationElement &el = sprite->getNotationElement();
+
+ int x = (int)(*it)->x();
+ bool shifted = false;
+ int nbw = m_selectedStaff->getNotePixmapFactory(false).getNoteBodyWidth();
+
+ // #957364 (Notation: Hard to select upper note in chords
+ // of seconds) -- adjust x-coord for shifted note head
+ if (el.event()->get<Rosegarden::Bool>
+ (m_selectedStaff->getProperties().NOTE_HEAD_SHIFTED, shifted)
+ && shifted) {
+ x += nbw;
+ }
+
+ // check if the element's rect
+ // is actually included in the selection rect.
+ //
+ if (!rect.contains(x, int((*it)->y()), true)) {
+ // #988217 (Notation: Special column of pixels
+ // prevents sweep selection) -- for notes, test again
+ // with centred x-coord
+ if (!el.isNote() || !rect.contains(x + nbw/2, int((*it)->y()), true)) {
+ continue;
+ }
+ }
+
+ // must be in the same segment as we first started on,
+ // we can't select events across multiple segments
+ if (selection->getSegment().findSingle(el.event()) !=
+ selection->getSegment().end()) {
+ selection->addEvent(el.event());
+ }
+ }
+ }
+
+ if (selection->getAddedEvents() > 0) {
+ return selection;
+ } else {
+ delete selection;
+ return 0;
+ }
+}
+
+const QString NotationSelector::ToolName = "notationselector";
+
+}
+#include "NotationSelector.moc"
diff --git a/src/gui/editors/notation/NotationSelector.h b/src/gui/editors/notation/NotationSelector.h
new file mode 100644
index 0000000..7266fd5
--- /dev/null
+++ b/src/gui/editors/notation/NotationSelector.h
@@ -0,0 +1,197 @@
+
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
+
+/*
+ Rosegarden
+ A MIDI and audio sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_NOTATIONSELECTOR_H_
+#define _RG_NOTATIONSELECTOR_H_
+
+#include "NotationTool.h"
+#include "NotationElement.h"
+#include <qstring.h>
+#include "base/Event.h"
+
+
+class QMouseEvent;
+class QCanvasRectangle;
+class m_clickedElement;
+
+
+namespace Rosegarden
+{
+
+class ViewElement;
+class NotationView;
+class NotationStaff;
+class NotationElement;
+class EventSelection;
+class Event;
+
+
+/**
+ * Rectangular note selection
+ */
+class NotationSelector : public NotationTool
+{
+ Q_OBJECT
+
+ friend class NotationToolBox;
+
+public:
+
+ ~NotationSelector();
+
+ virtual void handleLeftButtonPress(timeT,
+ int height,
+ int staffNo,
+ QMouseEvent*,
+ ViewElement* el);
+
+ virtual void handleRightButtonPress(timeT time,
+ int height,
+ int staffNo,
+ QMouseEvent*,
+ ViewElement*);
+
+ virtual int handleMouseMove(timeT,
+ int height,
+ QMouseEvent*);
+
+ virtual void handleMouseRelease(timeT time,
+ int height,
+ QMouseEvent*);
+
+ virtual void handleMouseDoubleClick(timeT,
+ int height,
+ int staffNo,
+ QMouseEvent*,
+ ViewElement*);
+
+ virtual void handleMouseTripleClick(timeT,
+ int height,
+ int staffNo,
+ QMouseEvent*,
+ ViewElement*);
+
+ /**
+ * Create the selection rect
+ *
+ * We need this because NotationView deletes all QCanvasItems
+ * along with it. This happens before the NotationSelector is
+ * deleted, so we can't delete the selection rect in
+ * ~NotationSelector because that leads to double deletion.
+ */
+ virtual void ready();
+
+ /**
+ * Delete the selection rect.
+ */
+ virtual void stow();
+
+ /**
+ * Returns the currently selected events
+ *
+ * The returned result is owned by the caller
+ */
+ EventSelection* getSelection();
+
+ /**
+ * Respond to an event being deleted -- it may be the one the tool
+ * is remembering as the current event.
+ */
+ virtual void handleEventRemoved(Event *event) {
+ if (m_clickedElement && m_clickedElement->event() == event) {
+ m_clickedElement = 0;
+ }
+ }
+
+ static const QString ToolName;
+
+signals:
+ void editElement(NotationStaff *, NotationElement *, bool advanced);
+
+public slots:
+ /**
+ * Hide the selection rectangle
+ *
+ * Should be called after a cut or a copy has been
+ * performed
+ */
+ void slotHideSelection();
+
+ void slotInsertSelected();
+ void slotEraseSelected();
+// void slotCollapseRests();
+ void slotCollapseRestsHard();
+ void slotRespellFlat();
+ void slotRespellSharp();
+ void slotRespellNatural();
+ void slotCollapseNotes();
+ void slotInterpret();
+ void slotStaffAbove();
+ void slotStaffBelow();
+ void slotMakeInvisible();
+ void slotMakeVisible();
+
+ void slotClickTimeout();
+
+protected:
+ NotationSelector(NotationView*);
+
+ /**
+ * Set the current selection on the parent NotationView
+ */
+ void setViewCurrentSelection(bool preview);
+
+ /**
+ * Look up the staff containing the given notation element
+ */
+ NotationStaff *getStaffForElement(NotationElement *elt);
+
+ void drag(int x, int y, bool final);
+ void dragFine(int x, int y, bool final);
+
+ //--------------- Data members ---------------------------------
+
+ QCanvasRectangle* m_selectionRect;
+ bool m_updateRect;
+
+ NotationStaff *m_selectedStaff;
+ NotationElement *m_clickedElement;
+ bool m_clickedShift;
+ bool m_startedFineDrag;
+
+ EventSelection *m_selectionToMerge;
+
+ long m_lastDragPitch;
+ timeT m_lastDragTime;
+
+ bool m_justSelectedBar;
+ bool m_wholeStaffSelectionComplete;
+};
+
+
+
+}
+
+#endif
diff --git a/src/gui/editors/notation/NotationStaff.cpp b/src/gui/editors/notation/NotationStaff.cpp
new file mode 100644
index 0000000..c5219b4
--- /dev/null
+++ b/src/gui/editors/notation/NotationStaff.cpp
@@ -0,0 +1,2300 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
+
+/*
+ Rosegarden
+ A MIDI and audio sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "NotationStaff.h"
+#include "misc/Debug.h"
+#include <kapplication.h>
+
+#include <klocale.h>
+#include "misc/Strings.h"
+#include "document/ConfigGroups.h"
+#include "base/Composition.h"
+#include "base/Device.h"
+#include "base/Event.h"
+#include "base/Exception.h"
+#include "base/Instrument.h"
+#include "base/MidiDevice.h"
+#include "base/MidiTypes.h"
+#include "base/NotationQuantizer.h"
+#include "base/NotationRules.h"
+#include "base/NotationTypes.h"
+#include "base/Profiler.h"
+#include "base/Segment.h"
+#include "base/Selection.h"
+#include "base/SnapGrid.h"
+#include "base/Staff.h"
+#include "base/Studio.h"
+#include "base/Track.h"
+#include "base/ViewElement.h"
+#include "document/RosegardenGUIDoc.h"
+#include "gui/editors/guitar/Chord.h"
+#include "gui/general/LinedStaff.h"
+#include "gui/general/PixmapFunctions.h"
+#include "gui/general/ProgressReporter.h"
+#include "gui/kdeext/QCanvasSimpleSprite.h"
+#include "NotationChord.h"
+#include "NotationElement.h"
+#include "NotationProperties.h"
+#include "NotationView.h"
+#include "NoteFontFactory.h"
+#include "NotePixmapFactory.h"
+#include "NotePixmapParameters.h"
+#include "NoteStyleFactory.h"
+#include <kconfig.h>
+#include <kmessagebox.h>
+#include <qcanvas.h>
+#include <qpainter.h>
+#include <qpoint.h>
+#include <qrect.h>
+
+
+namespace Rosegarden
+{
+
+NotationStaff::NotationStaff(QCanvas *canvas, Segment *segment,
+ SnapGrid *snapGrid, int id,
+ NotationView *view,
+ std::string fontName, int resolution) :
+ ProgressReporter(0),
+ LinedStaff(canvas, segment, snapGrid, id, resolution,
+ resolution / 16 + 1, // line thickness
+ LinearMode, 0, 0, // pageMode, pageWidth and pageHeight set later
+ 0 // row spacing
+ ),
+ m_notePixmapFactory(0),
+ m_graceNotePixmapFactory(0),
+ m_previewSprite(0),
+ m_staffName(0),
+ m_notationView(view),
+ m_legerLineCount(8),
+ m_barNumbersEvery(0),
+ m_colourQuantize(true),
+ m_showUnknowns(true),
+ m_showRanges(true),
+ m_showCollisions(true),
+ m_printPainter(0),
+ m_ready(false),
+ m_lastRenderedBar(0)
+{
+ KConfig *config = kapp->config();
+ config->setGroup(NotationViewConfigGroup);
+ m_colourQuantize = config->readBoolEntry("colourquantize", false);
+
+ // Shouldn't change these during the lifetime of the staff, really:
+ m_showUnknowns = config->readBoolEntry("showunknowns", false);
+ m_showRanges = config->readBoolEntry("showranges", true);
+ m_showCollisions = config->readBoolEntry("showcollisions", true);
+
+ m_keySigCancelMode = config->readNumEntry("keysigcancelmode", 1);
+
+ changeFont(fontName, resolution);
+}
+
+NotationStaff::~NotationStaff()
+{
+ deleteTimeSignatures();
+ delete m_notePixmapFactory;
+ delete m_graceNotePixmapFactory;
+}
+
+void
+NotationStaff::changeFont(std::string fontName, int size)
+{
+ setResolution(size);
+
+ delete m_notePixmapFactory;
+ m_notePixmapFactory = new NotePixmapFactory(fontName, size);
+
+ std::vector<int> sizes = NoteFontFactory::getScreenSizes(fontName);
+ int graceSize = size;
+ for (unsigned int i = 0; i < sizes.size(); ++i) {
+ if (sizes[i] == size || sizes[i] > size*3 / 4)
+ break;
+ graceSize = sizes[i];
+ }
+ delete m_graceNotePixmapFactory;
+ m_graceNotePixmapFactory = new NotePixmapFactory(fontName, graceSize);
+
+ setLineThickness(m_notePixmapFactory->getStaffLineThickness());
+}
+
+void
+NotationStaff::insertTimeSignature(double layoutX,
+ const TimeSignature &timeSig)
+{
+ if (timeSig.isHidden())
+ return ;
+
+ m_notePixmapFactory->setSelected(false);
+ QCanvasPixmap *pixmap = m_notePixmapFactory->makeTimeSigPixmap(timeSig);
+ QCanvasTimeSigSprite *sprite =
+ new QCanvasTimeSigSprite(layoutX, pixmap, m_canvas);
+
+ LinedStaffCoords sigCoords =
+ getCanvasCoordsForLayoutCoords(layoutX, getLayoutYForHeight(4));
+
+ sprite->move(sigCoords.first, (double)sigCoords.second);
+ sprite->show();
+ m_timeSigs.insert(sprite);
+}
+
+void
+NotationStaff::deleteTimeSignatures()
+{
+ // NOTATION_DEBUG << "NotationStaff::deleteTimeSignatures()" << endl;
+
+ for (SpriteSet::iterator i = m_timeSigs.begin();
+ i != m_timeSigs.end(); ++i) {
+ delete *i;
+ }
+
+ m_timeSigs.clear();
+}
+
+void
+NotationStaff::insertRepeatedClefAndKey(double layoutX, int barNo)
+{
+ bool needClef = false, needKey = false;
+ timeT t;
+
+ timeT barStart = getSegment().getComposition()->getBarStart(barNo);
+
+ Clef clef = getSegment().getClefAtTime(barStart, t);
+ if (t < barStart)
+ needClef = true;
+
+ ::Rosegarden::Key key = getSegment().getKeyAtTime(barStart, t);
+ if (t < barStart)
+ needKey = true;
+
+ double dx = m_notePixmapFactory->getBarMargin() / 2;
+
+ if (!m_notationView->isInPrintMode())
+ m_notePixmapFactory->setShaded(true);
+
+ if (needClef) {
+
+ int layoutY = getLayoutYForHeight(clef.getAxisHeight());
+
+ LinedStaffCoords coords =
+ getCanvasCoordsForLayoutCoords(layoutX + dx, layoutY);
+
+ QCanvasPixmap *pixmap = m_notePixmapFactory->makeClefPixmap(clef);
+
+ QCanvasNonElementSprite *sprite =
+ new QCanvasNonElementSprite(pixmap, m_canvas);
+
+ sprite->move(coords.first, coords.second);
+ sprite->show();
+ m_repeatedClefsAndKeys.insert(sprite);
+
+ dx += pixmap->width() + m_notePixmapFactory->getNoteBodyWidth() * 2 / 3;
+ }
+
+ if (needKey) {
+
+ int layoutY = getLayoutYForHeight(12);
+
+ LinedStaffCoords coords =
+ getCanvasCoordsForLayoutCoords(layoutX + dx, layoutY);
+
+ QCanvasPixmap *pixmap = m_notePixmapFactory->makeKeyPixmap(key, clef);
+
+ QCanvasNonElementSprite *sprite =
+ new QCanvasNonElementSprite(pixmap, m_canvas);
+
+ sprite->move(coords.first, coords.second);
+ sprite->show();
+ m_repeatedClefsAndKeys.insert(sprite);
+
+ dx += pixmap->width();
+ }
+
+ /* attempt to blot out things like slurs & ties that overrun this area: doesn't work
+
+ if (m_notationView->isInPrintMode() && (needClef || needKey)) {
+
+ int layoutY = getLayoutYForHeight(14);
+ int h = getLayoutYForHeight(-8) - layoutY;
+
+ LinedStaffCoords coords =
+ getCanvasCoordsForLayoutCoords(layoutX, layoutY);
+
+ QCanvasRectangle *rect = new QCanvasRectangle(coords.first, coords.second,
+ dx, h, m_canvas);
+ rect->setPen(Qt::black);
+ rect->setBrush(Qt::white);
+ rect->setZ(1);
+ rect->show();
+
+ m_repeatedClefsAndKeys.insert(rect);
+ }
+ */
+
+ m_notePixmapFactory->setShaded(false);
+}
+
+void
+NotationStaff::deleteRepeatedClefsAndKeys()
+{
+ for (ItemSet::iterator i = m_repeatedClefsAndKeys.begin();
+ i != m_repeatedClefsAndKeys.end(); ++i) {
+ delete *i;
+ }
+
+ m_repeatedClefsAndKeys.clear();
+}
+
+void
+NotationStaff::drawStaffName()
+{
+ delete m_staffName;
+
+ m_staffNameText =
+ getSegment().getComposition()->
+ getTrackById(getSegment().getTrack())->getLabel();
+
+ QCanvasPixmap *map =
+ m_notePixmapFactory->makeTextPixmap
+ (Text(m_staffNameText, Text::StaffName));
+
+ m_staffName = new QCanvasStaffNameSprite(map, m_canvas);
+
+ int layoutY = getLayoutYForHeight(3);
+ LinedStaffCoords coords = getCanvasCoordsForLayoutCoords(0, layoutY);
+ m_staffName->move(getX() + getMargin() + m_notePixmapFactory->getNoteBodyWidth(),
+ coords.second - map->height() / 2);
+ m_staffName->show();
+}
+
+bool
+NotationStaff::isStaffNameUpToDate()
+{
+ return (m_staffNameText ==
+ getSegment().getComposition()->
+ getTrackById(getSegment().getTrack())->getLabel());
+}
+
+timeT
+NotationStaff::getTimeAtCanvasCoords(double cx, int cy) const
+{
+ LinedStaffCoords layoutCoords = getLayoutCoordsForCanvasCoords(cx, cy);
+ RulerScale * rs = m_notationView->getHLayout();
+ return rs->getTimeForX(layoutCoords.first);
+}
+
+void
+NotationStaff::getClefAndKeyAtCanvasCoords(double cx, int cy,
+ Clef &clef,
+ ::Rosegarden::Key &key) const
+{
+ LinedStaffCoords layoutCoords = getLayoutCoordsForCanvasCoords(cx, cy);
+ int i;
+
+ for (i = 0; i < m_clefChanges.size(); ++i) {
+ if (m_clefChanges[i].first > layoutCoords.first)
+ break;
+ clef = m_clefChanges[i].second;
+ }
+
+ for (i = 0; i < m_keyChanges.size(); ++i) {
+ if (m_keyChanges[i].first > layoutCoords.first)
+ break;
+ key = m_keyChanges[i].second;
+ }
+}
+
+ViewElementList::iterator
+NotationStaff::getClosestElementToLayoutX(double x,
+ Event *&clef,
+ Event *&key,
+ bool notesAndRestsOnly,
+ int proximityThreshold)
+{
+ START_TIMING;
+
+ double minDist = 10e9, prevDist = 10e9;
+
+ NotationElementList *notes = getViewElementList();
+ NotationElementList::iterator it, result;
+
+ // TODO: this is grossly inefficient
+
+ for (it = notes->begin(); it != notes->end(); ++it) {
+ NotationElement *el = static_cast<NotationElement*>(*it);
+
+ bool before = ((*it)->getLayoutX() < x);
+
+ if (!el->isNote() && !el->isRest()) {
+ if (before) {
+ if ((*it)->event()->isa(Clef::EventType)) {
+ clef = (*it)->event();
+ } else if ((*it)->event()->isa(::Rosegarden::Key::EventType)) {
+ key = (*it)->event();
+ }
+ }
+ if (notesAndRestsOnly)
+ continue;
+ }
+
+ double dx = x - (*it)->getLayoutX();
+ if (dx < 0)
+ dx = -dx;
+
+ if (dx < minDist) {
+ minDist = dx;
+ result = it;
+ } else if (!before) {
+ break;
+ }
+
+ prevDist = dx;
+ }
+
+ if (proximityThreshold > 0 && minDist > proximityThreshold) {
+ NOTATION_DEBUG << "NotationStaff::getClosestElementToLayoutX() : element is too far away : "
+ << minDist << endl;
+ return notes->end();
+ }
+
+ NOTATION_DEBUG << "NotationStaff::getClosestElementToLayoutX: found element at layout " << (*result)->getLayoutX() << " - we're at layout " << x << endl;
+
+ PRINT_ELAPSED("NotationStaff::getClosestElementToLayoutX");
+
+ return result;
+}
+
+ViewElementList::iterator
+NotationStaff::getElementUnderLayoutX(double x,
+ Event *&clef,
+ Event *&key)
+{
+ NotationElementList *notes = getViewElementList();
+ NotationElementList::iterator it;
+
+ // TODO: this is grossly inefficient
+
+ for (it = notes->begin(); it != notes->end(); ++it) {
+ NotationElement* el = static_cast<NotationElement*>(*it);
+
+ bool before = ((*it)->getLayoutX() <= x);
+
+ if (!el->isNote() && !el->isRest()) {
+ if (before) {
+ if ((*it)->event()->isa(Clef::EventType)) {
+ clef = (*it)->event();
+ } else if ((*it)->event()->isa(::Rosegarden::Key::EventType)) {
+ key = (*it)->event();
+ }
+ }
+ }
+
+ double airX, airWidth;
+ el->getLayoutAirspace(airX, airWidth);
+ if (x >= airX && x < airX + airWidth) {
+ return it;
+ } else if (!before) {
+ if (it != notes->begin())
+ --it;
+ return it;
+ }
+ }
+
+ return notes->end();
+}
+
+std::string
+NotationStaff::getNoteNameAtCanvasCoords(double x, int y,
+ Accidental) const
+{
+ Clef clef;
+ ::Rosegarden::Key key;
+ getClefAndKeyAtCanvasCoords(x, y, clef, key);
+
+ KConfig *config = kapp->config();
+ config->setGroup(GeneralOptionsConfigGroup);
+ int baseOctave = config->readNumEntry("midipitchoctave", -2);
+
+ Pitch p(getHeightAtCanvasCoords(x, y), clef, key);
+ //!!! i18n() how?
+ return p.getAsString(key.isSharp(), true, baseOctave);
+}
+
+void
+NotationStaff::renderElements(NotationElementList::iterator from,
+ NotationElementList::iterator to)
+{
+ // NOTATION_DEBUG << "NotationStaff " << this << "::renderElements()" << endl;
+ Profiler profiler("NotationStaff::renderElements");
+
+ emit setOperationName(i18n("Rendering staff %1...").arg(getId() + 1));
+ emit setProgress(0);
+
+ throwIfCancelled();
+
+ // These are only used when rendering keys, and we don't have the
+ // right start data here so we choose not to render keys at all in
+ // this method (see below) so that we can pass bogus clef and key
+ // data to renderSingleElement
+ Clef currentClef;
+ ::Rosegarden::Key currentKey;
+
+ int elementCount = 0;
+ timeT endTime =
+ (to != getViewElementList()->end() ? (*to)->getViewAbsoluteTime() :
+ getSegment().getEndMarkerTime());
+ timeT startTime = (from != to ? (*from)->getViewAbsoluteTime() : endTime);
+
+ for (NotationElementList::iterator it = from, nextIt = from;
+ it != to; it = nextIt) {
+
+ ++nextIt;
+
+ if (isDirectlyPrintable(*it)) {
+ // notes are renderable direct to the printer, so don't render
+ // them to the canvas here
+ continue;
+ }
+
+ if ((*it)->event()->isa(::Rosegarden::Key::EventType)) {
+ // force rendering in positionElements instead
+ NotationElement* el = static_cast<NotationElement*>(*it);
+ el->removeCanvasItem();
+ continue;
+ }
+
+ bool selected = isSelected(it);
+ // NOTATION_DEBUG << "Rendering at " << (*it)->getAbsoluteTime()
+ // << " (selected = " << selected << ")" << endl;
+
+ renderSingleElement(it, currentClef, currentKey, selected);
+
+ if ((endTime > startTime) &&
+ (++elementCount % 200 == 0)) {
+
+ timeT myTime = (*it)->getViewAbsoluteTime();
+ emit setProgress((myTime - startTime) * 100 / (endTime - startTime));
+ throwIfCancelled();
+ }
+ }
+
+ // NOTATION_DEBUG << "NotationStaff " << this << "::renderElements: "
+ // << elementCount << " elements rendered" << endl;
+}
+
+void
+NotationStaff::renderPrintable(timeT from, timeT to)
+{
+ if (!m_printPainter)
+ return ;
+
+ Profiler profiler("NotationStaff::renderElements");
+
+ emit setOperationName(i18n("Rendering notes on staff %1...").arg(getId() + 1));
+ emit setProgress(0);
+
+ throwIfCancelled();
+
+ // These are only used when rendering keys, and we don't do that
+ // here, so we don't care what they are
+ Clef currentClef;
+ ::Rosegarden::Key currentKey;
+
+ Composition *composition = getSegment().getComposition();
+ NotationElementList::iterator beginAt =
+ getViewElementList()->findTime(composition->getBarStartForTime(from));
+ NotationElementList::iterator endAt =
+ getViewElementList()->findTime(composition->getBarEndForTime(to));
+
+ int elementCount = 0;
+
+ for (NotationElementList::iterator it = beginAt, nextIt = beginAt;
+ it != endAt; it = nextIt) {
+
+ ++nextIt;
+
+ if (!isDirectlyPrintable(*it)) {
+ continue;
+ }
+
+ bool selected = isSelected(it);
+ // NOTATION_DEBUG << "Rendering at " << (*it)->getAbsoluteTime()
+ // << " (selected = " << selected << ")" << endl;
+
+ renderSingleElement(it, currentClef, currentKey, selected);
+
+ if ((to > from) && (++elementCount % 200 == 0)) {
+
+ timeT myTime = (*it)->getViewAbsoluteTime();
+ emit setProgress((myTime - from) * 100 / (to - from));
+ throwIfCancelled();
+ }
+ }
+
+ // NOTATION_DEBUG << "NotationStaff " << this << "::renderElements: "
+ // << elementCount << " elements rendered" << endl;
+}
+
+const NotationProperties &
+NotationStaff::getProperties() const
+{
+ return m_notationView->getProperties();
+}
+
+void
+NotationStaff::positionElements(timeT from, timeT to)
+{
+ // NOTATION_DEBUG << "NotationStaff " << this << "::positionElements()"
+ // << from << " -> " << to << endl;
+ Profiler profiler("NotationStaff::positionElements");
+
+ // Following 4 lines are a workaround to not have m_clefChanges and
+ // m_keyChanges truncated when positionElements() is called with
+ // args outside current segment.
+ // Maybe a better fix would be not to call positionElements() with
+ // such args ...
+ int startTime = getSegment().getStartTime();
+ if (from < startTime) from = startTime;
+ if (to < startTime) to = startTime;
+ if (to == from) return;
+
+ emit setOperationName(i18n("Positioning staff %1...").arg(getId() + 1));
+ emit setProgress(0);
+ throwIfCancelled();
+
+ const NotationProperties &properties(getProperties());
+
+ int elementsPositioned = 0;
+ int elementsRendered = 0; // diagnostic
+
+ Composition *composition = getSegment().getComposition();
+
+ timeT nextBarTime = composition->getBarEndForTime(to);
+
+ NotationElementList::iterator beginAt =
+ getViewElementList()->findTime(composition->getBarStartForTime(from));
+
+ NotationElementList::iterator endAt =
+ getViewElementList()->findTime(composition->getBarEndForTime(to));
+
+ if (beginAt == getViewElementList()->end())
+ return ;
+
+ truncateClefsAndKeysAt(static_cast<int>((*beginAt)->getLayoutX()));
+
+ Clef currentClef; // used for rendering key sigs
+ bool haveCurrentClef = false;
+
+ ::Rosegarden::Key currentKey;
+ bool haveCurrentKey = false;
+
+ for (NotationElementList::iterator it = beginAt, nextIt = beginAt;
+ it != endAt; it = nextIt) {
+
+ NotationElement * el = static_cast<NotationElement*>(*it);
+
+ ++nextIt;
+
+ if (el->event()->isa(Clef::EventType)) {
+
+ currentClef = Clef(*el->event());
+ m_clefChanges.push_back(ClefChange(int(el->getLayoutX()),
+ currentClef));
+ haveCurrentClef = true;
+
+ } else if (el->event()->isa(::Rosegarden::Key::EventType)) {
+
+ m_keyChanges.push_back
+ (KeyChange(int(el->getLayoutX()),
+ ::Rosegarden::Key(*el->event())));
+
+ if (!haveCurrentClef) { // need this to know how to present the key
+ currentClef = getSegment().getClefAtTime
+ (el->event()->getAbsoluteTime());
+ haveCurrentClef = true;
+ }
+
+ if (!haveCurrentKey) { // stores the key _before_ this one
+ currentKey = getSegment().getKeyAtTime
+ (el->event()->getAbsoluteTime() - 1);
+ haveCurrentKey = true;
+ }
+
+ } else if (isDirectlyPrintable(el)) {
+ // these are rendered by renderPrintable for printing
+ continue;
+ }
+
+ bool selected = isSelected(it);
+ bool needNewSprite = (selected != el->isSelected());
+
+ if (!el->getCanvasItem()) {
+
+ needNewSprite = true;
+
+ } else if (el->isNote() && !el->isRecentlyRegenerated()) {
+
+ // If the note's y-coordinate has changed, we should
+ // redraw it -- its stem direction may have changed, or it
+ // may need leger lines. This will happen e.g. if the
+ // user inserts a new clef; unfortunately this means
+ // inserting clefs is rather slow.
+
+ needNewSprite = needNewSprite || !elementNotMovedInY(el);
+
+ if (!needNewSprite) {
+
+ // If the event is a beamed or tied-forward note, then
+ // we might need a new sprite if the distance from
+ // this note to the next has changed (because the beam
+ // or tie is part of the note's sprite).
+
+ bool spanning = false;
+ (void)(el->event()->get
+ <Bool>
+ (properties.BEAMED, spanning));
+ if (!spanning) {
+ (void)(el->event()->get
+ <Bool>(BaseProperties::TIED_FORWARD, spanning));
+ }
+
+ if (spanning) {
+ needNewSprite =
+ (el->getViewAbsoluteTime() < nextBarTime ||
+ !elementShiftedOnly(it));
+ }
+ }
+
+ } else if (el->event()->isa(Indication::EventType) &&
+ !el->isRecentlyRegenerated()) {
+ needNewSprite = true;
+ }
+
+ if (needNewSprite) {
+ renderSingleElement(it, currentClef, currentKey, selected);
+ ++elementsRendered;
+ }
+
+ if (el->event()->isa(::Rosegarden::Key::EventType)) {
+ // update currentKey after rendering, not before
+ currentKey = ::Rosegarden::Key(*el->event());
+ }
+
+ if (!needNewSprite) {
+ LinedStaffCoords coords = getCanvasCoordsForLayoutCoords
+ (el->getLayoutX(), (int)el->getLayoutY());
+ el->reposition(coords.first, (double)coords.second);
+ }
+
+ el->setSelected(selected);
+
+ if ((to > from) &&
+ (++elementsPositioned % 300 == 0)) {
+ timeT myTime = el->getViewAbsoluteTime();
+ emit setProgress((myTime - from) * 100 / (to - from));
+ throwIfCancelled();
+ }
+ }
+
+ // NOTATION_DEBUG << "NotationStaff " << this << "::positionElements "
+ // << from << " -> " << to << ": "
+ // << elementsPositioned << " elements positioned, "
+ // << elementsRendered << " re-rendered"
+ // << endl;
+
+ // NotePixmapFactory::dumpStats(std::cerr);
+}
+
+void
+NotationStaff::truncateClefsAndKeysAt(int x)
+{
+ for (FastVector<ClefChange>::iterator i = m_clefChanges.begin();
+ i != m_clefChanges.end(); ++i) {
+ if (i->first >= x) {
+ m_clefChanges.erase(i, m_clefChanges.end());
+ break;
+ }
+ }
+
+ for (FastVector<KeyChange>::iterator i = m_keyChanges.begin();
+ i != m_keyChanges.end(); ++i) {
+ if (i->first >= x) {
+ m_keyChanges.erase(i, m_keyChanges.end());
+ break;
+ }
+ }
+}
+
+NotationElementList::iterator
+NotationStaff::findUnchangedBarStart(timeT from)
+{
+ NotationElementList *nel = (NotationElementList *)getViewElementList();
+
+ // Track back bar-by-bar until we find one whose start position
+ // hasn't changed
+
+ NotationElementList::iterator beginAt = nel->begin();
+ do {
+ from = getSegment().getComposition()->getBarStartForTime(from - 1);
+ beginAt = nel->findTime(from);
+ } while (beginAt != nel->begin() &&
+ (beginAt == nel->end() || !elementNotMoved(static_cast<NotationElement*>(*beginAt))));
+
+ return beginAt;
+}
+
+NotationElementList::iterator
+NotationStaff::findUnchangedBarEnd(timeT to)
+{
+ NotationElementList *nel = (NotationElementList *)getViewElementList();
+
+ // Track forward to the end, similarly. Here however it's very
+ // common for all the positions to have changed right up to the
+ // end of the piece; so we save time by assuming that to be the
+ // case if we get more than (arbitrary) 3 changed bars.
+
+ // We also record the start of the bar following the changed
+ // section, for later use.
+
+ NotationElementList::iterator endAt = nel->end();
+
+ int changedBarCount = 0;
+ NotationElementList::iterator candidate = nel->end();
+ do {
+ candidate = nel->findTime(getSegment().getBarEndForTime(to));
+ if (candidate != nel->end()) {
+ to = (*candidate)->getViewAbsoluteTime();
+ }
+ ++changedBarCount;
+ } while (changedBarCount < 4 &&
+ candidate != nel->end() &&
+ !elementNotMoved(static_cast<NotationElement*>(*candidate)));
+
+ if (changedBarCount < 4)
+ return candidate;
+ else
+ return endAt;
+}
+
+bool
+NotationStaff::elementNotMoved(NotationElement *elt)
+{
+ if (!elt->getCanvasItem())
+ return false;
+
+ LinedStaffCoords coords = getCanvasCoordsForLayoutCoords
+ (elt->getLayoutX(), (int)elt->getLayoutY());
+
+ bool ok =
+ (int)(elt->getCanvasX()) == (int)(coords.first) &&
+ (int)(elt->getCanvasY()) == (int)(coords.second);
+
+ if (!ok) {
+ NOTATION_DEBUG
+ << "elementNotMoved: elt at " << elt->getViewAbsoluteTime() <<
+ ", ok is " << ok << endl;
+ NOTATION_DEBUG << "(cf " << (int)(elt->getCanvasX()) << " vs "
+ << (int)(coords.first) << ", "
+ << (int)(elt->getCanvasY()) << " vs "
+ << (int)(coords.second) << ")" << endl;
+ } else {
+ NOTATION_DEBUG << "elementNotMoved: elt at " << elt->getViewAbsoluteTime()
+ << " is ok" << endl;
+ }
+
+ return ok;
+}
+
+bool
+NotationStaff::elementNotMovedInY(NotationElement *elt)
+{
+ if (!elt->getCanvasItem())
+ return false;
+
+ LinedStaffCoords coords = getCanvasCoordsForLayoutCoords
+ (elt->getLayoutX(), (int)elt->getLayoutY());
+
+ bool ok = (int)(elt->getCanvasY()) == (int)(coords.second);
+
+ // if (!ok) {
+ // NOTATION_DEBUG
+ // << "elementNotMovedInY: elt at " << elt->getAbsoluteTime() <<
+ // ", ok is " << ok << endl;
+ // NOTATION_DEBUG << "(cf " << (int)(elt->getCanvasY()) << " vs "
+ // << (int)(coords.second) << ")" << std::endl;
+ // }
+ return ok;
+}
+
+bool
+NotationStaff::elementShiftedOnly(NotationElementList::iterator i)
+{
+ int shift = 0;
+ bool ok = false;
+
+ for (NotationElementList::iterator j = i;
+ j != getViewElementList()->end(); ++j) {
+
+ NotationElement *elt = static_cast<NotationElement*>(*j);
+ if (!elt->getCanvasItem())
+ break;
+
+ LinedStaffCoords coords = getCanvasCoordsForLayoutCoords
+ (elt->getLayoutX(), (int)elt->getLayoutY());
+
+ // regard any shift in y as suspicious
+ if ((int)(elt->getCanvasY()) != (int)(coords.second))
+ break;
+
+ int myShift = (int)(elt->getCanvasX()) - (int)(coords.first);
+ if (j == i)
+ shift = myShift;
+ else if (myShift != shift)
+ break;
+
+ if (elt->getViewAbsoluteTime() > (*i)->getViewAbsoluteTime()) {
+ // all events up to and including this one have passed
+ ok = true;
+ break;
+ }
+ }
+
+ if (!ok) {
+ NOTATION_DEBUG
+ << "elementShiftedOnly: elt at " << (*i)->getViewAbsoluteTime()
+ << ", ok is " << ok << endl;
+ }
+
+ return ok;
+}
+
+bool
+NotationStaff::isDirectlyPrintable(ViewElement *velt)
+{
+ if (!m_printPainter)
+ return false;
+ return (velt->event()->isa(Note::EventType) ||
+ velt->event()->isa(Note::EventRestType) ||
+ velt->event()->isa(Text::EventType) ||
+ velt->event()->isa(Indication::EventType));
+}
+
+void
+NotationStaff::renderSingleElement(ViewElementList::iterator &vli,
+ const Clef &currentClef,
+ const ::Rosegarden::Key &currentKey,
+ bool selected)
+{
+ const NotationProperties &properties(getProperties());
+ static NotePixmapParameters restParams(Note::Crotchet, 0);
+
+ NotationElement* elt = static_cast<NotationElement*>(*vli);
+
+ bool invisible = false;
+ if (elt->event()->get
+ <Bool>(BaseProperties::INVISIBLE, invisible) && invisible) {
+ if (m_printPainter)
+ return ;
+ KConfig *config = kapp->config();
+ config->setGroup("Notation Options");
+ bool showInvisibles = config->readBoolEntry("showinvisibles", true);
+ if (!showInvisibles)
+ return ;
+ }
+
+ try {
+ m_notePixmapFactory->setNoteStyle
+ (NoteStyleFactory::getStyleForEvent(elt->event()));
+
+ } catch (NoteStyleFactory::StyleUnavailable u) {
+
+ std::cerr << "WARNING: Note style unavailable: "
+ << u.getMessage() << std::endl;
+
+ static bool warned = false;
+ if (!warned) {
+ KMessageBox::error(0, i18n(strtoqstr(u.getMessage())));
+ warned = true;
+ }
+ }
+
+ try {
+
+ QCanvasPixmap *pixmap = 0;
+
+ m_notePixmapFactory->setSelected(selected);
+ m_notePixmapFactory->setShaded(invisible);
+ int z = selected ? 3 : 0;
+
+ // these are actually only used for the printer stuff
+ LinedStaffCoords coords;
+ if (m_printPainter)
+ coords = getCanvasCoordsForLayoutCoords
+ (elt->getLayoutX(), (int)elt->getLayoutY());
+
+ FitPolicy policy = PretendItFittedAllAlong;
+
+ if (elt->isNote()) {
+
+ renderNote(vli);
+
+ } else if (elt->isRest()) {
+
+ bool ignoreRest = false;
+ // NotationHLayout sets this property if it finds the rest
+ // in the middle of a chord -- Quantizer still sometimes gets
+ // this wrong
+ elt->event()->get
+ <Bool>(properties.REST_TOO_SHORT, ignoreRest);
+
+ if (!ignoreRest) {
+
+ Note::Type note = elt->event()->get
+ <Int>(BaseProperties::NOTE_TYPE);
+ int dots = elt->event()->get
+ <Int>(BaseProperties::NOTE_DOTS);
+ restParams.setNoteType(note);
+ restParams.setDots(dots);
+ setTuplingParameters(elt, restParams);
+ restParams.setQuantized(false);
+ bool restOutside = false;
+ elt->event()->get
+ <Bool>(properties.REST_OUTSIDE_STAVE,
+ restOutside);
+ restParams.setRestOutside(restOutside);
+ if (restOutside) {
+ NOTATION_DEBUG << "NotationStaff::renderSingleElement() : rest outside staff" << endl;
+ if (note == Note::DoubleWholeNote) {
+ NOTATION_DEBUG << "NotationStaff::renderSingleElement() : breve rest needs leger lines" << endl;
+ restParams.setLegerLines(5);
+ }
+ }
+
+ if (m_printPainter) {
+ m_notePixmapFactory->drawRest
+ (restParams,
+ *m_printPainter, int(coords.first), coords.second);
+ } else {
+ pixmap = m_notePixmapFactory->makeRestPixmap(restParams);
+ }
+ }
+
+ } else if (elt->event()->isa(Clef::EventType)) {
+
+ pixmap = m_notePixmapFactory->makeClefPixmap
+ (Clef(*elt->event()));
+
+ } else if (elt->event()->isa(::Rosegarden::Key::EventType)) {
+
+ ::Rosegarden::Key key(*elt->event());
+ ::Rosegarden::Key cancelKey = currentKey;
+
+ if (m_keySigCancelMode == 0) { // only when entering C maj / A min
+
+ if (key.getAccidentalCount() != 0)
+ cancelKey = ::Rosegarden::Key();
+
+ } else if (m_keySigCancelMode == 1) { // only when reducing acc count
+
+ if (!(key.isSharp() == cancelKey.isSharp() &&
+ key.getAccidentalCount() < cancelKey.getAccidentalCount())) {
+ cancelKey = ::Rosegarden::Key();
+ }
+ }
+
+ pixmap = m_notePixmapFactory->makeKeyPixmap
+ (key, currentClef, cancelKey);
+
+ } else if (elt->event()->isa(Text::EventType)) {
+
+ policy = MoveBackToFit;
+
+ if (elt->event()->has(Text::TextTypePropertyName) &&
+ elt->event()->get
+ <String>
+ (Text::TextTypePropertyName) ==
+ Text::Annotation &&
+ !m_notationView->areAnnotationsVisible()) {
+
+ // nothing I guess
+
+ }
+ else if (elt->event()->has(Text::TextTypePropertyName) &&
+ elt->event()->get
+ <String>
+ (Text::TextTypePropertyName) ==
+ Text::LilyPondDirective &&
+ !m_notationView->areLilyPondDirectivesVisible()) {
+
+ // nothing here either
+
+ }
+ else {
+
+ try {
+ if (m_printPainter) {
+ Text text(*elt->event());
+ int length = m_notePixmapFactory->getTextWidth(text);
+ for (double w = -1, inc = 0; w != 0; inc += w) {
+ w = setPainterClipping(m_printPainter,
+ elt->getLayoutX(),
+ int(elt->getLayoutY()),
+ int(inc), length, coords,
+ policy);
+ m_notePixmapFactory->drawText
+ (text, *m_printPainter, int(coords.first), coords.second);
+ m_printPainter->restore();
+ }
+ } else {
+ pixmap = m_notePixmapFactory->makeTextPixmap
+ (Text(*elt->event()));
+ }
+ } catch (Exception e) { // Text ctor failed
+ NOTATION_DEBUG << "Bad text event" << endl;
+ }
+ }
+
+ } else if (elt->event()->isa(Indication::EventType)) {
+
+ policy = SplitToFit;
+
+ try {
+ Indication indication(*elt->event());
+
+ timeT indicationDuration = indication.getIndicationDuration();
+ timeT indicationEndTime =
+ elt->getViewAbsoluteTime() + indicationDuration;
+
+ NotationElementList::iterator indicationEnd =
+ getViewElementList()->findTime(indicationEndTime);
+
+ std::string indicationType = indication.getIndicationType();
+
+ int length, y1;
+
+ if ((indicationType == Indication::Slur ||
+ indicationType == Indication::PhrasingSlur) &&
+ indicationEnd != getViewElementList()->begin()) {
+ --indicationEnd;
+ }
+
+ if ((indicationType != Indication::Slur &&
+ indicationType != Indication::PhrasingSlur) &&
+ indicationEnd != getViewElementList()->begin() &&
+ (indicationEnd == getViewElementList()->end() ||
+ indicationEndTime ==
+ getSegment().getBarStartForTime(indicationEndTime))) {
+
+ while (indicationEnd == getViewElementList()->end() ||
+ (*indicationEnd)->getViewAbsoluteTime() >= indicationEndTime)
+ --indicationEnd;
+
+ double x, w;
+ static_cast<NotationElement *>(*indicationEnd)->
+ getLayoutAirspace(x, w);
+ length = (int)(x + w - elt->getLayoutX() -
+ m_notePixmapFactory->getBarMargin());
+
+ } else {
+
+ length = (int)((*indicationEnd)->getLayoutX() -
+ elt->getLayoutX());
+
+ if (indication.isOttavaType()) {
+ length -= m_notePixmapFactory->getNoteBodyWidth();
+ }
+ }
+
+ y1 = (int)(*indicationEnd)->getLayoutY();
+
+ if (length < m_notePixmapFactory->getNoteBodyWidth()) {
+ length = m_notePixmapFactory->getNoteBodyWidth();
+ }
+
+ if (indicationType == Indication::Crescendo ||
+ indicationType == Indication::Decrescendo) {
+
+ if (m_printPainter) {
+ for (double w = -1, inc = 0; w != 0; inc += w) {
+ w = setPainterClipping(m_printPainter,
+ elt->getLayoutX(),
+ int(elt->getLayoutY()),
+ int(inc), length, coords,
+ policy);
+ m_notePixmapFactory->drawHairpin
+ (length, indicationType == Indication::Crescendo,
+ *m_printPainter, int(coords.first), coords.second);
+ m_printPainter->restore();
+ }
+ } else {
+ pixmap = m_notePixmapFactory->makeHairpinPixmap
+ (length, indicationType == Indication::Crescendo);
+ }
+
+ } else if (indicationType == Indication::Slur ||
+ indicationType == Indication::PhrasingSlur) {
+
+ bool above = true;
+ long dy = 0;
+ long length = 10;
+
+ elt->event()->get
+ <Bool>(properties.SLUR_ABOVE, above);
+ elt->event()->get
+ <Int>(properties.SLUR_Y_DELTA, dy);
+ elt->event()->get
+ <Int>(properties.SLUR_LENGTH, length);
+
+ if (m_printPainter) {
+ for (double w = -1, inc = 0; w != 0; inc += w) {
+ w = setPainterClipping(m_printPainter,
+ elt->getLayoutX(),
+ int(elt->getLayoutY()),
+ int(inc), length, coords,
+ policy);
+ m_notePixmapFactory->drawSlur
+ (length, dy, above,
+ indicationType == Indication::PhrasingSlur,
+ *m_printPainter, int(coords.first), coords.second);
+ m_printPainter->restore();
+ }
+ } else {
+ pixmap = m_notePixmapFactory->makeSlurPixmap
+ (length, dy, above,
+ indicationType == Indication::PhrasingSlur);
+ }
+
+ } else {
+
+ int octaves = indication.getOttavaShift();
+
+ if (octaves != 0) {
+ if (m_printPainter) {
+ for (double w = -1, inc = 0; w != 0; inc += w) {
+ w = setPainterClipping(m_printPainter,
+ elt->getLayoutX(),
+ int(elt->getLayoutY()),
+ int(inc), length, coords,
+ policy);
+ m_notePixmapFactory->drawOttava
+ (length, octaves,
+ *m_printPainter, int(coords.first), coords.second);
+ m_printPainter->restore();
+ }
+ } else {
+ pixmap = m_notePixmapFactory->makeOttavaPixmap
+ (length, octaves);
+ }
+ } else {
+
+ NOTATION_DEBUG
+ << "Unrecognised indicationType " << indicationType << endl;
+ if (m_showUnknowns) {
+ pixmap = m_notePixmapFactory->makeUnknownPixmap();
+ }
+ }
+ }
+ } catch (...) {
+ NOTATION_DEBUG << "Bad indication!" << endl;
+ }
+
+ } else if (elt->event()->isa(Controller::EventType)) {
+
+ bool isSustain = false;
+
+ long controlNumber = 0;
+ elt->event()->get
+ <Int>(Controller::NUMBER, controlNumber);
+
+ Studio *studio = &m_notationView->getDocument()->getStudio();
+ Track *track = getSegment().getComposition()->getTrackById
+ (getSegment().getTrack());
+
+ if (track) {
+
+ Instrument *instrument = studio->getInstrumentById
+ (track->getInstrument());
+ if (instrument) {
+ MidiDevice *device = dynamic_cast<MidiDevice *>
+ (instrument->getDevice());
+ if (device) {
+ for (ControlList::const_iterator i =
+ device->getControlParameters().begin();
+ i != device->getControlParameters().end(); ++i) {
+ if (i->getType() == Controller::EventType &&
+ i->getControllerValue() == controlNumber) {
+ if (i->getName() == "Sustain" ||
+ strtoqstr(i->getName()) == i18n("Sustain")) {
+ isSustain = true;
+ }
+ break;
+ }
+ }
+ } else if (instrument->getDevice() &&
+ instrument->getDevice()->getType() == Device::SoftSynth) {
+ if (controlNumber == 64) {
+ isSustain = true;
+ }
+ }
+ }
+ }
+
+ if (isSustain) {
+ long value = 0;
+ elt->event()->get
+ <Int>(Controller::VALUE, value);
+ if (value > 0) {
+ pixmap = m_notePixmapFactory->makePedalDownPixmap();
+ } else {
+ pixmap = m_notePixmapFactory->makePedalUpPixmap();
+ }
+
+ } else {
+
+ if (m_showUnknowns) {
+ pixmap = m_notePixmapFactory->makeUnknownPixmap();
+ }
+ }
+ } else if (elt->event()->isa(Guitar::Chord::EventType)) {
+
+ // Create a guitar chord pixmap
+ try {
+
+ Guitar::Chord chord (*elt->event());
+
+ /* UNUSED - for printing, just use a large pixmap as below
+ if (m_printPainter) {
+
+ int length = m_notePixmapFactory->getTextWidth(text);
+ for (double w = -1, inc = 0; w != 0; inc += w) {
+ w = setPainterClipping(m_printPainter,
+ elt->getLayoutX(),
+ int(elt->getLayoutY()),
+ int(inc), length, coords,
+ policy);
+ m_notePixmapFactory->drawText
+ (text, *m_printPainter, int(coords.first), coords.second);
+ m_printPainter->restore();
+ }
+ } else {
+ */
+
+ pixmap = m_notePixmapFactory->makeGuitarChordPixmap (chord.getFingering(),
+ int(coords.first),
+ coords.second);
+ // }
+ } catch (Exception e) { // GuitarChord ctor failed
+ NOTATION_DEBUG << "Bad guitar chord event" << endl;
+ }
+
+ } else {
+
+ if (m_showUnknowns) {
+ pixmap = m_notePixmapFactory->makeUnknownPixmap();
+ }
+ }
+
+ // Show the result, one way or another
+
+ if (elt->isNote()) {
+
+ // No need, we already set and showed it in renderNote
+
+ } else if (pixmap) {
+
+ setPixmap(elt, pixmap, z, policy);
+
+ } else {
+ elt->removeCanvasItem();
+ }
+
+ // NOTATION_DEBUG << "NotationStaff::renderSingleElement: Setting selected at " << elt->getAbsoluteTime() << " to " << selected << endl;
+
+ } catch (...) {
+ std::cerr << "Event lacks the proper properties: "
+ << std::endl;
+ elt->event()->dump(std::cerr);
+ }
+
+ m_notePixmapFactory->setSelected(false);
+ m_notePixmapFactory->setShaded(false);
+}
+
+double
+NotationStaff::setPainterClipping(QPainter *painter, double lx, int ly,
+ double dx, double w, LinedStaffCoords &coords,
+ FitPolicy policy)
+{
+ painter->save();
+
+ // NOTATION_DEBUG << "NotationStaff::setPainterClipping: lx " << lx << ", dx " << dx << ", w " << w << endl;
+
+ coords = getCanvasCoordsForLayoutCoords(lx + dx, ly);
+ int row = getRowForLayoutX(lx + dx);
+ double rightMargin = getCanvasXForRightOfRow(row);
+ double available = rightMargin - coords.first;
+
+ // NOTATION_DEBUG << "NotationStaff::setPainterClipping: row " << row << ", rightMargin " << rightMargin << ", available " << available << endl;
+
+ switch (policy) {
+
+ case SplitToFit: {
+ bool fit = (w - dx <= available + m_notePixmapFactory->getNoteBodyWidth());
+ if (dx > 0.01 || !fit) {
+ int clipLeft = int(coords.first), clipWidth = int(available);
+ if (dx < 0.01) {
+ // never clip the left side of the first part of something
+ clipWidth += clipLeft;
+ clipLeft = 0;
+ }
+ QRect clip(clipLeft, coords.second - getRowSpacing() / 2,
+ clipWidth, getRowSpacing());
+ painter->setClipRect(clip, QPainter::CoordPainter);
+ coords.first -= dx;
+ }
+ if (fit) {
+ return 0.0;
+ }
+ return available;
+ }
+
+ case MoveBackToFit:
+ if (w - dx > available + m_notePixmapFactory->getNoteBodyWidth()) {
+ coords.first -= (w - dx) - available;
+ }
+ return 0.0;
+
+ default:
+ return 0.0;
+ }
+}
+
+void
+NotationStaff::setPixmap(NotationElement *elt, QCanvasPixmap *pixmap, int z,
+ FitPolicy policy)
+{
+ double layoutX = elt->getLayoutX();
+ int layoutY = (int)elt->getLayoutY();
+
+ elt->removeCanvasItem();
+
+ while (1) {
+
+ LinedStaffCoords coords =
+ getCanvasCoordsForLayoutCoords(layoutX, layoutY);
+
+ double canvasX = coords.first;
+ int canvasY = coords.second;
+
+ QCanvasItem *item = 0;
+
+ if (m_pageMode == LinearMode || policy == PretendItFittedAllAlong) {
+
+ item = new QCanvasNotationSprite(*elt, pixmap, m_canvas);
+
+ } else {
+
+ int row = getRowForLayoutX(layoutX);
+ double rightMargin = getCanvasXForRightOfRow(row);
+ double extent = canvasX + pixmap->width();
+
+ // NOTATION_DEBUG << "NotationStaff::setPixmap: row " << row << ", right margin " << rightMargin << ", extent " << extent << endl;
+
+ if (extent > rightMargin + m_notePixmapFactory->getNoteBodyWidth()) {
+
+ if (policy == SplitToFit) {
+
+ // NOTATION_DEBUG << "splitting at " << (rightMargin-canvasX) << endl;
+
+ std::pair<QPixmap, QPixmap> split =
+ PixmapFunctions::splitPixmap(*pixmap,
+ int(rightMargin - canvasX));
+
+ QCanvasPixmap *leftCanvasPixmap = new QCanvasPixmap
+ (split.first, QPoint(pixmap->offsetX(), pixmap->offsetY()));
+
+ QCanvasPixmap *rightCanvasPixmap = new QCanvasPixmap
+ (split.second, QPoint(0, pixmap->offsetY()));
+
+ item = new QCanvasNotationSprite(*elt, leftCanvasPixmap, m_canvas);
+ item->setZ(z);
+
+ if (elt->getCanvasItem()) {
+ elt->addCanvasItem(item, canvasX, canvasY);
+ } else {
+ elt->setCanvasItem(item, canvasX, canvasY);
+ }
+
+ item->show();
+
+ delete pixmap;
+ pixmap = rightCanvasPixmap;
+
+ layoutX += rightMargin - canvasX + 0.01; // ensure flip to next row
+
+ continue;
+
+ } else { // policy == MoveBackToFit
+
+ item = new QCanvasNotationSprite(*elt, pixmap, m_canvas);
+ elt->setLayoutX(elt->getLayoutX() - (extent - rightMargin));
+ coords = getCanvasCoordsForLayoutCoords(layoutX, layoutY);
+ canvasX = coords.first;
+ }
+ } else {
+ item = new QCanvasNotationSprite(*elt, pixmap, m_canvas);
+ }
+ }
+
+ item->setZ(z);
+ if (elt->getCanvasItem()) {
+ elt->addCanvasItem(item, canvasX, canvasY);
+ } else {
+ elt->setCanvasItem(item, canvasX, canvasY);
+ }
+ item->show();
+ break;
+ }
+}
+
+void
+NotationStaff::renderNote(ViewElementList::iterator &vli)
+{
+ NotationElement* elt = static_cast<NotationElement*>(*vli);
+
+ const NotationProperties &properties(getProperties());
+ static NotePixmapParameters params(Note::Crotchet, 0);
+
+ Note::Type note = elt->event()->get
+ <Int>(BaseProperties::NOTE_TYPE);
+ int dots = elt->event()->get
+ <Int>(BaseProperties::NOTE_DOTS);
+
+ Accidental accidental = Accidentals::NoAccidental;
+ (void)elt->event()->get
+ <String>(properties.DISPLAY_ACCIDENTAL, accidental);
+
+ bool cautionary = false;
+ if (accidental != Accidentals::NoAccidental) {
+ (void)elt->event()->get
+ <Bool>(properties.DISPLAY_ACCIDENTAL_IS_CAUTIONARY,
+ cautionary);
+ }
+
+ bool up = true;
+ // (void)(elt->event()->get<Bool>(properties.STEM_UP, up));
+ (void)(elt->event()->get
+ <Bool>(properties.VIEW_LOCAL_STEM_UP, up));
+
+ bool flag = true;
+ (void)(elt->event()->get
+ <Bool>(properties.DRAW_FLAG, flag));
+
+ bool beamed = false;
+ (void)(elt->event()->get
+ <Bool>(properties.BEAMED, beamed));
+
+ bool shifted = false;
+ (void)(elt->event()->get
+ <Bool>(properties.NOTE_HEAD_SHIFTED, shifted));
+
+ bool dotShifted = false;
+ (void)(elt->event()->get
+ <Bool>(properties.NOTE_DOT_SHIFTED, dotShifted));
+
+ long stemLength = m_notePixmapFactory->getNoteBodyHeight();
+ (void)(elt->event()->get
+ <Int>(properties.UNBEAMED_STEM_LENGTH, stemLength));
+
+ long heightOnStaff = 0;
+ int legerLines = 0;
+
+ (void)(elt->event()->get
+ <Int>(properties.HEIGHT_ON_STAFF, heightOnStaff));
+ if (heightOnStaff < 0) {
+ legerLines = heightOnStaff;
+ } else if (heightOnStaff > 8) {
+ legerLines = heightOnStaff - 8;
+ }
+
+ long slashes = 0;
+ (void)(elt->event()->get
+ <Int>(properties.SLASHES, slashes));
+
+ bool quantized = false;
+ if (m_colourQuantize && !elt->isTuplet()) {
+ quantized =
+ (elt->getViewAbsoluteTime() != elt->event()->getAbsoluteTime() ||
+ elt->getViewDuration() != elt->event()->getDuration());
+ }
+ params.setQuantized(quantized);
+
+ bool trigger = false;
+ if (elt->event()->has(BaseProperties::TRIGGER_SEGMENT_ID))
+ trigger = true;
+ params.setTrigger(trigger);
+
+ bool inRange = true;
+ Pitch p(*elt->event());
+ Segment *segment = &getSegment();
+ if (m_showRanges) {
+ int pitch = p.getPerformancePitch();
+ if (pitch > segment->getHighestPlayable() ||
+ pitch < segment->getLowestPlayable()) {
+ inRange = false;
+ }
+ }
+ params.setInRange(inRange);
+
+ params.setNoteType(note);
+ params.setDots(dots);
+ params.setAccidental(accidental);
+ params.setAccidentalCautionary(cautionary);
+ params.setNoteHeadShifted(shifted);
+ params.setNoteDotShifted(dotShifted);
+ params.setDrawFlag(flag);
+ params.setDrawStem(true);
+ params.setStemGoesUp(up);
+ params.setLegerLines(legerLines);
+ params.setSlashes(slashes);
+ params.setBeamed(false);
+ params.setIsOnLine(heightOnStaff % 2 == 0);
+ params.removeMarks();
+ params.setSafeVertDistance(0);
+
+ bool primary = false;
+ int safeVertDistance = 0;
+
+ if (elt->event()->get
+ <Bool>(properties.CHORD_PRIMARY_NOTE, primary)
+ && primary) {
+
+ long marks = 0;
+ elt->event()->get
+ <Int>(properties.CHORD_MARK_COUNT, marks);
+ if (marks) {
+ NotationChord chord(*getViewElementList(), vli,
+ m_segment.getComposition()->getNotationQuantizer(),
+ properties);
+ params.setMarks(chord.getMarksForChord());
+ }
+
+ // params.setMarks(Marks::getMarks(*elt->event()));
+
+ if (up && note < Note::Semibreve) {
+ safeVertDistance = m_notePixmapFactory->getStemLength();
+ safeVertDistance = std::max(safeVertDistance, int(stemLength));
+ }
+ }
+
+ long tieLength = 0;
+ (void)(elt->event()->get<Int>(properties.TIE_LENGTH, tieLength));
+ if (tieLength > 0) {
+ params.setTied(true);
+ params.setTieLength(tieLength);
+ } else {
+ params.setTied(false);
+ }
+
+ if (elt->event()->has(BaseProperties::TIE_IS_ABOVE)) {
+ params.setTiePosition
+ (true, elt->event()->get<Bool>(BaseProperties::TIE_IS_ABOVE));
+ } else {
+ params.setTiePosition(false, false); // the default
+ }
+
+ long accidentalShift = 0;
+ bool accidentalExtra = false;
+ if (elt->event()->get<Int>(properties.ACCIDENTAL_SHIFT, accidentalShift)) {
+ elt->event()->get<Bool>(properties.ACCIDENTAL_EXTRA_SHIFT, accidentalExtra);
+ }
+ params.setAccidentalShift(accidentalShift);
+ params.setAccExtraShift(accidentalExtra);
+
+ double airX, airWidth;
+ elt->getLayoutAirspace(airX, airWidth);
+ params.setWidth(int(airWidth));
+
+ if (beamed) {
+
+ if (elt->event()->get<Bool>(properties.CHORD_PRIMARY_NOTE, primary)
+ && primary) {
+
+ int myY = elt->event()->get<Int>(properties.BEAM_MY_Y);
+
+ stemLength = myY - (int)elt->getLayoutY();
+ if (stemLength < 0)
+ stemLength = -stemLength;
+
+ int nextBeamCount =
+ elt->event()->get
+ <Int>(properties.BEAM_NEXT_BEAM_COUNT);
+ int width =
+ elt->event()->get
+ <Int>(properties.BEAM_SECTION_WIDTH);
+ int gradient =
+ elt->event()->get
+ <Int>(properties.BEAM_GRADIENT);
+
+ bool thisPartialBeams(false), nextPartialBeams(false);
+ (void)elt->event()->get
+ <Bool>
+ (properties.BEAM_THIS_PART_BEAMS, thisPartialBeams);
+ (void)elt->event()->get
+ <Bool>
+ (properties.BEAM_NEXT_PART_BEAMS, nextPartialBeams);
+
+ params.setBeamed(true);
+ params.setNextBeamCount(nextBeamCount);
+ params.setThisPartialBeams(thisPartialBeams);
+ params.setNextPartialBeams(nextPartialBeams);
+ params.setWidth(width);
+ params.setGradient((double)gradient / 100.0);
+ if (up)
+ safeVertDistance = stemLength;
+
+ }
+ else {
+ params.setBeamed(false);
+ params.setDrawStem(false);
+ }
+ }
+
+ if (heightOnStaff < 7) {
+ int gap = (((7 - heightOnStaff) * m_notePixmapFactory->getLineSpacing()) / 2);
+ if (safeVertDistance < gap)
+ safeVertDistance = gap;
+ }
+
+ params.setStemLength(stemLength);
+ params.setSafeVertDistance(safeVertDistance);
+ setTuplingParameters(elt, params);
+
+ NotePixmapFactory *factory = m_notePixmapFactory;
+
+ if (elt->isGrace()) {
+ // lift this code from elsewhere to fix #1930309, and it seems to work a
+ // treat, as y'all Wrongpondians are wont to say
+ params.setLegerLines(heightOnStaff < 0 ? heightOnStaff :
+ heightOnStaff > 8 ? heightOnStaff - 8 : 0);
+ m_graceNotePixmapFactory->setSelected(m_notePixmapFactory->isSelected());
+ m_graceNotePixmapFactory->setShaded(m_notePixmapFactory->isShaded());
+ factory = m_graceNotePixmapFactory;
+ }
+
+ if (m_printPainter) {
+
+ // Return no canvas item, but instead render straight to
+ // the printer.
+
+ LinedStaffCoords coords = getCanvasCoordsForLayoutCoords
+ (elt->getLayoutX(), (int)elt->getLayoutY());
+
+ // We don't actually know how wide the note drawing will be,
+ // but we should be able to use a fairly pessimistic estimate
+ // without causing any problems
+ int length = tieLength + 10 * m_notePixmapFactory->getNoteBodyWidth();
+
+ for (double w = -1, inc = 0; w != 0; inc += w) {
+
+ w = setPainterClipping(m_printPainter,
+ elt->getLayoutX(),
+ int(elt->getLayoutY()),
+ int(inc), length, coords,
+ SplitToFit);
+
+ factory->drawNote
+ (params, *m_printPainter, int(coords.first), coords.second);
+
+ m_printPainter->restore(); // save() called by setPainterClipping
+ }
+
+ } else {
+
+ // The normal on-screen case
+
+ bool collision = false;
+ QCanvasItem * haloItem = 0;
+ if (m_showCollisions) {
+ collision = elt->isColliding();
+ if (collision) {
+ // Make collision halo
+ QCanvasPixmap *haloPixmap = factory->makeNoteHaloPixmap(params);
+ haloItem = new QCanvasNotationSprite(*elt, haloPixmap, m_canvas);
+ haloItem->setZ(-1);
+ }
+ }
+
+ QCanvasPixmap *pixmap = factory->makeNotePixmap(params);
+
+ int z = 0;
+ if (factory->isSelected())
+ z = 3;
+ else if (quantized)
+ z = 2;
+
+ setPixmap(elt, pixmap, z, SplitToFit);
+
+ if (collision) {
+ // Display collision halo
+ LinedStaffCoords coords =
+ getCanvasCoordsForLayoutCoords(elt->getLayoutX(),
+ elt->getLayoutY());
+ double canvasX = coords.first;
+ int canvasY = coords.second;
+ elt->addCanvasItem(haloItem, canvasX, canvasY);
+ haloItem->show();
+ }
+ }
+}
+
+void
+NotationStaff::setTuplingParameters(NotationElement *elt,
+ NotePixmapParameters &params)
+{
+ const NotationProperties &properties(getProperties());
+
+ params.setTupletCount(0);
+ long tuplingLineY = 0;
+ bool tupled = (elt->event()->get
+ <Int>(properties.TUPLING_LINE_MY_Y, tuplingLineY));
+
+ if (tupled) {
+
+ long tuplingLineWidth = 0;
+ if (!elt->event()->get
+ <Int>(properties.TUPLING_LINE_WIDTH, tuplingLineWidth)) {
+ std::cerr << "WARNING: Tupled event at " << elt->event()->getAbsoluteTime() << " has no tupling line width" << std::endl;
+ }
+
+ long tuplingLineGradient = 0;
+ if (!(elt->event()->get
+ <Int>(properties.TUPLING_LINE_GRADIENT,
+ tuplingLineGradient))) {
+ std::cerr << "WARNING: Tupled event at " << elt->event()->getAbsoluteTime() << " has no tupling line gradient" << std::endl;
+ }
+
+ bool tuplingLineFollowsBeam = false;
+ elt->event()->get
+ <Bool>(properties.TUPLING_LINE_FOLLOWS_BEAM,
+ tuplingLineFollowsBeam);
+
+ long tupletCount;
+ if (elt->event()->get<Int>
+ (BaseProperties::BEAMED_GROUP_UNTUPLED_COUNT, tupletCount)) {
+
+ params.setTupletCount(tupletCount);
+ params.setTuplingLineY(tuplingLineY - (int)elt->getLayoutY());
+ params.setTuplingLineWidth(tuplingLineWidth);
+ params.setTuplingLineGradient(double(tuplingLineGradient) / 100.0);
+ params.setTuplingLineFollowsBeam(tuplingLineFollowsBeam);
+ }
+ }
+}
+
+bool
+NotationStaff::isSelected(NotationElementList::iterator it)
+{
+ const EventSelection *selection =
+ m_notationView->getCurrentSelection();
+ return selection && selection->contains((*it)->event());
+}
+
+void
+NotationStaff::showPreviewNote(double layoutX, int heightOnStaff,
+ const Note &note, bool grace)
+{
+ NotePixmapFactory *npf = m_notePixmapFactory;
+ if (grace) npf = m_graceNotePixmapFactory;
+
+ NotePixmapParameters params(note.getNoteType(), note.getDots());
+ NotationRules rules;
+
+ params.setAccidental(Accidentals::NoAccidental);
+ params.setNoteHeadShifted(false);
+ params.setDrawFlag(true);
+ params.setDrawStem(true);
+ params.setStemGoesUp(rules.isStemUp(heightOnStaff));
+ params.setLegerLines(heightOnStaff < 0 ? heightOnStaff :
+ heightOnStaff > 8 ? heightOnStaff - 8 : 0);
+ params.setBeamed(false);
+ params.setIsOnLine(heightOnStaff % 2 == 0);
+ params.setTied(false);
+ params.setBeamed(false);
+ params.setTupletCount(0);
+ params.setSelected(false);
+ params.setHighlighted(true);
+
+ delete m_previewSprite;
+ m_previewSprite = new QCanvasSimpleSprite
+ (npf->makeNotePixmap(params), m_canvas);
+
+ int layoutY = getLayoutYForHeight(heightOnStaff);
+ LinedStaffCoords coords = getCanvasCoordsForLayoutCoords(layoutX, layoutY);
+
+ m_previewSprite->move(coords.first, (double)coords.second);
+ m_previewSprite->setZ(4);
+ m_previewSprite->show();
+ m_canvas->update();
+}
+
+void
+NotationStaff::clearPreviewNote()
+{
+ delete m_previewSprite;
+ m_previewSprite = 0;
+}
+
+bool
+NotationStaff::wrapEvent(Event *e)
+{
+ bool wrap = true;
+
+ /*!!! always wrap unknowns, just don't necessarily render them?
+
+ if (!m_showUnknowns) {
+ std::string etype = e->getType();
+ if (etype != Note::EventType &&
+ etype != Note::EventRestType &&
+ etype != Clef::EventType &&
+ etype != Key::EventType &&
+ etype != Indication::EventType &&
+ etype != Text::EventType) {
+ wrap = false;
+ }
+ }
+ */
+
+ if (wrap)
+ wrap = Staff::wrapEvent(e);
+
+ return wrap;
+}
+
+void
+NotationStaff::eventRemoved(const Segment *segment,
+ Event *event)
+{
+ LinedStaff::eventRemoved(segment, event);
+ m_notationView->handleEventRemoved(event);
+}
+
+void
+NotationStaff::markChanged(timeT from, timeT to, bool movedOnly)
+{
+ // first time through this, m_ready is false -- we mark it true
+
+ NOTATION_DEBUG << "NotationStaff::markChanged (" << from << " -> " << to << ") " << movedOnly << endl;
+
+ drawStaffName();//!!!
+
+ if (from == to) {
+
+ m_status.clear();
+
+ if (!movedOnly && m_ready) { // undo all the rendering we've already done
+ for (NotationElementList::iterator i = getViewElementList()->begin();
+ i != getViewElementList()->end(); ++i) {
+ static_cast<NotationElement *>(*i)->removeCanvasItem();
+ }
+
+ m_clefChanges.clear();
+ m_keyChanges.clear();
+ }
+
+ drawStaffName();
+
+ } else {
+
+ Segment *segment = &getSegment();
+ Composition *composition = segment->getComposition();
+
+ NotationElementList::iterator unchanged = findUnchangedBarEnd(to);
+
+ int finalBar;
+ if (unchanged == getViewElementList()->end()) {
+ finalBar = composition->getBarNumber(segment->getEndMarkerTime());
+ } else {
+ finalBar = composition->getBarNumber((*unchanged)->getViewAbsoluteTime());
+ }
+
+ int fromBar = composition->getBarNumber(from);
+ int toBar = composition->getBarNumber(to);
+ if (finalBar < toBar)
+ finalBar = toBar;
+
+ for (int bar = fromBar; bar <= finalBar; ++bar) {
+
+ if (bar > toBar)
+ movedOnly = true;
+
+ // NOTATION_DEBUG << "bar " << bar << " status " << m_status[bar] << endl;
+
+ if (bar >= m_lastRenderCheck.first &&
+ bar <= m_lastRenderCheck.second) {
+
+ // NOTATION_DEBUG << "bar " << bar << " rendering and positioning" << endl;
+
+ if (!movedOnly || m_status[bar] == UnRendered) {
+ renderElements
+ (getViewElementList()->findTime(composition->getBarStart(bar)),
+ getViewElementList()->findTime(composition->getBarEnd(bar)));
+ }
+ positionElements(composition->getBarStart(bar),
+ composition->getBarEnd(bar));
+ m_status[bar] = Positioned;
+
+ } else if (!m_ready) {
+ // NOTATION_DEBUG << "bar " << bar << " rendering and positioning" << endl;
+
+ // first time through -- we don't need a separate render phase,
+ // only to mark as not yet positioned
+ m_status[bar] = Rendered;
+
+ } else if (movedOnly) {
+ if (m_status[bar] == Positioned) {
+ // NOTATION_DEBUG << "bar " << bar << " marking unpositioned" << endl;
+ m_status[bar] = Rendered;
+ }
+
+ } else {
+ // NOTATION_DEBUG << "bar " << bar << " marking unrendered" << endl;
+
+ m_status[bar] = UnRendered;
+ }
+ }
+ }
+
+ m_ready = true;
+}
+
+void
+NotationStaff::setPrintPainter(QPainter *painter)
+{
+ m_printPainter = painter;
+}
+
+bool
+NotationStaff::checkRendered(timeT from, timeT to)
+{
+ if (!m_ready)
+ return false;
+ Composition *composition = getSegment().getComposition();
+ if (!composition) {
+ NOTATION_DEBUG << "NotationStaff::checkRendered: warning: segment has no composition -- is my paint event late?" << endl;
+ return false;
+ }
+
+ // NOTATION_DEBUG << "NotationStaff::checkRendered: " << from << " -> " << to << endl;
+
+ int fromBar = composition->getBarNumber(from);
+ int toBar = composition->getBarNumber(to);
+ bool something = false;
+
+ if (fromBar > toBar)
+ std::swap(fromBar, toBar);
+
+ for (int bar = fromBar; bar <= toBar; ++bar) {
+ // NOTATION_DEBUG << "NotationStaff::checkRendered: bar " << bar << " status "
+ // << m_status[bar] << endl;
+
+ switch (m_status[bar]) {
+
+ case UnRendered:
+ renderElements
+ (getViewElementList()->findTime(composition->getBarStart(bar)),
+ getViewElementList()->findTime(composition->getBarEnd(bar)));
+
+ case Rendered:
+ positionElements
+ (composition->getBarStart(bar),
+ composition->getBarEnd(bar));
+ m_lastRenderedBar = bar;
+
+ something = true;
+
+ case Positioned:
+ break;
+ }
+
+ m_status[bar] = Positioned;
+ }
+
+ m_lastRenderCheck = std::pair<int, int>(fromBar, toBar);
+ return something;
+}
+
+bool
+NotationStaff::doRenderWork(timeT from, timeT to)
+{
+ if (!m_ready)
+ return true;
+ Composition *composition = getSegment().getComposition();
+
+ int fromBar = composition->getBarNumber(from);
+ int toBar = composition->getBarNumber(to);
+
+ if (fromBar > toBar)
+ std::swap(fromBar, toBar);
+
+ for (int bar = fromBar; bar <= toBar; ++bar) {
+
+ switch (m_status[bar]) {
+
+ case UnRendered:
+ renderElements
+ (getViewElementList()->findTime(composition->getBarStart(bar)),
+ getViewElementList()->findTime(composition->getBarEnd(bar)));
+ m_status[bar] = Rendered;
+ return true;
+
+ case Rendered:
+ positionElements
+ (composition->getBarStart(bar),
+ composition->getBarEnd(bar));
+ m_status[bar] = Positioned;
+ m_lastRenderedBar = bar;
+ return true;
+
+ case Positioned:
+ // The bars currently displayed are rendered before the others.
+ // Later, when preceding bars are rendered, truncateClefsAndKeysAt()
+ // is called and possible clefs and/or keys from the bars previously
+ // rendered may be lost. Following code should restore these clefs
+ // and keys in m_clefChanges and m_keyChanges lists.
+ if (bar > m_lastRenderedBar)
+ checkAndCompleteClefsAndKeys(bar);
+ continue;
+ }
+ }
+
+ return false;
+}
+
+void
+NotationStaff::checkAndCompleteClefsAndKeys(int bar)
+{
+ // Look for Clef or Key in current bar
+ Composition *composition = getSegment().getComposition();
+ timeT barStartTime = composition->getBarStart(bar);
+ timeT barEndTime = composition->getBarEnd(bar);
+
+ for (ViewElementList::iterator it =
+ getViewElementList()->findTime(barStartTime);
+ (it != getViewElementList()->end())
+ && ((*it)->getViewAbsoluteTime() < barEndTime); ++it) {
+ if ((*it)->event()->isa(Clef::EventType)) {
+ // Clef found
+ Clef clef = *(*it)->event();
+
+ // Is this clef already in m_clefChanges list ?
+ int xClef = int((*it)->getLayoutX());
+ bool found = false;
+ for (int i = 0; i < m_clefChanges.size(); ++i) {
+ if ( (m_clefChanges[i].first == xClef)
+ && (m_clefChanges[i].second == clef)) {
+ found = true;
+ break;
+ }
+ }
+
+ // If not, add it
+ if (!found) {
+ m_clefChanges.push_back(ClefChange(xClef, clef));
+ }
+
+ } else if ((*it)->event()->isa(::Rosegarden::Key::EventType)) {
+ ::Rosegarden::Key key = *(*it)->event();
+
+ // Is this key already in m_keyChanges list ?
+ int xKey = int((*it)->getLayoutX());
+ bool found = false;
+ for (int i = 0; i < m_keyChanges.size(); ++i) {
+ if ( (m_keyChanges[i].first == xKey)
+ && (m_keyChanges[i].second == key)) {
+ found = true;
+ break;
+ }
+ }
+
+ // If not, add it
+ if (!found) {
+ m_keyChanges.push_back(KeyChange(xKey, key));
+ }
+ }
+ }
+}
+
+LinedStaff::BarStyle
+NotationStaff::getBarStyle(int barNo) const
+{
+ const Segment *s = &getSegment();
+ Composition *c = s->getComposition();
+
+ int firstBar = c->getBarNumber(s->getStartTime());
+ int lastNonEmptyBar = c->getBarNumber(s->getEndMarkerTime() - 1);
+
+ // Currently only the first and last bar in a segment have any
+ // possibility of getting special treatment:
+ if (barNo > firstBar && barNo <= lastNonEmptyBar)
+ return PlainBar;
+
+ // First and last bar in a repeating segment get repeat bars.
+
+ if (s->isRepeating()) {
+ if (barNo == firstBar)
+ return RepeatStartBar;
+ else if (barNo == lastNonEmptyBar + 1)
+ return RepeatEndBar;
+ }
+
+ if (barNo <= lastNonEmptyBar)
+ return PlainBar;
+
+ // Last bar on a given track gets heavy double bars. Exploit the
+ // fact that Composition's iterator returns segments in track
+ // order.
+
+ Segment *lastSegmentOnTrack = 0;
+
+ for (Composition::iterator i = c->begin(); i != c->end(); ++i) {
+ if ((*i)->getTrack() == s->getTrack()) {
+ lastSegmentOnTrack = *i;
+ } else if (lastSegmentOnTrack != 0) {
+ break;
+ }
+ }
+
+ if (&getSegment() == lastSegmentOnTrack)
+ return HeavyDoubleBar;
+ else
+ return PlainBar;
+}
+
+double
+NotationStaff::getBarInset(int barNo, bool isFirstBarInRow) const
+{
+ LinedStaff::BarStyle style = getBarStyle(barNo);
+
+ NOTATION_DEBUG << "getBarInset(" << barNo << "," << isFirstBarInRow << ")" << endl;
+
+ if (!(style == RepeatStartBar || style == RepeatBothBar))
+ return 0.0;
+
+ const Segment &s = getSegment();
+ Composition *composition = s.getComposition();
+ timeT barStart = composition->getBarStart(barNo);
+
+ double inset = 0.0;
+
+ NOTATION_DEBUG << "ready" << endl;
+
+ bool haveKey = false, haveClef = false;
+
+ ::Rosegarden::Key key;
+ ::Rosegarden::Key cancelKey;
+ Clef clef;
+
+ for (Segment::iterator i = s.findTime(barStart);
+ s.isBeforeEndMarker(i) && ((*i)->getNotationAbsoluteTime() == barStart);
+ ++i) {
+
+ NOTATION_DEBUG << "type " << (*i)->getType() << " at " << (*i)->getNotationAbsoluteTime() << endl;
+
+ if ((*i)->isa(::Rosegarden::Key::EventType)) {
+
+ try {
+ key = ::Rosegarden::Key(**i);
+
+ if (barNo > composition->getBarNumber(s.getStartTime())) {
+ cancelKey = s.getKeyAtTime(barStart - 1);
+ }
+
+ if (m_keySigCancelMode == 0) { // only when entering C maj / A min
+
+ if (key.getAccidentalCount() != 0)
+ cancelKey = ::Rosegarden::Key();
+
+ } else if (m_keySigCancelMode == 1) { // only when reducing acc count
+
+ if (!(key.isSharp() == cancelKey.isSharp() &&
+ key.getAccidentalCount() < cancelKey.getAccidentalCount())) {
+ cancelKey = ::Rosegarden::Key();
+ }
+ }
+
+ haveKey = true;
+
+ } catch (...) {
+ NOTATION_DEBUG << "getBarInset: Bad key in event" << endl;
+ }
+
+ } else if ((*i)->isa(Clef::EventType)) {
+
+ try {
+ clef = Clef(**i);
+ haveClef = true;
+ } catch (...) {
+ NOTATION_DEBUG << "getBarInset: Bad clef in event" << endl;
+ }
+ }
+ }
+
+ if (isFirstBarInRow) {
+ if (!haveKey) {
+ key = s.getKeyAtTime(barStart);
+ haveKey = true;
+ }
+ if (!haveClef) {
+ clef = s.getClefAtTime(barStart);
+ haveClef = true;
+ }
+ }
+
+ if (haveKey) {
+ inset += m_notePixmapFactory->getKeyWidth(key, cancelKey);
+ }
+ if (haveClef) {
+ inset += m_notePixmapFactory->getClefWidth(clef);
+ }
+ if (haveClef || haveKey) {
+ inset += m_notePixmapFactory->getBarMargin() / 3;
+ }
+ if (haveClef && haveKey) {
+ inset += m_notePixmapFactory->getNoteBodyWidth() / 2;
+ }
+
+ NOTATION_DEBUG << "getBarInset(" << barNo << "," << isFirstBarInRow << "): inset " << inset << endl;
+
+
+ return inset;
+}
+
+Rosegarden::ViewElement* NotationStaff::makeViewElement(Rosegarden::Event* e)
+{
+ return new NotationElement(e);
+}
+
+}
diff --git a/src/gui/editors/notation/NotationStaff.h b/src/gui/editors/notation/NotationStaff.h
new file mode 100644
index 0000000..4a0302c
--- /dev/null
+++ b/src/gui/editors/notation/NotationStaff.h
@@ -0,0 +1,488 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
+
+/*
+ Rosegarden
+ A MIDI and audio sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_NOTATIONSTAFF_H_
+#define _RG_NOTATIONSTAFF_H_
+
+#include "base/FastVector.h"
+#include "base/Staff.h"
+#include "base/ViewElement.h"
+#include "gui/general/LinedStaff.h"
+#include "gui/general/ProgressReporter.h"
+#include <map>
+#include <set>
+#include <string>
+#include <utility>
+#include "base/Event.h"
+#include "NotationElement.h"
+
+
+class QPainter;
+class QCanvasPixmap;
+class QCanvasItem;
+class QCanvas;
+class LinedStaffCoords;
+
+
+namespace Rosegarden
+{
+
+class ViewElement;
+class TimeSignature;
+class SnapGrid;
+class Segment;
+class QCanvasSimpleSprite;
+class NotePixmapParameters;
+class NotePixmapFactory;
+class Note;
+class NotationView;
+class NotationProperties;
+class Key;
+class Event;
+class Clef;
+
+
+/**
+ * The Staff is a repository for information about the notation
+ * representation of a single Segment. This includes all of the
+ * NotationElements representing the Events on that Segment, the staff
+ * lines, as well as basic positional and size data. This class
+ * used to be in gui/staff.h, but it's been moved and renamed
+ * following the introduction of the core Staff base class, and
+ * much of the functionality has been extracted into the LinedStaff
+ * base class.
+ */
+
+class NotationStaff : public ProgressReporter, public LinedStaff
+{
+public:
+
+ /**
+ * Creates a new NotationStaff for the specified Segment
+ * \a id is the id of the staff in the NotationView
+ */
+ NotationStaff(QCanvas *, Segment *, SnapGrid *,
+ int id, NotationView *view,
+ std::string fontName, int resolution);
+ virtual ~NotationStaff();
+
+ /**
+ * Changes the resolution of the note pixmap factory and the
+ * staff lines, etc
+ */
+ virtual void changeFont(std::string fontName, int resolution);
+
+ void setLegerLineCount(int legerLineCount) {
+ if (legerLineCount == -1) m_legerLineCount = 8;
+ else m_legerLineCount = legerLineCount;
+ }
+
+ void setBarNumbersEvery(int barNumbersEvery) {
+ m_barNumbersEvery = barNumbersEvery;
+ }
+
+ LinedStaff::setPageMode;
+ LinedStaff::setPageWidth;
+ LinedStaff::setRowsPerPage;
+ LinedStaff::setRowSpacing;
+ LinedStaff::setConnectingLineLength;
+
+ /**
+ * Gets a read-only reference to the pixmap factory used by the
+ * staff. (For use by NotationHLayout, principally.) This
+ * reference isn't const because the NotePixmapFactory maintains
+ * too much state for its methods to be const, but you should
+ * treat the returned reference as if it were const anyway.
+ */
+ virtual NotePixmapFactory& getNotePixmapFactory(bool grace) {
+ return grace ? *m_graceNotePixmapFactory : *m_notePixmapFactory;
+ }
+
+ /**
+ * Generate or re-generate sprites for all the elements between
+ * from and to. Call this when you've just made a change,
+ * specifying the extents of the change in the from and to
+ * parameters.
+ *
+ * This method does not reposition any elements outside the given
+ * range -- so after any edit that may change the visible extents
+ * of a range, you will then need to call positionElements for the
+ * changed range and the entire remainder of the staff.
+ */
+ virtual void renderElements(NotationElementList::iterator from,
+ NotationElementList::iterator to);
+
+
+ /**
+ * Assign suitable coordinates to the elements on the staff,
+ * based entirely on the layout X and Y coordinates they were
+ * given by the horizontal and vertical layout processes.
+ *
+ * This is necessary because the sprites that are being positioned
+ * may have been created either after the layout process completed
+ * (by renderElements) or before (by the previous renderElements
+ * call, if the sprites are unchanged but have moved) -- so
+ * neither the layout nor renderElements can authoritatively set
+ * their final positions.
+ *
+ * This method also updates the selected-ness of any elements it
+ * sees (i.e. it turns the selected ones blue and the unselected
+ * ones black), and re-generates sprites for any elements for
+ * which it seems necessary. In general it will only notice a
+ * element needs regenerating if its position has changed, not if
+ * the nature of the element has changed, so this is no substitute
+ * for calling renderElements.
+ *
+ * The from and to arguments are used to indicate the extents of a
+ * changed area within the staff. The actual area within which the
+ * elements end up being repositioned will begin at the start of
+ * the bar containing the changed area's start, and will end at the
+ * start of the next bar whose first element hasn't moved, after
+ * the changed area's end.
+ *
+ * Call this after renderElements, or after changing the selection,
+ * passing from and to arguments corresponding to the times of those
+ * passed to renderElements.
+ */
+ virtual void positionElements(timeT from,
+ timeT to);
+
+ /**
+ * Re-render and position elements as necessary, based on the
+ * given extents and any information obtained from calls to
+ * markChanged(). This provides a render-on-demand mechanism. If
+ * you are going to use this rendering mechanism, it's generally
+ * wise to avoid explicitly calling
+ * renderElements/positionElements as well.
+ *
+ * Returns true if something needed re-rendering.
+ */
+ virtual bool checkRendered(timeT from,
+ timeT to);
+
+ /**
+ * Find something between the given times that has not yet been
+ * rendered, and render a small amount of it. Return true if it
+ * found something to do. This is to be used as a background work
+ * procedure for rendering not-yet-visible areas of notation.
+ */
+ virtual bool doRenderWork(timeT from,
+ timeT to);
+
+ /**
+ * Mark a region of staff as changed, for use by the on-demand
+ * rendering mechanism. If fromBar == toBar == -1, mark the
+ * entire staff as changed (and recover the memory used for its
+ * elements). Pass movedOnly as true to indicate that elements
+ * have not changed but only been repositioned, for example as a
+ * consequence of a modification on another staff that caused a
+ * relayout.
+ */
+ virtual void markChanged(timeT from = 0,
+ timeT to = 0,
+ bool movedOnly = false);
+
+ /**
+ * Set a painter as the printer output. If this painter is
+ * non-null, subsequent renderElements calls will only render
+ * those elements that cannot be rendered directly to a print
+ * painter; those that can, will be rendered by renderPrintable()
+ * instead.
+ */
+ virtual void setPrintPainter(QPainter *painter);
+
+ /**
+ * Render to the current print painter those elements that can be
+ * rendered directly to a print painter. If no print painter is
+ * set, do nothing.
+ */
+ virtual void renderPrintable(timeT from,
+ timeT to);
+
+ /**
+ * Insert time signature at x-coordinate \a x.
+ */
+ virtual void insertTimeSignature(double layoutX,
+ const TimeSignature &timeSig);
+
+ /**
+ * Delete all time signatures
+ */
+ virtual void deleteTimeSignatures();
+
+ /**
+ * Insert repeated clef and key at start of new line, at x-coordinate \a x.
+ */
+ virtual void insertRepeatedClefAndKey(double layoutX, int barNo);
+
+ /**
+ * Delete all repeated clefs and keys.
+ */
+ virtual void deleteRepeatedClefsAndKeys();
+
+ /**
+ * (Re)draw the staff name from the track's current name
+ */
+ virtual void drawStaffName();
+
+ /**
+ * Return true if the staff name as currently drawn is up-to-date
+ * with that in the composition
+ */
+ virtual bool isStaffNameUpToDate();
+
+ /**
+ * Return the clef and key in force at the given canvas
+ * coordinates
+ */
+ virtual void getClefAndKeyAtCanvasCoords(double x, int y,
+ Clef &clef,
+ ::Rosegarden::Key &key) const;
+
+ /**
+ * Return the note name (C4, Bb3, whatever) corresponding to the
+ * given canvas coordinates
+ */
+ virtual std::string getNoteNameAtCanvasCoords
+ (double x, int y,
+ Accidental accidental =
+ Accidentals::NoAccidental) const;
+
+ /**
+ * Find the NotationElement whose layout x-coord is closest to x,
+ * without regard to its y-coord.
+ *
+ * If notesAndRestsOnly is true, will return the closest note
+ * or rest but will never return any other kind of element.
+ *
+ * If the closest event is further than \a proximityThreshold
+ * horizontally away from x, in pixels, end() is returned.
+ * (If proximityThreshold is negative, there will be no limit
+ * to the distances that will be considered.)
+ *
+ * Also returns the clef and key in force at the given coordinate.
+ */
+ virtual ViewElementList::iterator getClosestElementToLayoutX
+ (double x, Event *&clef, Event *&key,
+ bool notesAndRestsOnly = false,
+ int proximityThreshold = 10);
+
+ /**
+ * Find the NotationElement "under" the given layout x-coord,
+ * without regard to its y-coord.
+ *
+ * Also returns the clef and key in force at the given coordinates.
+ */
+ virtual ViewElementList::iterator getElementUnderLayoutX
+ (double x, Event *&clef, Event *&key);
+
+ /**
+ * Draw a note on the staff to show an insert position prior to
+ * an insert.
+ */
+ virtual void showPreviewNote(double layoutX, int heightOnStaff,
+ const Note &note, bool grace);
+
+ /**
+ * Remove any visible preview note.
+ */
+ virtual void clearPreviewNote();
+
+ /**
+ * Overridden from Staff<T>.
+ * We want to avoid wrapping things like controller events, if
+ * our showUnknowns preference is off
+ */
+ virtual bool wrapEvent(Event *);
+
+ /**
+ * Override from Staff<T>
+ * Let tools know if their current element has gone
+ */
+ virtual void eventRemoved(const Segment *, Event *);
+
+ /**
+ * Return the view-local PropertyName definitions for this staff's view
+ */
+ const NotationProperties &getProperties() const;
+
+ virtual double getBarInset(int barNo, bool isFirstBarInRow) const;
+
+ /**
+ * Return the time at the given canvas coordinates
+ */
+ timeT getTimeAtCanvasCoords(double x, int y) const;
+
+protected:
+
+ virtual ViewElement* makeViewElement(Event*);
+
+ // definition of staff
+ virtual int getLineCount() const { return 5; }
+ virtual int getLegerLineCount() const { return m_legerLineCount; }
+ virtual int getBottomLineHeight() const { return 0; }
+ virtual int getHeightPerLine() const { return 2; }
+ virtual int showBarNumbersEvery() const { return m_barNumbersEvery; }
+
+ virtual BarStyle getBarStyle(int barNo) const;
+
+ /**
+ * Assign a suitable sprite to the given element (the clef is
+ * needed in case it's a key event, in which case we need to judge
+ * the correct pitch for the key)
+ */
+ virtual void renderSingleElement(ViewElementList::iterator &,
+ const Clef &,
+ const ::Rosegarden::Key &,
+ bool selected);
+
+ bool isDirectlyPrintable(ViewElement *elt);
+
+ void setTuplingParameters(NotationElement *, NotePixmapParameters &);
+
+ /**
+ * Set a sprite representing the given note event to the given notation element
+ */
+ virtual void renderNote(ViewElementList::iterator &);
+
+ /**
+ * Return a NotationElementList::iterator pointing to the
+ * start of a bar prior to the given time that doesn't appear
+ * to have been affected by any changes around that time
+ */
+ NotationElementList::iterator findUnchangedBarStart(timeT);
+
+ /**
+ * Return a NotationElementList::iterator pointing to the
+ * end of a bar subsequent to the given time that doesn't appear
+ * to have been affected by any changes around that time
+ */
+ NotationElementList::iterator findUnchangedBarEnd(timeT);
+
+ /**
+ * Return true if the element has a canvas item that is already
+ * at the correct coordinates
+ */
+ virtual bool elementNotMoved(NotationElement *);
+
+ /**
+ * Return true if the element has a canvas item that is already
+ * at the correct y-coordinate
+ */
+ virtual bool elementNotMovedInY(NotationElement *);
+
+ /**
+ * Returns true if the item at the given iterator appears to have
+ * moved horizontally without the spacing around it changing.
+ *
+ * In practice, calculates the offset between the intended layout
+ * and current canvas coordinates of the item at the given
+ * iterator, and returns true if this offset is equal to those of
+ * all other following iterators at the same time as well as the
+ * first iterator found at a greater time.
+ */
+ virtual bool elementShiftedOnly(NotationElementList::iterator);
+
+ enum FitPolicy {
+ PretendItFittedAllAlong = 0,
+ MoveBackToFit,
+ SplitToFit
+ };
+
+ /**
+ * Prepare a painter to draw an object of logical width w at
+ * layout-x coord x, starting at offset dx into the object, by
+ * setting the painter's clipping so as to crop the object at the
+ * right edge of the row if it would otherwise overrun. The
+ * return value is the amount of the object visible on this row
+ * (i.e. the increment in offset for the next call to this method)
+ * or zero if no crop was necessary. The canvas coords at which
+ * the object should subsequently be drawn are returned in coords.
+ *
+ * This function calls painter.save(), and the caller must call
+ * painter.restore() after use.
+ */
+ virtual double setPainterClipping(QPainter *, double layoutX, int layoutY,
+ double dx, double w, LinedStaffCoords &coords,
+ FitPolicy policy);
+
+ /**
+ * Set a single pixmap to a notation element, or split it into
+ * bits if it overruns the end of a row and set the bits
+ * separately.
+ */
+ virtual void setPixmap(NotationElement *, QCanvasPixmap *, int z,
+ FitPolicy policy);
+
+ bool isSelected(NotationElementList::iterator);
+
+ typedef std::set<QCanvasSimpleSprite *> SpriteSet;
+ SpriteSet m_timeSigs;
+
+ typedef std::set<QCanvasItem *> ItemSet;
+ ItemSet m_repeatedClefsAndKeys;
+
+ typedef std::pair<int, Clef> ClefChange;
+ FastVector<ClefChange> m_clefChanges;
+
+ typedef std::pair<int, ::Rosegarden::Key> KeyChange;
+ FastVector<KeyChange> m_keyChanges;
+
+ void truncateClefsAndKeysAt(int);
+
+ /** Verify that a possible Clef or Key in bar is already inserted
+ * in m_clefChange or m_keyChange.
+ * If not, do the insertion.
+ */
+ void checkAndCompleteClefsAndKeys(int bar);
+
+ NotePixmapFactory *m_notePixmapFactory;
+ NotePixmapFactory *m_graceNotePixmapFactory;
+ QCanvasSimpleSprite *m_previewSprite;
+ QCanvasSimpleSprite *m_staffName;
+ std::string m_staffNameText;
+ NotationView *m_notationView;
+ int m_legerLineCount;
+ int m_barNumbersEvery;
+ bool m_colourQuantize;
+ bool m_showUnknowns;
+ bool m_showRanges;
+ bool m_showCollisions;
+ int m_keySigCancelMode;
+
+ QPainter *m_printPainter;
+
+ enum BarStatus { UnRendered = 0, Rendered, Positioned };
+ typedef std::map<int, BarStatus> BarStatusMap;
+ BarStatusMap m_status;
+ std::pair<int, int> m_lastRenderCheck;
+ bool m_ready;
+
+ int m_lastRenderedBar;
+};
+
+
+}
+
+#endif
diff --git a/src/gui/editors/notation/NotationStrings.cpp b/src/gui/editors/notation/NotationStrings.cpp
new file mode 100644
index 0000000..6f8defd
--- /dev/null
+++ b/src/gui/editors/notation/NotationStrings.cpp
@@ -0,0 +1,301 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
+
+/*
+ Rosegarden
+ A MIDI and audio sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "NotationStrings.h"
+#include <kapplication.h>
+
+#include <klocale.h>
+#include "misc/Strings.h"
+#include "document/ConfigGroups.h"
+#include "base/Exception.h"
+#include "base/NotationTypes.h"
+#include "gui/configuration/GeneralConfigurationPage.h"
+#include <kconfig.h>
+#include <qstring.h>
+
+
+namespace Rosegarden
+{
+
+QString
+NotationStrings::addDots(QString s, int dots,
+ bool hyphenate, bool internationalize)
+{
+ if (!dots)
+ return s;
+
+ if (internationalize) {
+ if (dots > 1) {
+ if (hyphenate)
+ return i18n("%1-dotted-%2").arg(dots).arg(s);
+ else
+ return i18n("%1-dotted %2").arg(dots).arg(s);
+ } else {
+ if (hyphenate)
+ return i18n("dotted-%1").arg(s);
+ else
+ return i18n("dotted %1").arg(s);
+ }
+ } else {
+ if (dots > 1) {
+ if (hyphenate)
+ return QString("%1-dotted-%2").arg(dots).arg(s);
+ else
+ return QString("%1-dotted %2").arg(dots).arg(s);
+ } else {
+ if (hyphenate)
+ return QString("dotted-%1").arg(s);
+ else
+ return QString("dotted %1").arg(s);
+ }
+ }
+}
+
+QString
+NotationStrings::getNoteName(Note note, bool plural, bool triplet)
+{
+ Note::Type type = note.getNoteType();
+ int dots = note.getDots();
+
+ static const QString names[] = {
+ i18n("sixty-fourth note"), i18n("thirty-second note"),
+ i18n("sixteenth note"), i18n("eighth note"),
+ i18n("quarter note"), i18n("half note"),
+ i18n("whole note"), i18n("double whole note")
+ };
+ static const QString pluralnames[] = {
+ i18n("sixty-fourth notes"), i18n("thirty-second notes"),
+ i18n("sixteenth notes"), i18n("eighth notes"),
+ i18n("quarter notes"), i18n("half notes"),
+ i18n("whole notes"), i18n("double whole notes")
+ };
+
+ if (plural && triplet) {
+ return addDots(i18n("%1 triplets").arg(names[type]), dots, false, true); // TODO PLURAL - this is broken because it assumes there's only 1 plural form
+ } else if (plural) {
+ return addDots(pluralnames[type], dots, false, true);
+ } else if (triplet) {
+ return addDots(i18n("%1 triplet").arg(names[type]), dots, false, true);
+ } else {
+ return addDots(names[type], dots, false, true);
+ }
+}
+
+QString
+NotationStrings::getAmericanName(Note note, bool plural, bool triplet)
+{
+ Note::Type type = note.getNoteType();
+ int dots = note.getDots();
+
+ static const QString names[] = {
+ "sixty-fourth note", "thirty-second note",
+ "sixteenth note", "eighth note",
+ "quarter note", "half note",
+ "whole note", "double whole note"
+ };
+ static const QString pluralnames[] = {
+ "sixty-fourth notes", "thirty-second notes",
+ "sixteenth notes", "eighth notes",
+ "quarter notes", "half notes",
+ "whole notes", "double whole notes"
+ };
+
+ if (plural && triplet) {
+ return addDots(QString("%1 triplets").arg(names[type]), dots, false, false);
+ } else if (plural) {
+ return addDots(pluralnames[type], dots, false, false);
+ } else if (triplet) {
+ return addDots(QString("%1 triplet").arg(names[type]), dots, false, false);
+ } else {
+ return addDots(names[type], dots, false, false);
+ }
+}
+
+QString
+NotationStrings::getShortNoteName(Note note, bool plural, bool triplet)
+{
+ Note::Type type = note.getNoteType();
+ int dots = note.getDots();
+
+ static const QString names[] = {
+ i18n("64th"), i18n("32nd"), i18n("16th"), i18n("8th"),
+ i18n("quarter"), i18n("half"), i18n("whole"),
+ i18n("double whole")
+ };
+ static const QString pluralnames[] = {
+ i18n("64ths"), i18n("32nds"), i18n("16ths"), i18n("8ths"),
+ i18n("quarters"), i18n("halves"), i18n("wholes"),
+ i18n("double wholes")
+ };
+
+ if (plural && triplet) {
+ return addDots(i18n("%1 triplets").arg(names[type]), dots, false, true); // TODO - this is broken because it assumes there's only 1 plural form
+ } else if (plural) {
+ return addDots(pluralnames[type], dots, false, true);
+ } else if (triplet) {
+ return addDots(i18n("%1 triplet").arg(names[type]), dots, false, true);
+ } else {
+ return addDots(names[type], dots, false, true);
+ }
+}
+
+QString
+NotationStrings::getReferenceName(Note note, bool isRest)
+{
+ Note::Type type = note.getNoteType();
+ int dots = note.getDots();
+
+ static const QString names[] = {
+ "hemidemisemi", "demisemi", "semiquaver",
+ "quaver", "crotchet", "minim", "semibreve", "breve"
+ };
+
+ QString name(names[type]);
+ if (isRest)
+ name = "rest-" + name;
+ return addDots(name, dots, true, false);
+}
+
+Note
+NotationStrings::getNoteForName(QString name)
+{
+ std::string origName(qstrtostr(name));
+ int pos = name.find('-');
+ int dots = 0;
+
+ if (pos > 0 && pos < 6 && pos < int(name.length()) - 1) {
+ dots = name.left(pos).toInt();
+ name = name.right(name.length() - pos - 1);
+ if (dots < 2) {
+ throw MalformedNoteName("Non-numeric or invalid dot count in \"" +
+ origName + "\"");
+ }
+ }
+
+ if (name.length() > 7 &&
+ (name.left(7) == "dotted " || name.left(7) == "dotted-")) {
+ if (dots == 0)
+ dots = 1;
+ name = name.right(name.length() - 7);
+ } else {
+ if (dots > 1) {
+ throw MalformedNoteName("Dot count without dotted tag in \"" +
+ origName + "\"");
+ }
+ }
+
+ if (name.length() > 5 && name.right(5) == " note") {
+ name = name.left(name.length() - 5);
+ }
+
+ Note::Type type;
+
+ static const char *names[][4] = {
+ { "64th", "sixty-fourth", "hemidemisemi", "hemidemisemiquaver"
+ },
+ { "32nd", "thirty-second", "demisemi", "demisemiquaver" },
+ { "16th", "sixteenth", "semi", "semiquaver" },
+ { "8th", "eighth", 0, "quaver" },
+ { "quarter", 0, 0, "crotchet", },
+ { "half", 0, 0, "minim" },
+ { "whole", 0, 0, "semibreve" },
+ { "double whole", 0, 0, "breve" }
+ };
+
+ for (type = Note::Shortest; type <= Note::Longest; ++type) {
+ for (int i = 0; i < 4; ++i) {
+ if (!names[type][i])
+ continue;
+ if (name == names[type][i])
+ return Note(type, dots);
+ }
+ }
+
+ throw MalformedNoteName("Can't parse note name \"" + origName + "\"");
+}
+
+QString
+NotationStrings::makeNoteMenuLabel(timeT duration,
+ bool brief,
+ timeT &errorReturn,
+ bool plural)
+{
+ Note nearestNote = Note::getNearestNote(duration);
+ bool triplet = false;
+ errorReturn = 0;
+
+ if (duration == 0)
+ return "0";
+
+ if (nearestNote.getDuration() != duration) {
+ Note tripletNote = Note::getNearestNote(duration * 3 / 2);
+ if (tripletNote.getDuration() == duration * 3 / 2) {
+ nearestNote = tripletNote;
+ triplet = true;
+ } else {
+ errorReturn = duration - nearestNote.getDuration();
+ duration = nearestNote.getDuration();
+ }
+ }
+
+ KConfig *config = kapp->config();
+ config->setGroup(GeneralOptionsConfigGroup);
+ GeneralConfigurationPage::NoteNameStyle noteNameStyle =
+ (GeneralConfigurationPage::NoteNameStyle)
+ config->readUnsignedNumEntry
+ ("notenamestyle", GeneralConfigurationPage::Local);
+
+ if (brief) {
+
+ timeT wholeNote = Note(Note::Semibreve).getDuration();
+ if ((wholeNote / duration) * duration == wholeNote) {
+ return QString("1/%1").arg(wholeNote / duration);
+ } else if ((duration / wholeNote) * wholeNote == duration) {
+ return QString("%1/1").arg(duration / wholeNote);
+ } else {
+ return i18n("%1 ticks").arg(duration);
+ plural = false;
+ }
+
+ } else {
+ QString noteName;
+
+ switch (noteNameStyle) {
+
+ case GeneralConfigurationPage::American:
+ noteName = getAmericanName(nearestNote, plural, triplet);
+ break;
+
+ case GeneralConfigurationPage::Local:
+ noteName = getNoteName(nearestNote, plural, triplet);
+ break;
+ }
+
+ // Already internationalised, if appropriate
+ return noteName;
+ }
+}
+
+}
diff --git a/src/gui/editors/notation/NotationStrings.h b/src/gui/editors/notation/NotationStrings.h
new file mode 100644
index 0000000..d79dff3
--- /dev/null
+++ b/src/gui/editors/notation/NotationStrings.h
@@ -0,0 +1,121 @@
+
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
+
+/*
+ Rosegarden
+ A MIDI and audio sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_NOTATIONSTRINGS_H_
+#define _RG_NOTATIONSTRINGS_H_
+
+#include "base/Exception.h"
+#include "base/NotationTypes.h"
+#include <qstring.h>
+#include "base/Event.h"
+
+
+
+
+namespace Rosegarden
+{
+
+
+
+/**
+ * String factory for note names, etc. used in the GUI
+ * Replaces use of base/NotationTypes.h strings which should
+ * be used only for non-user purposes.
+ */
+class NotationStrings
+{
+public:
+ NotationStrings();
+ ~NotationStrings();
+
+
+ /**
+ * Get the name of a note. The default return values are American
+ * (e.g. quarter note, dotted sixteenth note). If the app is
+ * internationalised, you will get return names local to your
+ * region. Note that this includes English note names- set your
+ * LC_LANG to en_GB.
+ */
+ static QString getNoteName(Note note,
+ bool plural = false, bool triplet = false);
+
+ /**
+ * Get the UNTRANSLATED American name of a note. This may be
+ * useful if the user has specified that they'd prefer American
+ * names to local names.
+ */
+ static QString getAmericanName(Note note,
+ bool plural = false, bool triplet = false);
+
+ /**
+ * Get the short name of a note. The default return values are
+ * American (e.g. quarter, dotted 16th). If the app is
+ * internationalised, you will get return names local to your
+ * region. Note that this includes English note names- set your
+ * LC_LANG to en_GB.
+ */
+ static QString getShortNoteName(Note note,
+ bool plural = false, bool triplet = false);
+
+
+ /**
+ * Get the UNTRANSLATED reference name of a note or rest. This is the
+ * formal name used to name pixmap files and the like, so the exact
+ * values of these strings are pretty sensitive.
+ */
+ static QString getReferenceName(Note note, bool isRest = false);
+
+ typedef Exception MalformedNoteName;
+
+ /**
+ * Get the note corresponding to the given string, which must be a
+ * reference name or an untranslated British, American or short name.
+ * May throw MalformedNoteName.
+ */
+ static Note getNoteForName(QString name);
+
+ /**
+ * Construct a label to describe the given duration as a note name in
+ * the proper locale. Uses the nearest available note to the duration
+ * and returns a non-zero value in errorReturn if it was not an exact
+ * match for the required duration.
+ */
+ static QString makeNoteMenuLabel(timeT duration,
+ bool brief,
+ timeT &errorReturn,
+ bool plural = false);
+
+private:
+ /**
+ * Return a string representing the dotted version of the input str.
+ */
+ static QString addDots(QString s, int dots,
+ bool hyphenate, bool internationalize);
+
+};
+
+}
+
+#endif
diff --git a/src/gui/editors/notation/NotationTool.cpp b/src/gui/editors/notation/NotationTool.cpp
new file mode 100644
index 0000000..8e82107
--- /dev/null
+++ b/src/gui/editors/notation/NotationTool.cpp
@@ -0,0 +1,57 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
+
+/*
+ Rosegarden
+ A MIDI and audio sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "NotationTool.h"
+#include "misc/Debug.h"
+
+#include "gui/general/EditTool.h"
+#include "NotationView.h"
+#include <qstring.h>
+
+
+namespace Rosegarden
+{
+
+NotationTool::NotationTool(const QString& menuName, NotationView* view)
+ : EditTool(menuName, view),
+ m_nParentView(view)
+{}
+
+NotationTool::~NotationTool()
+{
+ NOTATION_DEBUG << "NotationTool::~NotationTool()" << endl;
+
+ // delete m_menu;
+ // m_parentView->factory()->removeClient(this);
+ // m_instance = 0;
+}
+
+void NotationTool::ready()
+{
+ m_nParentView->setCanvasCursor(Qt::arrowCursor);
+ m_nParentView->setHeightTracking(false);
+}
+
+}
diff --git a/src/gui/editors/notation/NotationTool.h b/src/gui/editors/notation/NotationTool.h
new file mode 100644
index 0000000..ab1020a
--- /dev/null
+++ b/src/gui/editors/notation/NotationTool.h
@@ -0,0 +1,93 @@
+
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
+
+/*
+ Rosegarden
+ A MIDI and audio sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "NotationToolBox.h"
+
+#include "gui/general/EditToolBox.h"
+#include "gui/general/EditTool.h"
+#include "NotationView.h"
+#include "NoteInserter.h"
+#include "RestInserter.h"
+#include "ClefInserter.h"
+#include "TextInserter.h"
+#include "GuitarChordInserter.h"
+#include "NotationEraser.h"
+#include "NotationSelector.h"
+
+#include <qstring.h>
+#include <kmessagebox.h>
+
+namespace Rosegarden
+{
+
+NotationToolBox::NotationToolBox(NotationView *parent)
+ : EditToolBox(parent),
+ m_nParentView(parent)
+{
+ //m_tools.setAutoDelete(true);
+}
+
+EditTool* NotationToolBox::createTool(const QString& toolName)
+{
+ NotationTool* tool = 0;
+
+ QString toolNamelc = toolName.lower();
+
+ if (toolNamelc == NoteInserter::ToolName)
+
+ tool = new NoteInserter(m_nParentView);
+
+ else if (toolNamelc == RestInserter::ToolName)
+
+ tool = new RestInserter(m_nParentView);
+
+ else if (toolNamelc == ClefInserter::ToolName)
+
+ tool = new ClefInserter(m_nParentView);
+
+ else if (toolNamelc == TextInserter::ToolName)
+
+ tool = new TextInserter(m_nParentView);
+
+ else if (toolNamelc == GuitarChordInserter::ToolName)
+
+ tool = new GuitarChordInserter(m_nParentView);
+
+/* else if (toolNamelc == LilyPondDirectiveInserter::ToolName)
+
+ tool = new LilyPondDirectiveInserter(m_nParentView);*/
+
+ else if (toolNamelc == NotationEraser::ToolName)
+
+ tool = new NotationEraser(m_nParentView);
+
+ else if (toolNamelc == NotationSelector::ToolName)
+
+ tool = new NotationSelector(m_nParentView);
+
+ else {
+ KMessageBox::error(0, QString("NotationToolBox::createTool : unrecognised toolname %1 (%2)")
+ .arg(toolName).arg(toolNamelc));
+ return 0;
+ }
+
+ m_tools.insert(toolName, tool);
+
+ return tool;
+}
+
+}
+#include "NotationToolBox.moc"
diff --git a/src/gui/editors/notation/NotationToolBox.h b/src/gui/editors/notation/NotationToolBox.h
new file mode 100644
index 0000000..48b1202
--- /dev/null
+++ b/src/gui/editors/notation/NotationToolBox.h
@@ -0,0 +1,65 @@
+
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
+
+/*
+ Rosegarden
+ A MIDI and audio sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#include <cmath>
+#include "NotationVLayout.h"
+#include "misc/Debug.h"
+
+#include <klocale.h>
+#include "base/Composition.h"
+#include "base/Event.h"
+#include "base/LayoutEngine.h"
+#include "base/NotationRules.h"
+#include "base/NotationTypes.h"
+#include "base/NotationQuantizer.h"
+#include "base/Staff.h"
+#include "gui/general/ProgressReporter.h"
+#include "gui/editors/guitar/Chord.h"
+#include "NotationChord.h"
+#include "NotationElement.h"
+#include "NotationProperties.h"
+#include "NotationStaff.h"
+#include "NotePixmapFactory.h"
+#include <kmessagebox.h>
+#include <qobject.h>
+#include <qstring.h>
+#include <qwidget.h>
+
+
+namespace Rosegarden
+{
+
+using namespace BaseProperties;
+
+
+NotationVLayout::NotationVLayout(Composition *c, NotePixmapFactory *npf,
+ const NotationProperties &properties,
+ QObject* parent, const char* name) :
+ ProgressReporter(parent, name),
+ m_composition(c),
+ m_npf(npf),
+ m_notationQuantizer(c->getNotationQuantizer()),
+ m_properties(properties)
+{
+ // empty
+}
+
+NotationVLayout::~NotationVLayout()
+{
+ // empty
+}
+
+NotationVLayout::SlurList &
+
+NotationVLayout::getSlurList(Staff &staff)
+{
+ SlurListMap::iterator i = m_slurs.find(&staff);
+ if (i == m_slurs.end()) {
+ m_slurs[&staff] = SlurList();
+ }
+
+ return m_slurs[&staff];
+}
+
+void
+NotationVLayout::reset()
+{
+ m_slurs.clear();
+}
+
+void
+NotationVLayout::resetStaff(Staff &staff, timeT, timeT)
+{
+ getSlurList(staff).clear();
+}
+
+void
+NotationVLayout::scanStaff(Staff &staffBase, timeT, timeT)
+{
+ START_TIMING;
+
+ NotationStaff &staff = dynamic_cast<NotationStaff &>(staffBase);
+ NotationElementList *notes = staff.getViewElementList();
+
+ NotationElementList::iterator from = notes->begin();
+ NotationElementList::iterator to = notes->end();
+ NotationElementList::iterator i;
+
+ for (i = from; i != to; ++i) {
+
+ NotationElement *el = static_cast<NotationElement*>(*i);
+
+ // Displaced Y will only be used for certain events -- in
+ // particular not for notes, whose y-coord is obviously kind
+ // of meaningful.
+ double displacedY = 0.0;
+ long dyRaw = 0;
+ el->event()->get<Int>(DISPLACED_Y, dyRaw);
+ displacedY = double(dyRaw * m_npf->getLineSpacing()) / 1000.0;
+
+ el->setLayoutY(staff.getLayoutYForHeight( -9) + displacedY);
+
+ if (el->isRest()) {
+
+ // rests for notes longer than the minim have hotspots
+ // aligned with the line above the middle line; the rest
+ // are aligned with the middle line
+
+ long noteType;
+ bool hasNoteType = el->event()->get
+ <Int>(NOTE_TYPE, noteType);
+ if (hasNoteType && noteType > Note::Minim) {
+ el->setLayoutY(staff.getLayoutYForHeight(6) + displacedY);
+ } else {
+ el->setLayoutY(staff.getLayoutYForHeight(4) + displacedY);
+ }
+
+ // Fix for bug 1090767 Rests outside staves have wrong glyphs
+ // by William <rosegarden4c AT orthoset.com>
+ // We use a "rest-outside-stave" glyph for any minim/semibreve/breve
+ // rest that has been displaced vertically e.g. by fine-positioning
+ // outside the stave. For small vertical displacements that keep
+ // the rest inside the stave, we use the "rest-inside-stave" glyph
+ // and also discretise the displacement into multiples of the
+ // stave-line spacing. The outside-stave glyphs match the character
+ // numbers 1D13A, 1D13B and 1D13C in the Unicode 4.0 standard.
+
+ if (hasNoteType && (displacedY > 0.1 || displacedY < -0.1)) {
+
+ // a fiddly check for transition from inside to outside:
+
+ int min = -1, max = 1;
+
+ switch (noteType) {
+ case Note::Breve:
+ min = -1;
+ max = 2;
+ break;
+ case Note::Semibreve:
+ min = -1;
+ max = 3;
+ break;
+ case Note::Minim:
+ min = -2;
+ max = 2;
+ break;
+ case Note::Crotchet:
+ min = -1;
+ max = 3;
+ break;
+ case Note::Quaver:
+ min = -2;
+ max = 3;
+ break;
+ case Note::Semiquaver:
+ min = -3;
+ max = 3;
+ break;
+ case Note::Demisemiquaver:
+ min = -3;
+ max = 4;
+ break;
+ case Note::Hemidemisemiquaver:
+ min = -4;
+ max = 4;
+ break;
+ }
+
+ bool outside = false;
+
+ if (noteType == Note::Breve) {
+ if (nearbyint(displacedY) < min * m_npf->getLineSpacing() ||
+ nearbyint(displacedY) > max * m_npf->getLineSpacing()) {
+ outside = true;
+ }
+ } else {
+ if ((int)displacedY < min * m_npf->getLineSpacing() ||
+ (int)displacedY > max * m_npf->getLineSpacing()) {
+ outside = true;
+ }
+ }
+
+ el->event()->setMaybe<Bool>(m_properties.REST_OUTSIDE_STAVE,
+ outside);
+
+ if (!outside) {
+ displacedY = (double)m_npf->getLineSpacing() *
+ (int(nearbyint((double)displacedY /
+ m_npf->getLineSpacing())));
+ if (noteType > Note::Minim)
+ el->setLayoutY(staff.getLayoutYForHeight(6) + displacedY);
+ else
+ el->setLayoutY(staff.getLayoutYForHeight(4) + displacedY);
+ }
+
+ // if (displacedY != 0.0)
+ // NOTATION_DEBUG << "REST_OUTSIDE_STAVE AFTER "
+ // << " : displacedY : " << displacedY
+ // << " line-spacing : " << m_npf->getLineSpacing()
+ // << " time : " << (el->getViewAbsoluteTime())
+ // << endl;
+ } else {
+ el->event()->setMaybe<Bool>(m_properties.REST_OUTSIDE_STAVE,
+ false);
+ }
+
+ } else if (el->isNote()) {
+
+ NotationChord chord(*notes, i, m_notationQuantizer, m_properties);
+ if (chord.size() == 0)
+ continue;
+
+ std::vector<int> h;
+ for (unsigned int j = 0; j < chord.size(); ++j) {
+ long height = 0;
+ if (!(*chord[j])->event()->get
+ <Int>
+ (m_properties.HEIGHT_ON_STAFF, height)) {
+ std::cerr << QString("ERROR: Event in chord at %1 has no HEIGHT_ON_STAFF property!\nThis is a bug (the program would previously have crashed by now)").arg((*chord[j])->getViewAbsoluteTime()) << std::endl;
+ (*chord[j])->event()->dump(std::cerr);
+ }
+ h.push_back(height);
+ }
+ bool stemmed = chord.hasStem();
+ bool stemUp = chord.hasStemUp();
+ bool hasNoteHeadShifted = chord.hasNoteHeadShifted();
+
+ unsigned int flaggedNote = (stemUp ? chord.size() - 1 : 0);
+
+ bool hasShifted = chord.hasNoteHeadShifted();
+
+ double y0 = -1E50; // A very unlikely Y layout value
+
+ for (unsigned int j = 0; j < chord.size(); ++j) {
+
+ el = static_cast<NotationElement*>(*chord[j]);
+ el->setLayoutY(staff.getLayoutYForHeight(h[j]));
+
+ // Look for collision
+ const double eps = 0.001;
+ Event *eel = el->event();
+ double y = el->getLayoutY();
+ if (eel->has("pitch")) {
+ el->setIsColliding(fabs(y - y0) < eps);
+ y0 = y;
+ }
+
+
+ // These calculations and assignments are pretty much final
+ // if the chord is not in a beamed group, but if it is then
+ // they will be reworked by NotationGroup::applyBeam, which
+ // is called from NotationHLayout::layout, which is called
+ // after this. Any inaccuracies here for beamed groups
+ // should be stamped out there.
+
+ // el->event()->setMaybe<Bool>(STEM_UP, stemUp);
+ el->event()->setMaybe<Bool>(m_properties.VIEW_LOCAL_STEM_UP, stemUp);
+
+ bool primary =
+ ((stemmed && stemUp) ? (j == 0) : (j == chord.size() - 1));
+ el->event()->setMaybe<Bool>
+ (m_properties.CHORD_PRIMARY_NOTE, primary);
+
+ if (primary) {
+ el->event()->setMaybe<Int>
+ (m_properties.CHORD_MARK_COUNT, chord.getMarkCountForChord());
+ }
+
+ bool shifted = chord.isNoteHeadShifted(chord[j]);
+ el->event()->setMaybe<Bool>
+ (m_properties.NOTE_HEAD_SHIFTED, shifted);
+
+ el->event()->setMaybe<Bool>
+ (m_properties.NOTE_DOT_SHIFTED, false);
+ if (hasShifted && stemUp) {
+ long dots = 0;
+ (void)el->event()->get
+ <Int>(NOTE_DOTS, dots);
+ if (dots > 0) {
+ el->event()->setMaybe<Bool>
+ (m_properties.NOTE_DOT_SHIFTED, true);
+ }
+ }
+
+ el->event()->setMaybe<Bool>
+ (m_properties.NEEDS_EXTRA_SHIFT_SPACE,
+ hasNoteHeadShifted && !stemUp);
+
+ el->event()->setMaybe<Bool>
+ (m_properties.DRAW_FLAG, j == flaggedNote);
+
+ int stemLength = -1;
+ if (j != flaggedNote) {
+ stemLength = staff.getLayoutYForHeight(h[flaggedNote]) -
+ staff.getLayoutYForHeight(h[j]);
+ if (stemLength < 0)
+ stemLength = -stemLength;
+ // NOTATION_DEBUG << "Setting stem length to "
+ // << stemLength << endl;
+ } else {
+ int minStemLength = stemLength;
+ if (h[j] < -2 && stemUp) {
+ //!!! needs tuning, & applying for beamed stems too
+ minStemLength = staff.getLayoutYForHeight(h[j]) -
+ staff.getLayoutYForHeight(4);
+ } else if (h[j] > 10 && !stemUp) {
+ minStemLength = staff.getLayoutYForHeight(4) -
+ staff.getLayoutYForHeight(h[j]);
+ }
+ stemLength = std::max(minStemLength, stemLength);
+ }
+
+ el->event()->setMaybe<Int>
+ (m_properties.UNBEAMED_STEM_LENGTH, stemLength);
+ }
+
+
+ // #938545 (Broken notation: Duplicated note can float
+ // outside stave) -- Need to cope with the case where a
+ // note that's not a member of a chord (different stem
+ // direction &c) falls between notes that are members.
+ // Not optimal, as we can end up scanning the chord
+ // multiple times (we'll return to it after scanning the
+ // contained note). [We can't just iterate over all
+ // elements within the chord (as we can in hlayout)
+ // because we need them in height order.]
+
+ i = chord.getFirstElementNotInChord();
+ if (i == notes->end())
+ i = chord.getFinalElement();
+ else
+ --i;
+
+ } else {
+
+ if (el->event()->isa(Clef::EventType)) {
+
+ // clef pixmaps have the hotspot placed to coincide
+ // with the pitch of the clef -- so the alto clef
+ // should be "on" the middle line, the treble clef
+ // "on" the line below the middle, etc
+
+ el->setLayoutY(staff.getLayoutYForHeight
+ (Clef(*el->event()).getAxisHeight()));
+
+ } else if (el->event()->isa(Rosegarden::Key::EventType)) {
+
+ el->setLayoutY(staff.getLayoutYForHeight(12));
+
+ } else if (el->event()->isa(Text::EventType)) {
+
+ std::string type = Text::UnspecifiedType;
+ el->event()->get<String>(Text::TextTypePropertyName, type);
+
+ if (type == Text::Dynamic ||
+ type == Text::LocalDirection ||
+ type == Text::UnspecifiedType) {
+ el->setLayoutY(staff.getLayoutYForHeight(-7) + displacedY);
+ } else if (type == Text::Lyric) {
+ long verse = 0;
+ el->event()->get<Int>(Text::LyricVersePropertyName, verse);
+ el->setLayoutY(staff.getLayoutYForHeight(-10 - 3 * verse) + displacedY);
+ } else if (type == Text::Annotation) {
+ el->setLayoutY(staff.getLayoutYForHeight(-13) + displacedY);
+ } else {
+ el->setLayoutY(staff.getLayoutYForHeight(22) + displacedY);
+ }
+
+ } else if (el->event()->isa(Indication::EventType)) {
+
+ try {
+ std::string indicationType =
+ el->event()->get
+ <String>(Indication::IndicationTypePropertyName);
+
+ if (indicationType == Indication::Slur ||
+ indicationType == Indication::PhrasingSlur) {
+ getSlurList(staff).push_back(i);
+ }
+
+ if (indicationType == Indication::OttavaUp ||
+ indicationType == Indication::QuindicesimaUp) {
+ el->setLayoutY(staff.getLayoutYForHeight(15) + displacedY);
+ } else {
+ el->setLayoutY(staff.getLayoutYForHeight( -9) + displacedY);
+ }
+ } catch (...) {
+ el->setLayoutY(staff.getLayoutYForHeight( -9) + displacedY);
+ }
+
+ } else if (el->event()->isa(Guitar::Chord::EventType)) {
+
+ el->setLayoutY(staff.getLayoutYForHeight(22) + displacedY);
+ }
+ }
+ }
+
+ PRINT_ELAPSED("NotationVLayout::scanStaff");
+}
+
+void
+NotationVLayout::finishLayout(timeT, timeT)
+{
+ START_TIMING;
+
+ for (SlurListMap::iterator mi = m_slurs.begin();
+ mi != m_slurs.end(); ++mi) {
+
+ for (SlurList::iterator si = mi->second.begin();
+ si != mi->second.end(); ++si) {
+
+ NotationElementList::iterator i = *si;
+ NotationStaff &staff = dynamic_cast<NotationStaff &>(*(mi->first));
+
+ positionSlur(staff, i);
+ }
+ }
+
+ PRINT_ELAPSED("NotationVLayout::finishLayout");
+}
+
+void
+NotationVLayout::positionSlur(NotationStaff &staff,
+ NotationElementList::iterator i)
+{
+ NotationRules rules;
+
+ bool phrasing = ((*i)->event()->get
+ <String>(Indication::IndicationTypePropertyName)
+ == Indication::PhrasingSlur);
+
+ NotationElementList::iterator scooter = i;
+
+ timeT slurDuration = (*i)->event()->getDuration();
+ if (slurDuration == 0 && (*i)->event()->has("indicationduration")) {
+ slurDuration = (*i)->event()->get
+ <Int>("indicationduration"); // obs property
+ }
+ timeT endTime = (*i)->getViewAbsoluteTime() + slurDuration;
+
+ bool haveStart = false;
+
+ int startTopHeight = 4, endTopHeight = 4,
+ startBottomHeight = 4, endBottomHeight = 4,
+ maxTopHeight = 4, minBottomHeight = 4,
+ maxCount = 0, minCount = 0;
+
+ int startX = (int)(*i)->getLayoutX(), endX = startX + 10;
+ bool startStemUp = false, endStemUp = false;
+ long startMarks = 0, endMarks = 0;
+ bool startTied = false, endTied = false;
+ bool beamAbove = false, beamBelow = false;
+ bool dynamic = false;
+
+ std::vector<Event *> stemUpNotes, stemDownNotes;
+
+ // Scan the notes spanned by the slur, recording the top and
+ // bottom heights of the first and last chords, plus the presence
+ // of any troublesome beams and high or low notes in the body.
+
+ while (scooter != staff.getViewElementList()->end()) {
+
+ if ((*scooter)->getViewAbsoluteTime() >= endTime)
+ break;
+ Event *event = (*scooter)->event();
+
+ if (event->isa(Note::EventType)) {
+
+ long h = 0;
+ if (!event->get
+ <Int>(m_properties.HEIGHT_ON_STAFF, h)) {
+ KMessageBox::sorry
+ ((QWidget *)parent(), i18n("Spanned note at %1 has no HEIGHT_ON_STAFF property!\nThis is a bug (the program would previously have crashed by now)").arg((*scooter)->getViewAbsoluteTime()));
+ event->dump(std::cerr);
+ }
+
+ bool stemUp = rules.isStemUp(h);
+ event->get
+ <Bool>(m_properties.VIEW_LOCAL_STEM_UP, stemUp);
+
+ bool beamed = false;
+ event->get
+ <Bool>(m_properties.BEAMED, beamed);
+
+ bool primary = false;
+
+ if (event->get
+ <Bool>
+ (m_properties.CHORD_PRIMARY_NOTE, primary) && primary) {
+
+ NotationChord chord(*(staff.getViewElementList()), scooter,
+ m_notationQuantizer, m_properties);
+
+ if (beamed) {
+ if (stemUp)
+ beamAbove = true;
+ else
+ beamBelow = true;
+ }
+
+ if (!haveStart) {
+
+ startBottomHeight = chord.getLowestNoteHeight();
+ startTopHeight = chord.getHighestNoteHeight();
+ minBottomHeight = startBottomHeight;
+ maxTopHeight = startTopHeight;
+
+ startX = (int)(*scooter)->getLayoutX();
+ startStemUp = stemUp;
+ startMarks = chord.getMarkCountForChord();
+
+ bool tied = false;
+ if ((event->get
+ <Bool>(TIED_FORWARD, tied) && tied) ||
+ (event->get<Bool>(TIED_BACKWARD, tied) && tied)) {
+ startTied = true;
+ }
+
+ haveStart = true;
+
+ } else {
+ if (chord.getLowestNoteHeight() < minBottomHeight) {
+ minBottomHeight = chord.getLowestNoteHeight();
+ ++minCount;
+ }
+ if (chord.getHighestNoteHeight() > maxTopHeight) {
+ maxTopHeight = chord.getHighestNoteHeight();
+ ++maxCount;
+ }
+ }
+
+ endBottomHeight = chord.getLowestNoteHeight();
+ endTopHeight = chord.getHighestNoteHeight();
+ endX = (int)(*scooter)->getLayoutX();
+ endStemUp = stemUp;
+ endMarks = chord.getMarkCountForChord();
+
+ bool tied = false;
+ if ((event->get
+ <Bool>(TIED_FORWARD, tied) && tied) ||
+ (event->get<Bool>(TIED_BACKWARD, tied) && tied)) {
+ endTied = true;
+ }
+ }
+
+ if (!beamed) {
+ if (stemUp)
+ stemUpNotes.push_back(event);
+ else
+ stemDownNotes.push_back(event);
+ }
+
+ } else if (event->isa(Indication::EventType)) {
+
+ try {
+ std::string indicationType =
+ event->get
+ <String>(Indication::IndicationTypePropertyName);
+
+ if (indicationType == Indication::Crescendo ||
+ indicationType == Indication::Decrescendo)
+ dynamic = true;
+ } catch (...) { }
+ }
+
+ ++scooter;
+ }
+
+ bool above = true;
+
+ if ((*i)->event()->has(NotationProperties::SLUR_ABOVE) &&
+ (*i)->event()->isPersistent<Bool>(NotationProperties::SLUR_ABOVE)) {
+
+ (*i)->event()->get
+ <Bool>(NotationProperties::SLUR_ABOVE, above);
+
+ } else if (phrasing) {
+
+ int score = 0; // for "above"
+
+ if (dynamic)
+ score += 2;
+
+ if (startStemUp == endStemUp) {
+ if (startStemUp)
+ score -= 2;
+ else
+ score += 2;
+ } else if (beamBelow != beamAbove) {
+ if (beamAbove)
+ score -= 2;
+ else
+ score += 2;
+ }
+
+ if (maxTopHeight < 6)
+ score += 1;
+ else if (minBottomHeight > 2)
+ score -= 1;
+
+ if (stemUpNotes.size() != stemDownNotes.size()) {
+ if (stemUpNotes.size() < stemDownNotes.size())
+ score += 1;
+ else
+ score -= 1;
+ }
+
+ above = (score >= 0);
+
+ } else {
+
+ if (startStemUp == endStemUp) {
+ above = !startStemUp;
+ } else if (beamBelow) {
+ above = true;
+ } else if (beamAbove) {
+ above = false;
+ } else if (stemUpNotes.size() != stemDownNotes.size()) {
+ above = (stemUpNotes.size() < stemDownNotes.size());
+ } else {
+ above = ((startTopHeight - 4) + (endTopHeight - 4) +
+ (4 - startBottomHeight) + (4 - endBottomHeight) <= 8);
+ }
+ }
+
+ // now choose the actual y-coord of the slur based on the side
+ // we've decided to put it on
+
+ int startHeight, endHeight;
+ int startOffset = 2, endOffset = 2;
+
+ if (above) {
+
+ if (!startStemUp)
+ startOffset += startMarks * 2;
+ else
+ startOffset += 5;
+
+ if (!endStemUp)
+ endOffset += startMarks * 2;
+ else
+ endOffset += 5;
+
+ startHeight = startTopHeight + startOffset;
+ endHeight = endTopHeight + endOffset;
+
+ bool maxRelevant = ((maxTopHeight != endTopHeight) || (maxCount > 1));
+ if (maxRelevant) {
+ int midHeight = (startHeight + endHeight) / 2;
+ if (maxTopHeight > midHeight - 1) {
+ startHeight += maxTopHeight - midHeight + 1;
+ endHeight += maxTopHeight - midHeight + 1;
+ }
+ }
+
+ } else {
+
+ if (startStemUp)
+ startOffset += startMarks * 2;
+ else
+ startOffset += 5;
+
+ if (endStemUp)
+ endOffset += startMarks * 2;
+ else
+ endOffset += 5;
+
+ startHeight = startBottomHeight - startOffset;
+ endHeight = endBottomHeight - endOffset;
+
+ bool minRelevant = ((minBottomHeight != endBottomHeight) || (minCount > 1));
+ if (minRelevant) {
+ int midHeight = (startHeight + endHeight) / 2;
+ if (minBottomHeight < midHeight + 1) {
+ startHeight -= midHeight - minBottomHeight + 1;
+ endHeight -= midHeight - minBottomHeight + 1;
+ }
+ }
+ }
+
+ int y0 = staff.getLayoutYForHeight(startHeight),
+ y1 = staff.getLayoutYForHeight(endHeight);
+
+ int dy = y1 - y0;
+ int length = endX - startX;
+ int diff = staff.getLayoutYForHeight(0) - staff.getLayoutYForHeight(3);
+ if (length < diff*10)
+ diff /= 2;
+ if (length > diff*3)
+ length -= diff / 2;
+ startX += diff;
+
+ (*i)->event()->setMaybe<Bool>(NotationProperties::SLUR_ABOVE, above);
+ (*i)->event()->setMaybe<Int>(m_properties.SLUR_Y_DELTA, dy);
+ (*i)->event()->setMaybe<Int>(m_properties.SLUR_LENGTH, length);
+
+ double displacedX = 0.0, displacedY = 0.0;
+
+ long dxRaw = 0;
+ (*i)->event()->get<Int>(DISPLACED_X, dxRaw);
+ displacedX = double(dxRaw * m_npf->getNoteBodyWidth()) / 1000.0;
+
+ long dyRaw = 0;
+ (*i)->event()->get<Int>(DISPLACED_Y, dyRaw);
+ displacedY = double(dyRaw * m_npf->getLineSpacing()) / 1000.0;
+
+ (*i)->setLayoutX(startX + displacedX);
+ (*i)->setLayoutY(y0 + displacedY);
+}
+
+}
diff --git a/src/gui/editors/notation/NotationVLayout.h b/src/gui/editors/notation/NotationVLayout.h
new file mode 100644
index 0000000..83a16c1
--- /dev/null
+++ b/src/gui/editors/notation/NotationVLayout.h
@@ -0,0 +1,122 @@
+
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
+
+/*
+ Rosegarden
+ A MIDI and audio sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_NOTATIONVLAYOUT_H_
+#define _RG_NOTATIONVLAYOUT_H_
+
+#include "base/FastVector.h"
+#include "base/LayoutEngine.h"
+#include "gui/general/ProgressReporter.h"
+#include <map>
+#include "base/Event.h"
+
+#include "NotationElement.h"
+
+
+class SlurList;
+class QObject;
+
+
+namespace Rosegarden
+{
+
+class Staff;
+class Quantizer;
+class Composition;
+class NotePixmapFactory;
+class NotationStaff;
+class NotationProperties;
+class Composition;
+
+
+/**
+ * Vertical notation layout
+ *
+ * computes the Y coordinate of notation elements
+ */
+
+class NotationVLayout : public ProgressReporter,
+ public VerticalLayoutEngine
+{
+public:
+ NotationVLayout(Composition *c, NotePixmapFactory *npf,
+ const NotationProperties &properties,
+ QObject* parent, const char* name = 0);
+
+ virtual ~NotationVLayout();
+
+ void setNotePixmapFactory(NotePixmapFactory *npf) {
+ m_npf = npf;
+ }
+
+ /**
+ * Resets internal data stores for all staffs
+ */
+ virtual void reset();
+
+ /**
+ * Resets internal data stores for a specific staff
+ */
+ virtual void resetStaff(Staff &,
+ timeT = 0,
+ timeT = 0);
+
+ /**
+ * Lay out a single staff.
+ */
+ virtual void scanStaff(Staff &,
+ timeT = 0,
+ timeT = 0);
+
+ /**
+ * Do any layout dependent on more than one staff. As it
+ * happens, we have none, but we do have some layout that
+ * depends on the final results from the horizontal layout
+ * (for slurs), so we should do that here
+ */
+ virtual void finishLayout(timeT = 0,
+ timeT = 0);
+
+private:
+ void positionSlur(NotationStaff &staff, NotationElementList::iterator i);
+
+ typedef FastVector<NotationElementList::iterator> SlurList;
+ typedef std::map<Staff *, SlurList> SlurListMap;
+
+ //--------------- Data members ---------------------------------
+
+ SlurListMap m_slurs;
+ SlurList &getSlurList(Staff &);
+
+ Composition *m_composition;
+ NotePixmapFactory *m_npf;
+ const Quantizer *m_notationQuantizer;
+ const NotationProperties &m_properties;
+};
+
+
+}
+
+#endif
diff --git a/src/gui/editors/notation/NotationView.cpp b/src/gui/editors/notation/NotationView.cpp
new file mode 100644
index 0000000..66cb4b3
--- /dev/null
+++ b/src/gui/editors/notation/NotationView.cpp
@@ -0,0 +1,7552 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
+
+/*
+ Rosegarden
+ A MIDI and audio sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "NotationView.h"
+#include <list>
+#include <qlayout.h>
+#include "misc/Debug.h"
+#include <kapplication.h>
+
+#include "gui/editors/segment/TrackEditor.h"
+#include "gui/editors/segment/TrackButtons.h"
+#include "base/BaseProperties.h"
+#include <klocale.h>
+#include <kstddirs.h>
+#include "misc/Strings.h"
+#include "base/AnalysisTypes.h"
+#include "base/Clipboard.h"
+#include "base/Composition.h"
+#include "base/CompositionTimeSliceAdapter.h"
+#include "base/Configuration.h"
+#include "base/Device.h"
+#include "base/Event.h"
+#include "base/Exception.h"
+#include "base/Instrument.h"
+#include "base/MidiDevice.h"
+#include "base/MidiTypes.h"
+#include "base/NotationTypes.h"
+#include "base/Profiler.h"
+#include "base/PropertyName.h"
+#include "base/NotationQuantizer.h"
+#include "base/RealTime.h"
+#include "base/RulerScale.h"
+#include "base/Segment.h"
+#include "base/Selection.h"
+#include "base/Staff.h"
+#include "base/Studio.h"
+#include "base/Track.h"
+#include "ClefInserter.h"
+#include "commands/edit/AddDotCommand.h"
+#include "commands/edit/ClearTriggersCommand.h"
+#include "commands/edit/CollapseNotesCommand.h"
+#include "commands/edit/CopyCommand.h"
+#include "commands/edit/CutAndCloseCommand.h"
+#include "commands/edit/CutCommand.h"
+#include "commands/edit/EraseCommand.h"
+#include "commands/edit/EventEditCommand.h"
+#include "commands/edit/EventQuantizeCommand.h"
+#include "commands/edit/InsertTriggerNoteCommand.h"
+#include "commands/edit/PasteEventsCommand.h"
+#include "commands/edit/SetLyricsCommand.h"
+#include "commands/edit/SetNoteTypeCommand.h"
+#include "commands/edit/SetTriggerCommand.h"
+#include "commands/edit/TransposeCommand.h"
+#include "commands/notation/AddFingeringMarkCommand.h"
+#include "commands/notation/AddIndicationCommand.h"
+#include "commands/notation/AddMarkCommand.h"
+#include "commands/notation/AddSlashesCommand.h"
+#include "commands/notation/AddTextMarkCommand.h"
+#include "commands/notation/AutoBeamCommand.h"
+#include "commands/notation/BeamCommand.h"
+#include "commands/notation/BreakCommand.h"
+#include "commands/notation/ChangeSlurPositionCommand.h"
+#include "commands/notation/ChangeTiePositionCommand.h"
+#include "commands/notation/ChangeStemsCommand.h"
+#include "commands/notation/ChangeStyleCommand.h"
+#include "commands/notation/ClefInsertionCommand.h"
+#include "commands/notation/CollapseRestsCommand.h"
+#include "commands/notation/DeCounterpointCommand.h"
+#include "commands/notation/EraseEventCommand.h"
+#include "commands/notation/FixNotationQuantizeCommand.h"
+#include "commands/notation/IncrementDisplacementsCommand.h"
+#include "commands/notation/InterpretCommand.h"
+#include "commands/notation/KeyInsertionCommand.h"
+#include "commands/notation/MakeAccidentalsCautionaryCommand.h"
+#include "commands/notation/MakeChordCommand.h"
+#include "commands/notation/MakeNotesViableCommand.h"
+#include "commands/notation/MultiKeyInsertionCommand.h"
+#include "commands/notation/NormalizeRestsCommand.h"
+#include "commands/notation/RemoveFingeringMarksCommand.h"
+#include "commands/notation/RemoveMarksCommand.h"
+#include "commands/notation/RemoveNotationQuantizeCommand.h"
+#include "commands/notation/ResetDisplacementsCommand.h"
+#include "commands/notation/RespellCommand.h"
+#include "commands/notation/RestoreSlursCommand.h"
+#include "commands/notation/RestoreTiesCommand.h"
+#include "commands/notation/RestoreStemsCommand.h"
+#include "commands/notation/SetVisibilityCommand.h"
+#include "commands/notation/SustainInsertionCommand.h"
+#include "commands/notation/TextInsertionCommand.h"
+#include "commands/notation/TieNotesCommand.h"
+#include "commands/notation/TupletCommand.h"
+#include "commands/notation/UntieNotesCommand.h"
+#include "commands/notation/UnTupletCommand.h"
+#include "commands/segment/PasteToTriggerSegmentCommand.h"
+#include "commands/segment/SegmentSyncCommand.h"
+#include "commands/segment/SegmentTransposeCommand.h"
+#include "commands/segment/RenameTrackCommand.h"
+#include "document/RosegardenGUIDoc.h"
+#include "document/ConfigGroups.h"
+#include "document/io/LilyPondExporter.h"
+#include "GuitarChordInserter.h"
+#include "gui/application/SetWaitCursor.h"
+#include "gui/application/RosegardenGUIView.h"
+#include "gui/dialogs/ClefDialog.h"
+#include "gui/dialogs/EventEditDialog.h"
+#include "gui/dialogs/InterpretDialog.h"
+#include "gui/dialogs/IntervalDialog.h"
+#include "gui/dialogs/KeySignatureDialog.h"
+#include "gui/dialogs/LilyPondOptionsDialog.h"
+#include "gui/dialogs/LyricEditDialog.h"
+#include "gui/dialogs/MakeOrnamentDialog.h"
+#include "gui/dialogs/PasteNotationDialog.h"
+#include "gui/dialogs/QuantizeDialog.h"
+#include "gui/dialogs/SimpleEventEditDialog.h"
+#include "gui/dialogs/TextEventDialog.h"
+#include "gui/dialogs/TupletDialog.h"
+#include "gui/dialogs/UseOrnamentDialog.h"
+#include "gui/rulers/StandardRuler.h"
+#include "gui/general/ActiveItem.h"
+#include "gui/general/ClefIndex.h"
+#include "gui/general/EditViewBase.h"
+#include "gui/general/EditView.h"
+#include "gui/general/GUIPalette.h"
+#include "gui/general/LinedStaff.h"
+#include "gui/general/LinedStaffManager.h"
+#include "gui/general/ProgressReporter.h"
+#include "gui/general/PresetHandlerDialog.h"
+#include "gui/general/RosegardenCanvasView.h"
+#include "gui/kdeext/KTmpStatusMsg.h"
+#include "gui/kdeext/QCanvasSimpleSprite.h"
+#include "gui/rulers/ChordNameRuler.h"
+#include "gui/rulers/RawNoteRuler.h"
+#include "gui/rulers/TempoRuler.h"
+#include "gui/rulers/LoopRuler.h"
+#include "gui/studio/StudioControl.h"
+#include "gui/dialogs/EventFilterDialog.h"
+#include "gui/widgets/ProgressBar.h"
+#include "gui/widgets/ProgressDialog.h"
+#include "gui/widgets/ScrollBoxDialog.h"
+#include "gui/widgets/ScrollBox.h"
+#include "gui/widgets/QDeferScrollView.h"
+#include "NotationCanvasView.h"
+#include "NotationElement.h"
+#include "NotationEraser.h"
+#include "NotationHLayout.h"
+#include "NotationProperties.h"
+#include "NotationSelector.h"
+#include "NotationStaff.h"
+#include "NotationStrings.h"
+#include "NotationToolBox.h"
+#include "NotationVLayout.h"
+#include "NoteFontFactory.h"
+#include "NoteInserter.h"
+#include "NotePixmapFactory.h"
+#include "NoteStyleFactory.h"
+#include "NoteStyle.h"
+#include "RestInserter.h"
+#include "sound/MappedEvent.h"
+#include "TextInserter.h"
+#include "HeadersGroup.h"
+#include <kaction.h>
+#include <kcombobox.h>
+#include <kconfig.h>
+#include <kglobal.h>
+#include <klineeditdlg.h>
+#include <kmessagebox.h>
+#include <kprinter.h>
+#include <kprocess.h>
+#include <kprogress.h>
+#include <kstatusbar.h>
+#include <kstdaction.h>
+#include <ktempfile.h>
+#include <ktoolbar.h>
+#include <kxmlguiclient.h>
+#include <qbrush.h>
+#include <qcanvas.h>
+#include <qcursor.h>
+#include <qdialog.h>
+#include <qevent.h>
+#include <qfont.h>
+#include <qfontmetrics.h>
+#include <qhbox.h>
+#include <qiconset.h>
+#include <qlabel.h>
+#include <qobject.h>
+#include <qpaintdevicemetrics.h>
+#include <qpainter.h>
+#include <qpixmap.h>
+#include <qpoint.h>
+#include <qprinter.h>
+#include <qrect.h>
+#include <qregexp.h>
+#include <qsize.h>
+#include <qstring.h>
+#include <qtimer.h>
+#include <qwidget.h>
+#include <qvalidator.h>
+#include <algorithm>
+#include <qpushbutton.h>
+#include <qtooltip.h>
+
+
+namespace Rosegarden
+{
+
+class NoteActionData
+{
+public:
+ NoteActionData();
+ NoteActionData(const QString& _title,
+ QString _actionName,
+ QString _pixmapName,
+ int _keycode,
+ bool _rest,
+ Note::Type _noteType,
+ int _dots);
+
+ QString title;
+ QString actionName;
+ QString pixmapName;
+ int keycode;
+ bool rest;
+ Note::Type noteType;
+ int dots;
+};
+
+NoteActionData::NoteActionData()
+ : title(0),
+ actionName(0),
+ pixmapName(0),
+ keycode(0),
+ rest(false),
+ noteType(0),
+ dots(0)
+{
+}
+
+NoteActionData::NoteActionData(const QString& _title,
+ QString _actionName,
+ QString _pixmapName,
+ int _keycode,
+ bool _rest,
+ Note::Type _noteType,
+ int _dots)
+ : title(_title),
+ actionName(_actionName),
+ pixmapName(_pixmapName),
+ keycode(_keycode),
+ rest(_rest),
+ noteType(_noteType),
+ dots(_dots)
+{
+}
+
+
+class NoteChangeActionData
+{
+public:
+ NoteChangeActionData();
+ NoteChangeActionData(const QString &_title,
+ QString _actionName,
+ QString _pixmapName,
+ int _keycode,
+ bool _notationOnly,
+ Note::Type _noteType);
+
+ QString title;
+ QString actionName;
+ QString pixmapName;
+ int keycode;
+ bool notationOnly;
+ Note::Type noteType;
+};
+
+NoteChangeActionData::NoteChangeActionData()
+ : title(0),
+ actionName(0),
+ pixmapName(0),
+ keycode(0),
+ notationOnly(false),
+ noteType(0)
+{
+}
+
+NoteChangeActionData::NoteChangeActionData(const QString& _title,
+ QString _actionName,
+ QString _pixmapName,
+ int _keycode,
+ bool _notationOnly,
+ Note::Type _noteType)
+ : title(_title),
+ actionName(_actionName),
+ pixmapName(_pixmapName),
+ keycode(_keycode),
+ notationOnly(_notationOnly),
+ noteType(_noteType)
+{
+}
+
+
+class MarkActionData
+{
+public:
+ MarkActionData() :
+ title(0),
+ actionName(0),
+ keycode(0) { }
+
+ MarkActionData(const QString &_title,
+ QString _actionName,
+ int _keycode,
+ Mark _mark) :
+ title(_title),
+ actionName(_actionName),
+ keycode(_keycode),
+ mark(_mark) { }
+
+ QString title;
+ QString actionName;
+ int keycode;
+ Mark mark;
+};
+
+
+NotationView::NotationView(RosegardenGUIDoc *doc,
+ std::vector<Segment *> segments,
+ QWidget *parent,
+ bool showProgressive) :
+ EditView(doc, segments, 2, parent, "notationview"),
+ m_properties(getViewLocalPropertyPrefix()),
+ m_selectionCounter(0),
+ m_insertModeLabel(0),
+ m_annotationsLabel(0),
+ m_lilyPondDirectivesLabel(0),
+ m_progressBar(0),
+ m_currentNotePixmap(0),
+ m_hoveredOverNoteName(0),
+ m_hoveredOverAbsoluteTime(0),
+ m_currentStaff( -1),
+ m_lastFinishingStaff( -1),
+ m_title(0),
+ m_subtitle(0),
+ m_composer(0),
+ m_copyright(0),
+ m_insertionTime(0),
+ m_deferredCursorMove(NoCursorMoveNeeded),
+ m_lastNoteAction("crotchet"),
+ m_fontName(NoteFontFactory::getDefaultFontName()),
+ m_fontSize(NoteFontFactory::getDefaultSize(m_fontName)),
+ m_pageMode(LinedStaff::LinearMode),
+ m_leftGutter(20),
+ m_notePixmapFactory(new NotePixmapFactory(m_fontName, m_fontSize)),
+ m_hlayout(new NotationHLayout(&doc->getComposition(), m_notePixmapFactory,
+ m_properties, this)),
+ m_vlayout(new NotationVLayout(&doc->getComposition(), m_notePixmapFactory,
+ m_properties, this)),
+ m_chordNameRuler(0),
+ m_tempoRuler(0),
+ m_rawNoteRuler(0),
+ m_annotationsVisible(false),
+ m_lilyPondDirectivesVisible(false),
+ m_selectDefaultNote(0),
+ m_fontCombo(0),
+ m_fontSizeCombo(0),
+ m_spacingCombo(0),
+ m_fontSizeActionMenu(0),
+ m_pannerDialog(new ScrollBoxDialog(this, ScrollBox::FixHeight)),
+ m_renderTimer(0),
+ m_playTracking(true),
+ m_progressDisplayer(PROGRESS_NONE),
+ m_inhibitRefresh(true),
+ m_ok(false),
+ m_printMode(false),
+ m_printSize(8), // set in positionStaffs
+ m_showHeadersGroup(0),
+ m_headersGroupView(0),
+ m_headersGroup(0),
+ m_headersTopFrame(0),
+ m_showHeadersMenuEntry(0)
+{
+ initActionDataMaps(); // does something only the 1st time it's called
+
+ m_toolBox = new NotationToolBox(this);
+
+ assert(segments.size() > 0);
+ NOTATION_DEBUG << "NotationView ctor" << endl;
+
+
+ // Initialise the display-related defaults that will be needed
+ // by both the actions and the layout toolbar
+
+ m_config->setGroup(NotationViewConfigGroup);
+
+ m_showHeadersGroup = m_config->readNumEntry("shownotationheader",
+ HeadersGroup::DefaultShowMode);
+
+ m_fontName = qstrtostr(m_config->readEntry
+ ("notefont",
+ strtoqstr(NoteFontFactory::getDefaultFontName())));
+
+ try
+ {
+ (void)NoteFontFactory::getFont
+ (m_fontName,
+ NoteFontFactory::getDefaultSize(m_fontName));
+ } catch (Exception e)
+ {
+ m_fontName = NoteFontFactory::getDefaultFontName();
+ }
+
+ m_fontSize = m_config->readUnsignedNumEntry
+ ((segments.size() > 1 ? "multistaffnotesize" : "singlestaffnotesize"),
+ NoteFontFactory::getDefaultSize(m_fontName));
+
+ int defaultSpacing = m_config->readNumEntry("spacing", 100);
+ m_hlayout->setSpacing(defaultSpacing);
+
+ int defaultProportion = m_config->readNumEntry("proportion", 60);
+ m_hlayout->setProportion(defaultProportion);
+
+ delete m_notePixmapFactory;
+ m_notePixmapFactory = new NotePixmapFactory(m_fontName, m_fontSize);
+ m_hlayout->setNotePixmapFactory(m_notePixmapFactory);
+ m_vlayout->setNotePixmapFactory(m_notePixmapFactory);
+
+ setupActions();
+ // setupAddControlRulerMenu(); - too early for notation, moved to end of ctor.
+ initLayoutToolbar();
+ initStatusBar();
+
+ setBackgroundMode(PaletteBase);
+
+ QCanvas *tCanvas = new QCanvas(this);
+ tCanvas->resize(width() * 2, height() * 2);
+
+ setCanvasView(new NotationCanvasView(*this, tCanvas, getCentralWidget()));
+
+ updateViewCaption();
+
+ m_chordNameRuler = new ChordNameRuler
+ (m_hlayout, doc, segments, m_leftGutter, 20, getCentralWidget());
+ addRuler(m_chordNameRuler);
+ if (showProgressive)
+ m_chordNameRuler->show();
+
+ m_tempoRuler = new TempoRuler
+ (m_hlayout, doc, this, m_leftGutter, 24, false, getCentralWidget());
+ addRuler(m_tempoRuler);
+ m_tempoRuler->hide();
+ static_cast<TempoRuler *>(m_tempoRuler)->connectSignals();
+
+ m_rawNoteRuler = new RawNoteRuler
+ (m_hlayout, segments[0], m_leftGutter, 20, getCentralWidget());
+ addRuler(m_rawNoteRuler);
+ m_rawNoteRuler->show();
+
+ // All toolbars should be created before this is called
+ setAutoSaveSettings("NotationView", true);
+
+ // All rulers must have been created before this is called,
+ // or the program will crash
+ readOptions();
+
+
+ setBottomStandardRuler(new StandardRuler(getDocument(), m_hlayout, m_leftGutter, 25,
+ true, getBottomWidget()));
+
+ for (unsigned int i = 0; i < segments.size(); ++i)
+ {
+ m_staffs.push_back(new NotationStaff
+ (canvas(), segments[i], 0, // snap
+ i, this,
+ m_fontName, m_fontSize));
+ }
+
+
+ // HeadersGroup ctor must not be called before m_staffs initialization
+ m_headersGroupView = new QDeferScrollView(getCentralWidget());
+ QWidget * vport = m_headersGroupView->viewport();
+ m_headersGroup = new HeadersGroup(vport, this, &doc->getComposition());
+ m_headersGroupView->setVScrollBarMode(QScrollView::AlwaysOff);
+ m_headersGroupView->setHScrollBarMode(QScrollView::AlwaysOff);
+ m_headersGroupView->setFixedWidth(m_headersGroupView->contentsWidth());
+ m_canvasView->setLeftFixedWidget(m_headersGroupView);
+
+ // Add a close button just above the track headers.
+ // The grid layout is only here to maintain the button in a
+ // right place
+ m_headersTopFrame = new QFrame(getCentralWidget());
+ QGridLayout * headersTopGrid
+ = new QGridLayout(m_headersTopFrame, 2, 2);
+ QString pixmapDir = KGlobal::dirs()->findResource("appdata", "pixmaps/");
+ QCanvasPixmap pixmap(pixmapDir + "/misc/close.xpm");
+ QPushButton * hideHeadersButton
+ = new QPushButton(m_headersTopFrame);
+ headersTopGrid->addWidget(hideHeadersButton, 1, 1,
+ Qt::AlignRight | Qt::AlignBottom);
+ hideHeadersButton->setIconSet(QIconSet(pixmap));
+ hideHeadersButton->setFlat(true);
+ QToolTip::add(hideHeadersButton, i18n("Close track headers"));
+ headersTopGrid->setMargin(4);
+ setTopStandardRuler(new StandardRuler(getDocument(),
+ m_hlayout, m_leftGutter, 25,
+ false, getCentralWidget()), m_headersTopFrame);
+
+ m_topStandardRuler->getLoopRuler()->setBackgroundColor
+ (GUIPalette::getColour(GUIPalette::InsertCursorRuler));
+
+ connect(m_topStandardRuler->getLoopRuler(), SIGNAL(startMouseMove(int)),
+ m_canvasView, SLOT(startAutoScroll(int)));
+ connect(m_topStandardRuler->getLoopRuler(), SIGNAL(stopMouseMove()),
+ m_canvasView, SLOT(stopAutoScroll()));
+
+ connect(m_bottomStandardRuler->getLoopRuler(), SIGNAL(startMouseMove(int)),
+ m_canvasView, SLOT(startAutoScroll(int)));
+ connect(m_bottomStandardRuler->getLoopRuler(), SIGNAL(stopMouseMove()),
+ m_canvasView, SLOT(stopAutoScroll()));
+
+ // Following connection have to be done before calling setPageMode())
+ connect(m_headersGroup, SIGNAL(headersResized(int)),
+ this, SLOT(slotHeadersWidthChanged(int)));
+
+
+ //
+ // layout
+ //
+ ProgressDialog* progressDlg = 0;
+
+ if (showProgressive)
+ {
+ show();
+ ProgressDialog::processEvents();
+
+ NOTATION_DEBUG << "NotationView : setting up progress dialog" << endl;
+
+ progressDlg = new ProgressDialog(i18n("Starting..."),
+ 100, this);
+ progressDlg->setAutoClose(false);
+ progressDlg->setAutoReset(true);
+ progressDlg->setMinimumDuration(1000);
+ setupProgress(progressDlg);
+
+ m_progressDisplayer = PROGRESS_DIALOG;
+ }
+
+ m_chordNameRuler->setStudio(&getDocument()->getStudio());
+
+ m_currentStaff = 0;
+ m_staffs[0]->setCurrent(true);
+
+ m_config->setGroup(NotationViewConfigGroup);
+ int layoutMode = m_config->readNumEntry("layoutmode", 0);
+
+ try
+ {
+
+ LinedStaff::PageMode mode = LinedStaff::LinearMode;
+ if (layoutMode == 1)
+ mode = LinedStaff::ContinuousPageMode;
+ else if (layoutMode == 2)
+ mode = LinedStaff::MultiPageMode;
+
+ setPageMode(mode);
+
+ for (unsigned int i = 0; i < m_staffs.size(); ++i) {
+ m_staffs[i]->getSegment().getRefreshStatus
+ (m_segmentsRefreshStatusIds[i]).setNeedsRefresh(false);
+ }
+
+ m_ok = true;
+
+ } catch (ProgressReporter::Cancelled c)
+ {
+ // when cancelled, m_ok is false -- checked by calling method
+ NOTATION_DEBUG << "NotationView ctor : layout Cancelled" << endl;
+ }
+
+ NOTATION_DEBUG << "NotationView ctor : m_ok = " << m_ok << endl;
+
+ delete progressDlg;
+
+ // at this point we can return if operation was cancelled
+ if (!isOK())
+ {
+ setOutOfCtor();
+ return ;
+ }
+
+
+ // otherwise, carry on
+ setupDefaultProgress();
+
+ //
+ // Connect signals
+ //
+
+ QObject::connect
+ (getCanvasView(), SIGNAL(renderRequired(double, double)),
+ this, SLOT(slotCheckRendered(double, double)));
+
+ m_topStandardRuler->connectRulerToDocPointer(doc);
+ m_bottomStandardRuler->connectRulerToDocPointer(doc);
+
+ // Disconnect the default connection for this signal from the
+ // top ruler, and connect our own instead
+
+ QObject::disconnect
+ (m_topStandardRuler->getLoopRuler(),
+ SIGNAL(setPointerPosition(timeT)), 0, 0);
+
+ QObject::connect
+ (m_topStandardRuler->getLoopRuler(),
+ SIGNAL(setPointerPosition(timeT)),
+ this, SLOT(slotSetInsertCursorPosition(timeT)));
+
+ QObject::connect
+ (m_topStandardRuler,
+ SIGNAL(dragPointerToPosition(timeT)),
+ this, SLOT(slotSetInsertCursorPosition(timeT)));
+
+ connect(m_bottomStandardRuler, SIGNAL(dragPointerToPosition(timeT)),
+ this, SLOT(slotSetPointerPosition(timeT)));
+
+ QObject::connect
+ (getCanvasView(), SIGNAL(itemPressed(int, int, QMouseEvent*, NotationElement*)),
+ this, SLOT (slotItemPressed(int, int, QMouseEvent*, NotationElement*)));
+
+ QObject::connect
+ (getCanvasView(), SIGNAL(activeItemPressed(QMouseEvent*, QCanvasItem*)),
+ this, SLOT (slotActiveItemPressed(QMouseEvent*, QCanvasItem*)));
+
+ QObject::connect
+ (getCanvasView(), SIGNAL(nonNotationItemPressed(QMouseEvent*, QCanvasItem*)),
+ this, SLOT (slotNonNotationItemPressed(QMouseEvent*, QCanvasItem*)));
+
+ QObject::connect
+ (getCanvasView(), SIGNAL(textItemPressed(QMouseEvent*, QCanvasItem*)),
+ this, SLOT (slotTextItemPressed(QMouseEvent*, QCanvasItem*)));
+
+ QObject::connect
+ (getCanvasView(), SIGNAL(mouseMoved(QMouseEvent*)),
+ this, SLOT (slotMouseMoved(QMouseEvent*)));
+
+ QObject::connect
+ (getCanvasView(), SIGNAL(mouseReleased(QMouseEvent*)),
+ this, SLOT (slotMouseReleased(QMouseEvent*)));
+
+ QObject::connect
+ (getCanvasView(), SIGNAL(hoveredOverNoteChanged(const QString&)),
+ this, SLOT (slotHoveredOverNoteChanged(const QString&)));
+
+ QObject::connect
+ (getCanvasView(), SIGNAL(hoveredOverAbsoluteTimeChanged(unsigned int)),
+ this, SLOT (slotHoveredOverAbsoluteTimeChanged(unsigned int)));
+
+ QObject::connect
+ (getCanvasView(), SIGNAL(zoomIn()), this, SLOT(slotZoomIn()));
+
+ QObject::connect
+ (getCanvasView(), SIGNAL(zoomOut()), this, SLOT(slotZoomOut()));
+
+ QObject::connect
+ (m_pannerDialog->scrollbox(), SIGNAL(valueChanged(const QPoint &)),
+ getCanvasView(), SLOT(slotSetScrollPos(const QPoint &)));
+
+ QObject::connect
+ (getCanvasView()->horizontalScrollBar(), SIGNAL(valueChanged(int)),
+ m_pannerDialog->scrollbox(), SLOT(setViewX(int)));
+
+ QObject::connect
+ (getCanvasView()->verticalScrollBar(), SIGNAL(valueChanged(int)),
+ m_pannerDialog->scrollbox(), SLOT(setViewY(int)));
+
+ QObject::connect
+ (doc, SIGNAL(pointerPositionChanged(timeT)),
+ this, SLOT(slotSetPointerPosition(timeT)));
+
+ //
+ // Connect vertical scrollbars between canvas and notation header
+ QObject::connect
+ (getCanvasView()->verticalScrollBar(), SIGNAL(valueChanged(int)),
+ this, SLOT(slotVerticalScrollHeadersGroup(int)));
+
+ QObject::connect
+ (getCanvasView()->verticalScrollBar(), SIGNAL(sliderMoved(int)),
+ this, SLOT(slotVerticalScrollHeadersGroup(int)));
+
+ QObject::connect
+ (m_headersGroupView, SIGNAL(gotWheelEvent(QWheelEvent*)),
+ getCanvasView(), SLOT(slotExternalWheelEvent(QWheelEvent*)));
+
+ // Ensure notation header keeps the right bottom margin when user
+ // toggles the canvas view bottom rulers
+ connect(getCanvasView(), SIGNAL(bottomWidgetHeightChanged(int)),
+ this, SLOT(slotCanvasBottomWidgetHeightChanged(int)));
+
+ // Signal canvas horizontal scroll to notation header
+ QObject::connect
+ (getCanvasView(), SIGNAL(contentsMoving(int, int)),
+ this, SLOT(slotUpdateHeaders(int, int)));
+
+ // Connect the close notation headers button
+ QObject::connect(hideHeadersButton, SIGNAL(clicked()),
+ this, SLOT(slotHideHeadersGroup()));
+
+ stateChanged("have_selection", KXMLGUIClient::StateReverse);
+ stateChanged("have_notes_in_selection", KXMLGUIClient::StateReverse);
+ stateChanged("have_rests_in_selection", KXMLGUIClient::StateReverse);
+ stateChanged("have_multiple_staffs",
+ (m_staffs.size() > 1 ? KXMLGUIClient::StateNoReverse :
+ KXMLGUIClient::StateReverse));
+ stateChanged("rest_insert_tool_current", KXMLGUIClient::StateReverse);
+ slotTestClipboard();
+
+ if (getSegmentsOnlyRestsAndClefs())
+ {
+ m_selectDefaultNote->activate();
+ stateChanged("note_insert_tool_current",
+ KXMLGUIClient::StateNoReverse);
+ } else
+ {
+ actionCollection()->action("select")->activate();
+ stateChanged("note_insert_tool_current",
+ KXMLGUIClient::StateReverse);
+ }
+
+ timeT start = doc->getComposition().getLoopStart();
+ timeT end = doc->getComposition().getLoopEnd();
+ m_topStandardRuler->getLoopRuler()->slotSetLoopMarker(start, end);
+ m_bottomStandardRuler->getLoopRuler()->slotSetLoopMarker(start, end);
+
+ slotSetInsertCursorPosition(0);
+ slotSetPointerPosition(doc->getComposition().getPosition());
+ setCurrentSelection(0, false, true);
+ slotUpdateInsertModeStatus();
+ m_chordNameRuler->repaint();
+ m_tempoRuler->repaint();
+ m_rawNoteRuler->repaint();
+ m_inhibitRefresh = false;
+
+ // slotCheckRendered(0, getCanvasView()->visibleWidth());
+ // getCanvasView()->repaintContents();
+ updateView();
+
+ QObject::connect
+ (this, SIGNAL(renderComplete()),
+ getCanvasView(), SLOT(slotRenderComplete()));
+
+ if (parent)
+ {
+ const TrackButtons * trackLabels =
+ ((RosegardenGUIView*)parent)->getTrackEditor()->getTrackButtons();
+ QObject::connect
+ (trackLabels, SIGNAL(nameChanged()),
+ this, SLOT(slotUpdateStaffName()));
+ }
+
+ setConfigDialogPageIndex(3);
+ setOutOfCtor();
+
+ // Property and Control Rulers
+ //
+ if (getCurrentSegment()->getViewFeatures())
+ slotShowVelocityControlRuler();
+ setupControllerTabs();
+
+ setupAddControlRulerMenu();
+ setRewFFwdToAutoRepeat();
+
+ slotCompositionStateUpdate();
+
+ NOTATION_DEBUG << "NotationView ctor exiting" << endl;
+}
+
+NotationView::NotationView(RosegardenGUIDoc *doc,
+ std::vector<Segment *> segments,
+ QWidget *parent,
+ NotationView *referenceView)
+ : EditView(doc, segments, 1, 0, "printview"),
+ m_properties(getViewLocalPropertyPrefix()),
+ m_selectionCounter(0),
+ m_currentNotePixmap(0),
+ m_hoveredOverNoteName(0),
+ m_hoveredOverAbsoluteTime(0),
+ m_lastFinishingStaff( -1),
+ m_title(0),
+ m_subtitle(0),
+ m_composer(0),
+ m_copyright(0),
+ m_insertionTime(0),
+ m_deferredCursorMove(NoCursorMoveNeeded),
+ m_lastNoteAction("crotchet"),
+ m_fontName(NoteFontFactory::getDefaultFontName()),
+ m_fontSize(NoteFontFactory::getDefaultSize(m_fontName)),
+ m_pageMode(LinedStaff::LinearMode),
+ m_leftGutter(0),
+ m_notePixmapFactory(new NotePixmapFactory(m_fontName, m_fontSize)),
+ m_hlayout(new NotationHLayout(&doc->getComposition(), m_notePixmapFactory,
+ m_properties, this)),
+ m_vlayout(new NotationVLayout(&doc->getComposition(), m_notePixmapFactory,
+ m_properties, this)),
+ m_chordNameRuler(0),
+ m_tempoRuler(0),
+ m_rawNoteRuler(0),
+ m_annotationsVisible(false),
+ m_lilyPondDirectivesVisible(false),
+ m_selectDefaultNote(0),
+ m_fontCombo(0),
+ m_fontSizeCombo(0),
+ m_spacingCombo(0),
+ m_fontSizeActionMenu(0),
+ m_pannerDialog(0),
+ m_renderTimer(0),
+ m_playTracking(false),
+ m_progressDisplayer(PROGRESS_NONE),
+ m_inhibitRefresh(true),
+ m_ok(false),
+ m_printMode(true),
+ m_printSize(8), // set in positionStaffs
+ m_showHeadersGroup(0),
+ m_headersGroupView(0),
+ m_headersGroup(0),
+ m_headersTopFrame(0),
+ m_showHeadersMenuEntry(0)
+{
+ assert(segments.size() > 0);
+ NOTATION_DEBUG << "NotationView print ctor" << endl;
+
+
+ // Initialise the display-related defaults that will be needed
+ // by both the actions and the layout toolbar
+
+ m_config->setGroup(NotationViewConfigGroup);
+
+ if (referenceView)
+ {
+ m_fontName = referenceView->m_fontName;
+ } else
+ {
+ m_fontName = qstrtostr(m_config->readEntry
+ ("notefont",
+ strtoqstr(NoteFontFactory::getDefaultFontName())));
+ }
+
+
+ // Force largest font size
+ std::vector<int> sizes = NoteFontFactory::getAllSizes(m_fontName);
+ m_fontSize = sizes[sizes.size() - 1];
+
+ if (referenceView)
+ {
+ m_hlayout->setSpacing(referenceView->m_hlayout->getSpacing());
+ m_hlayout->setProportion(referenceView->m_hlayout->getProportion());
+ } else
+ {
+ int defaultSpacing = m_config->readNumEntry("spacing", 100);
+ m_hlayout->setSpacing(defaultSpacing);
+ int defaultProportion = m_config->readNumEntry("proportion", 60);
+ m_hlayout->setProportion(defaultProportion);
+ }
+
+ delete m_notePixmapFactory;
+ m_notePixmapFactory = new NotePixmapFactory(m_fontName, m_fontSize);
+ m_hlayout->setNotePixmapFactory(m_notePixmapFactory);
+ m_vlayout->setNotePixmapFactory(m_notePixmapFactory);
+
+ setBackgroundMode(PaletteBase);
+ m_config->setGroup(NotationViewConfigGroup);
+
+ QCanvas *tCanvas = new QCanvas(this);
+ tCanvas->resize(width() * 2, height() * 2); //!!!
+
+ setCanvasView(new NotationCanvasView(*this, tCanvas, getCentralWidget()));
+ canvas()->retune(128); // tune for larger canvas
+
+ for (unsigned int i = 0; i < segments.size(); ++i)
+ {
+ m_staffs.push_back(new NotationStaff(canvas(), segments[i], 0, // snap
+ i, this,
+ m_fontName, m_fontSize));
+ }
+
+ m_currentStaff = 0;
+ m_staffs[0]->setCurrent(true);
+
+ ProgressDialog* progressDlg = 0;
+
+ if (parent)
+ {
+
+ ProgressDialog::processEvents();
+
+ NOTATION_DEBUG << "NotationView : setting up progress dialog" << endl;
+
+ progressDlg = new ProgressDialog(i18n("Preparing to print..."),
+ 100, parent);
+ progressDlg->setAutoClose(false);
+ progressDlg->setAutoReset(true);
+ progressDlg->setMinimumDuration(1000);
+ setupProgress(progressDlg);
+
+ m_progressDisplayer = PROGRESS_DIALOG;
+ }
+
+ try
+ {
+
+ setPageMode(LinedStaff::MultiPageMode); // also positions and renders the staffs!
+
+ for (unsigned int i = 0; i < m_staffs.size(); ++i) {
+ m_staffs[i]->getSegment().getRefreshStatus
+ (m_segmentsRefreshStatusIds[i]).setNeedsRefresh(false);
+ }
+
+ m_ok = true;
+
+ } catch (ProgressReporter::Cancelled c)
+ {
+ // when cancelled, m_ok is false -- checked by calling method
+ NOTATION_DEBUG << "NotationView ctor : layout Cancelled" << endl;
+ }
+
+ NOTATION_DEBUG << "NotationView ctor : m_ok = " << m_ok << endl;
+
+ delete progressDlg;
+
+ if (!isOK())
+ {
+ setOutOfCtor();
+ return ; // In case more code is added there later
+ }
+
+ setOutOfCtor(); // keep this as last call in the ctor
+}
+
+NotationView::~NotationView()
+{
+ NOTATION_DEBUG << "-> ~NotationView()" << endl;
+
+ if (!m_printMode && m_ok)
+ slotSaveOptions();
+
+ delete m_chordNameRuler;
+
+ delete m_renderTimer;
+
+ for (unsigned int i = 0; i < m_staffs.size(); ++i) {
+ for (Segment::iterator j = m_staffs[i]->getSegment().begin();
+ j != m_staffs[i]->getSegment().end(); ++j) {
+ removeViewLocalProperties(*j);
+ }
+ delete m_staffs[i]; // this will erase all "notes" canvas items
+ }
+
+ PixmapArrayGC::deleteAll();
+ Profiles::getInstance()->dump();
+
+ NOTATION_DEBUG << "<- ~NotationView()" << endl;
+}
+
+void
+NotationView::removeViewLocalProperties(Event *e)
+{
+ Event::PropertyNames names(e->getPropertyNames());
+ std::string prefix(getViewLocalPropertyPrefix());
+
+ for (Event::PropertyNames::iterator i = names.begin();
+ i != names.end(); ++i) {
+ if (i->getName().substr(0, prefix.size()) == prefix) {
+ e->unset(*i);
+ }
+ }
+}
+
+const NotationProperties &
+NotationView::getProperties() const
+{
+ return m_properties;
+}
+
+void NotationView::positionStaffs()
+{
+ NOTATION_DEBUG << "NotationView::positionStaffs" << endl;
+
+ m_config->setGroup(NotationViewConfigGroup);
+ m_printSize = m_config->readUnsignedNumEntry("printingnotesize", 5);
+
+ int minTrack = 0, maxTrack = 0;
+ bool haveMinTrack = false;
+ typedef std::map<int, int> TrackIntMap;
+ TrackIntMap trackHeights;
+ TrackIntMap trackCoords;
+
+ int pageWidth, pageHeight, leftMargin, topMargin;
+ pageWidth = getPageWidth();
+ pageHeight = getPageHeight();
+ leftMargin = 0, topMargin = 0;
+ getPageMargins(leftMargin, topMargin);
+
+ int accumulatedHeight;
+ int rowsPerPage = 1;
+ int legerLines = 8;
+ if (m_pageMode != LinedStaff::LinearMode)
+ legerLines = 7;
+ int rowGapPercent = (m_staffs.size() > 1 ? 40 : 10);
+ int aimFor = -1;
+
+ bool done = false;
+
+ int titleHeight = 0;
+
+ if (m_title)
+ delete m_title;
+ if (m_subtitle)
+ delete m_subtitle;
+ if (m_composer)
+ delete m_composer;
+ if (m_copyright)
+ delete m_copyright;
+ m_title = m_subtitle = m_composer = m_copyright = 0;
+
+ if (m_pageMode == LinedStaff::MultiPageMode) {
+
+ const Configuration &metadata =
+ getDocument()->getComposition().getMetadata();
+
+ QFont defaultFont(NotePixmapFactory::defaultSerifFontFamily);
+ m_config->setGroup(NotationViewConfigGroup);
+ QFont font = m_config->readFontEntry("textfont", &defaultFont);
+ font.setPixelSize(m_fontSize * 5);
+ QFontMetrics metrics(font);
+
+ if (metadata.has(CompositionMetadataKeys::Title)) {
+ QString title(strtoqstr(metadata.get<String>
+ (CompositionMetadataKeys::Title)));
+ m_title = new QCanvasText(title, font, canvas());
+ m_title->setX(m_leftGutter + pageWidth / 2 - metrics.width(title) / 2);
+ m_title->setY(20 + topMargin / 4 + metrics.ascent());
+ m_title->show();
+ titleHeight += metrics.height() * 3 / 2 + topMargin / 4;
+ }
+
+ font.setPixelSize(m_fontSize * 3);
+ metrics = QFontMetrics(font);
+
+ if (metadata.has(CompositionMetadataKeys::Subtitle)) {
+ QString subtitle(strtoqstr(metadata.get<String>
+ (CompositionMetadataKeys::Subtitle)));
+ m_subtitle = new QCanvasText(subtitle, font, canvas());
+ m_subtitle->setX(m_leftGutter + pageWidth / 2 - metrics.width(subtitle) / 2);
+ m_subtitle->setY(20 + titleHeight + metrics.ascent());
+ m_subtitle->show();
+ titleHeight += metrics.height() * 3 / 2;
+ }
+
+ if (metadata.has(CompositionMetadataKeys::Composer)) {
+ QString composer(strtoqstr(metadata.get<String>
+ (CompositionMetadataKeys::Composer)));
+ m_composer = new QCanvasText(composer, font, canvas());
+ m_composer->setX(m_leftGutter + pageWidth - metrics.width(composer) - leftMargin);
+ m_composer->setY(20 + titleHeight + metrics.ascent());
+ m_composer->show();
+ titleHeight += metrics.height() * 3 / 2;
+ }
+
+ font.setPixelSize(m_fontSize * 2);
+ metrics = QFontMetrics(font);
+
+ if (metadata.has(CompositionMetadataKeys::Copyright)) {
+ QString copyright(strtoqstr(metadata.get<String>
+ (CompositionMetadataKeys::Copyright)));
+ m_copyright = new QCanvasText(copyright, font, canvas());
+ m_copyright->setX(m_leftGutter + leftMargin);
+ m_copyright->setY(20 + pageHeight - topMargin - metrics.descent());
+ m_copyright->show();
+ }
+ }
+
+ while (1) {
+
+ accumulatedHeight = 0;
+ int maxTrackHeight = 0;
+
+ trackHeights.clear();
+
+ for (unsigned int i = 0; i < m_staffs.size(); ++i) {
+
+ m_staffs[i]->setLegerLineCount(legerLines);
+
+ int height = m_staffs[i]->getHeightOfRow();
+ TrackId trackId = m_staffs[i]->getSegment().getTrack();
+ Track *track =
+ m_staffs[i]->getSegment().getComposition()->
+ getTrackById(trackId);
+
+ if (!track)
+ continue; // This Should Not Happen, My Friend
+
+ int trackPosition = track->getPosition();
+
+ TrackIntMap::iterator hi = trackHeights.find(trackPosition);
+ if (hi == trackHeights.end()) {
+ trackHeights.insert(TrackIntMap::value_type
+ (trackPosition, height));
+ } else if (height > hi->second) {
+ hi->second = height;
+ }
+
+ if (height > maxTrackHeight)
+ maxTrackHeight = height;
+
+ if (trackPosition < minTrack || !haveMinTrack) {
+ minTrack = trackPosition;
+ haveMinTrack = true;
+ }
+ if (trackPosition > maxTrack) {
+ maxTrack = trackPosition;
+ }
+ }
+
+ for (int i = minTrack; i <= maxTrack; ++i) {
+ TrackIntMap::iterator hi = trackHeights.find(i);
+ if (hi != trackHeights.end()) {
+ trackCoords[i] = accumulatedHeight;
+ accumulatedHeight += hi->second;
+ }
+ }
+
+ accumulatedHeight += maxTrackHeight * rowGapPercent / 100;
+
+ if (done)
+ break;
+
+ if (m_pageMode != LinedStaff::MultiPageMode) {
+
+ rowsPerPage = 0;
+ done = true;
+ break;
+
+ } else {
+
+ // Check how well all this stuff actually fits on the
+ // page. If things don't fit as well as we'd like, modify
+ // at most one parameter so as to save some space, then
+ // loop around again and see if it worked. This iterative
+ // approach is inefficient but the time spent here is
+ // neglible in context, and it's a simple way to code it.
+
+ int staffPageHeight = pageHeight - topMargin * 2 - titleHeight;
+ rowsPerPage = staffPageHeight / accumulatedHeight;
+
+ if (rowsPerPage < 1) {
+
+ if (legerLines > 5)
+ --legerLines;
+ else if (rowGapPercent > 20)
+ rowGapPercent -= 10;
+ else if (legerLines > 4)
+ --legerLines;
+ else if (rowGapPercent > 0)
+ rowGapPercent -= 10;
+ else if (legerLines > 3)
+ --legerLines;
+ else if (m_printSize > 3)
+ --m_printSize;
+ else { // just accept that we'll have to overflow
+ rowsPerPage = 1;
+ done = true;
+ }
+
+ } else {
+
+ if (aimFor == rowsPerPage) {
+
+ titleHeight +=
+ (staffPageHeight - (rowsPerPage * accumulatedHeight)) / 2;
+
+ done = true;
+
+ } else {
+
+ if (aimFor == -1)
+ aimFor = rowsPerPage + 1;
+
+ // we can perhaps accommodate another row, with care
+ if (legerLines > 5)
+ --legerLines;
+ else if (rowGapPercent > 20)
+ rowGapPercent -= 10;
+ else if (legerLines > 3)
+ --legerLines;
+ else if (rowGapPercent > 0)
+ rowGapPercent -= 10;
+ else { // no, we can't
+ rowGapPercent = 0;
+ legerLines = 8;
+ done = true;
+ }
+ }
+ }
+ }
+ }
+
+ m_hlayout->setPageWidth(pageWidth - leftMargin * 2);
+
+ int topGutter = 0;
+
+ if (m_pageMode == LinedStaff::MultiPageMode) {
+
+ topGutter = 20;
+
+ } else if (m_pageMode == LinedStaff::ContinuousPageMode) {
+
+ // fewer leger lines above staff than in linear mode --
+ // compensate for this on the top staff
+ topGutter = m_notePixmapFactory->getLineSpacing() * 2;
+ }
+
+ for (unsigned int i = 0; i < m_staffs.size(); ++i) {
+
+ TrackId trackId = m_staffs[i]->getSegment().getTrack();
+ Track *track =
+ m_staffs[i]->getSegment().getComposition()->
+ getTrackById(trackId);
+
+ if (!track)
+ continue; // Once Again, My Friend, You Should Never See Me Here
+
+ int trackPosition = track->getPosition();
+
+ m_staffs[i]->setTitleHeight(titleHeight);
+ m_staffs[i]->setRowSpacing(accumulatedHeight);
+
+ if (trackPosition < maxTrack) {
+ m_staffs[i]->setConnectingLineLength(trackHeights[trackPosition]);
+ }
+
+ if (trackPosition == minTrack &&
+ m_pageMode != LinedStaff::LinearMode) {
+ m_staffs[i]->setBarNumbersEvery(5);
+ } else {
+ m_staffs[i]->setBarNumbersEvery(0);
+ }
+
+ m_staffs[i]->setX(m_leftGutter);
+ m_staffs[i]->setY(topGutter + trackCoords[trackPosition] + topMargin);
+ m_staffs[i]->setPageWidth(pageWidth - leftMargin * 2);
+ m_staffs[i]->setRowsPerPage(rowsPerPage);
+ m_staffs[i]->setPageMode(m_pageMode);
+ m_staffs[i]->setMargin(leftMargin);
+
+ NOTATION_DEBUG << "NotationView::positionStaffs: set staff's page width to "
+ << (pageWidth - leftMargin * 2) << endl;
+
+ }
+
+
+ if (!m_printMode) {
+ // Destroy then recreate all track headers
+ hideHeadersGroup();
+ m_headersGroup->removeAllHeaders();
+ if (m_pageMode == LinedStaff::LinearMode) {
+ for (int i = minTrack; i <= maxTrack; ++i) {
+ TrackIntMap::iterator hi = trackHeights.find(i);
+ if (hi != trackHeights.end()) {
+ TrackId trackId = getDocument()->getComposition()
+ .getTrackByPosition(i)->getId();
+ m_headersGroup->addHeader(trackId, trackHeights[i],
+ trackCoords[i], getCanvasLeftX());
+ }
+ }
+
+ m_headersGroup->completeToHeight(canvas()->height());
+
+ m_headersGroupView->addChild(m_headersGroup);
+
+ getCanvasView()->updateLeftWidgetGeometry();
+
+ if ( (m_showHeadersGroup == HeadersGroup::ShowAlways)
+ || ( (m_showHeadersGroup == HeadersGroup::ShowWhenNeeded)
+ && (m_headersGroup->getUsedHeight()
+ > getCanvasView()->visibleHeight()))) {
+ m_headersGroup->slotUpdateAllHeaders(getCanvasLeftX(), 0, true);
+ showHeadersGroup();
+
+ // Disable menu entry when headers are shown
+ m_showHeadersMenuEntry->setEnabled(false);
+ } else {
+ // Enable menu entry when headers are hidden
+ m_showHeadersMenuEntry->setEnabled(true);
+ }
+ } else {
+ // Disable menu entry when not in linear mode
+ m_showHeadersMenuEntry->setEnabled(false);
+ }
+ }
+}
+
+void NotationView::slotCanvasBottomWidgetHeightChanged(int newHeight)
+{
+ getCanvasView()->updateLeftWidgetGeometry();
+}
+
+void NotationView::positionPages()
+{
+ if (m_printMode)
+ return ;
+
+ QPixmap background;
+ QPixmap deskBackground;
+ bool haveBackground = false;
+
+ m_config->setGroup(NotationViewConfigGroup);
+ if (m_config->readBoolEntry("backgroundtextures", true)) {
+ QString pixmapDir =
+ KGlobal::dirs()->findResource("appdata", "pixmaps/");
+ if (background.load(QString("%1/misc/bg-paper-cream.xpm").
+ arg(pixmapDir))) {
+ haveBackground = true;
+ }
+ // we're happy to ignore errors from this one:
+ deskBackground.load(QString("%1/misc/bg-desktop.xpm").arg(pixmapDir));
+ }
+
+ int pageWidth = getPageWidth();
+ int pageHeight = getPageHeight();
+ int leftMargin = 0, topMargin = 0;
+ getPageMargins(leftMargin, topMargin);
+ int maxPageCount = 1;
+
+ for (unsigned int i = 0; i < m_staffs.size(); ++i) {
+ int pageCount = m_staffs[i]->getPageCount();
+ if (pageCount > maxPageCount)
+ maxPageCount = pageCount;
+ }
+
+ for (unsigned int i = 0; i < m_pages.size(); ++i) {
+ delete m_pages[i];
+ delete m_pageNumbers[i];
+ }
+ m_pages.clear();
+ m_pageNumbers.clear();
+
+ if (m_pageMode != LinedStaff::MultiPageMode) {
+ if (haveBackground) {
+ canvas()->setBackgroundPixmap(background);
+ getCanvasView()->setBackgroundMode(Qt::FixedPixmap);
+ getCanvasView()->setPaletteBackgroundPixmap(background);
+ getCanvasView()->setErasePixmap(background);
+ }
+ } else {
+ if (haveBackground) {
+ canvas()->setBackgroundPixmap(deskBackground);
+ getCanvasView()->setBackgroundMode(Qt::FixedPixmap);
+ getCanvasView()->setPaletteBackgroundPixmap(background);
+ getCanvasView()->setErasePixmap(background);
+ }
+
+ QFont pageNumberFont;
+ pageNumberFont.setPixelSize(m_fontSize * 2);
+ QFontMetrics pageNumberMetrics(pageNumberFont);
+
+ for (int page = 0; page < maxPageCount; ++page) {
+
+ int x = m_leftGutter + pageWidth * page + leftMargin / 4;
+ int y = 20;
+ int w = pageWidth - leftMargin / 2;
+ int h = pageHeight;
+
+ QString str = QString("%1").arg(page + 1);
+ QCanvasText *text = new QCanvasText(str, pageNumberFont, canvas());
+ text->setX(m_leftGutter + pageWidth * page + pageWidth - pageNumberMetrics.width(str) - leftMargin);
+ text->setY(y + h - pageNumberMetrics.descent() - topMargin);
+ text->setZ( -999);
+ text->show();
+ m_pageNumbers.push_back(text);
+
+ QCanvasRectangle *rect = new QCanvasRectangle(x, y, w, h, canvas());
+ if (haveBackground)
+ rect->setBrush(QBrush(Qt::white, background));
+ rect->setPen(Qt::black);
+ rect->setZ( -1000);
+ rect->show();
+ m_pages.push_back(rect);
+ }
+
+ updateThumbnails(false);
+ }
+
+ m_config->setGroup(NotationViewConfigGroup);
+}
+
+void NotationView::slotUpdateStaffName()
+{
+ LinedStaff *staff = getLinedStaff(m_currentStaff);
+ staff->drawStaffName();
+ m_headersGroup->slotUpdateAllHeaders(getCanvasLeftX(), 0, true);
+}
+
+void NotationView::slotSaveOptions()
+{
+ m_config->setGroup(NotationViewConfigGroup);
+
+ m_config->writeEntry("Show Chord Name Ruler", getToggleAction("show_chords_ruler")->isChecked());
+ m_config->writeEntry("Show Raw Note Ruler", getToggleAction("show_raw_note_ruler")->isChecked());
+ m_config->writeEntry("Show Tempo Ruler", getToggleAction("show_tempo_ruler")->isChecked());
+ m_config->writeEntry("Show Annotations", m_annotationsVisible);
+ m_config->writeEntry("Show LilyPond Directives", m_lilyPondDirectivesVisible);
+
+ m_config->sync();
+}
+
+void NotationView::setOneToolbar(const char *actionName,
+ const char *toolbarName)
+{
+ KToggleAction *action = getToggleAction(actionName);
+ if (!action) {
+ std::cerr << "WARNING: No such action as " << actionName << std::endl;
+ return ;
+ }
+ QWidget *toolbar = toolBar(toolbarName);
+ if (!toolbar) {
+ std::cerr << "WARNING: No such toolbar as " << toolbarName << std::endl;
+ return ;
+ }
+ action->setChecked(!toolbar->isHidden());
+}
+
+void NotationView::readOptions()
+{
+ EditView::readOptions();
+
+ setOneToolbar("show_tools_toolbar", "Tools Toolbar");
+ setOneToolbar("show_notes_toolbar", "Notes Toolbar");
+ setOneToolbar("show_rests_toolbar", "Rests Toolbar");
+ setOneToolbar("show_clefs_toolbar", "Clefs Toolbar");
+ setOneToolbar("show_group_toolbar", "Group Toolbar");
+ setOneToolbar("show_marks_toolbar", "Marks Toolbar");
+ setOneToolbar("show_layout_toolbar", "Layout Toolbar");
+ setOneToolbar("show_transport_toolbar", "Transport Toolbar");
+ setOneToolbar("show_accidentals_toolbar", "Accidentals Toolbar");
+ setOneToolbar("show_meta_toolbar", "Meta Toolbar");
+
+ m_config->setGroup(NotationViewConfigGroup);
+
+ bool opt;
+
+ opt = m_config->readBoolEntry("Show Chord Name Ruler", false);
+ getToggleAction("show_chords_ruler")->setChecked(opt);
+ slotToggleChordsRuler();
+
+ opt = m_config->readBoolEntry("Show Raw Note Ruler", true);
+ getToggleAction("show_raw_note_ruler")->setChecked(opt);
+ slotToggleRawNoteRuler();
+
+ opt = m_config->readBoolEntry("Show Tempo Ruler", true);
+ getToggleAction("show_tempo_ruler")->setChecked(opt);
+ slotToggleTempoRuler();
+
+ opt = m_config->readBoolEntry("Show Annotations", true);
+ m_annotationsVisible = opt;
+ getToggleAction("show_annotations")->setChecked(opt);
+ slotUpdateAnnotationsStatus();
+ // slotToggleAnnotations();
+
+ opt = m_config->readBoolEntry("Show LilyPond Directives", true);
+ m_lilyPondDirectivesVisible = opt;
+ getToggleAction("show_lilypond_directives")->setChecked(opt);
+ slotUpdateLilyPondDirectivesStatus();
+}
+
+void NotationView::setupActions()
+{
+ KStdAction::print(this, SLOT(slotFilePrint()), actionCollection());
+ KStdAction::printPreview(this, SLOT(slotFilePrintPreview()),
+ actionCollection());
+
+ new KAction(i18n("Print &with LilyPond..."), 0, 0, this,
+ SLOT(slotPrintLilyPond()), actionCollection(),
+ "file_print_lilypond");
+
+ new KAction(i18n("Preview with Lil&yPond..."), 0, 0, this,
+ SLOT(slotPreviewLilyPond()), actionCollection(),
+ "file_preview_lilypond");
+
+ EditViewBase::setupActions("notation.rc");
+ EditView::setupActions();
+
+ KRadioAction* noteAction = 0;
+
+ // View menu stuff
+
+ KActionMenu *fontActionMenu =
+ new KActionMenu(i18n("Note &Font"), this, "note_font_actionmenu");
+
+ std::set
+ <std::string> fs(NoteFontFactory::getFontNames());
+ std::vector<std::string> f(fs.begin(), fs.end());
+ std::sort(f.begin(), f.end());
+
+ for (std::vector<std::string>::iterator i = f.begin(); i != f.end(); ++i) {
+
+ QString fontQName(strtoqstr(*i));
+
+ KToggleAction *fontAction =
+ new KToggleAction
+ (fontQName, 0, this, SLOT(slotChangeFontFromAction()),
+ actionCollection(), "note_font_" + fontQName);
+
+ fontAction->setChecked(*i == m_fontName);
+ fontActionMenu->insert(fontAction);
+ }
+
+ actionCollection()->insert(fontActionMenu);
+
+ m_fontSizeActionMenu =
+ new KActionMenu(i18n("Si&ze"), this, "note_font_size_actionmenu");
+ setupFontSizeMenu();
+
+ actionCollection()->insert(m_fontSizeActionMenu);
+
+ m_showHeadersMenuEntry
+ = new KAction(i18n("Show Track Headers"), 0, this,
+ SLOT(slotShowHeadersGroup()),
+ actionCollection(), "show_track_headers");
+
+ KActionMenu *spacingActionMenu =
+ new KActionMenu(i18n("S&pacing"), this, "stretch_actionmenu");
+
+ int defaultSpacing = m_hlayout->getSpacing();
+ std::vector<int> spacings = NotationHLayout::getAvailableSpacings();
+
+ for (std::vector<int>::iterator i = spacings.begin();
+ i != spacings.end(); ++i) {
+
+ KToggleAction *spacingAction =
+ new KToggleAction
+ (QString("%1%").arg(*i), 0, this,
+ SLOT(slotChangeSpacingFromAction()),
+ actionCollection(), QString("spacing_%1").arg(*i));
+
+ spacingAction->setExclusiveGroup("spacing");
+ spacingAction->setChecked(*i == defaultSpacing);
+ spacingActionMenu->insert(spacingAction);
+ }
+
+ actionCollection()->insert(spacingActionMenu);
+
+ KActionMenu *proportionActionMenu =
+ new KActionMenu(i18n("Du&ration Factor"), this, "proportion_actionmenu");
+
+ int defaultProportion = m_hlayout->getProportion();
+ std::vector<int> proportions = NotationHLayout::getAvailableProportions();
+
+ for (std::vector<int>::iterator i = proportions.begin();
+ i != proportions.end(); ++i) {
+
+ QString name = QString("%1%").arg(*i);
+ if (*i == 0)
+ name = i18n("None");
+
+ KToggleAction *proportionAction =
+ new KToggleAction
+ (name, 0, this,
+ SLOT(slotChangeProportionFromAction()),
+ actionCollection(), QString("proportion_%1").arg(*i));
+
+ proportionAction->setExclusiveGroup("proportion");
+ proportionAction->setChecked(*i == defaultProportion);
+ proportionActionMenu->insert(proportionAction);
+ }
+
+ actionCollection()->insert(proportionActionMenu);
+
+ KActionMenu *styleActionMenu =
+ new KActionMenu(i18n("Note &Style"), this, "note_style_actionmenu");
+
+ std::vector<NoteStyleName> styles
+ (NoteStyleFactory::getAvailableStyleNames());
+
+ for (std::vector<NoteStyleName>::iterator i = styles.begin();
+ i != styles.end(); ++i) {
+
+ QString styleQName(strtoqstr(*i));
+
+ KAction *styleAction =
+ new KAction
+ (styleQName, 0, this, SLOT(slotSetStyleFromAction()),
+ actionCollection(), "style_" + styleQName);
+
+ styleActionMenu->insert(styleAction);
+ }
+
+ actionCollection()->insert(styleActionMenu);
+
+ KActionMenu *ornamentActionMenu =
+ new KActionMenu(i18n("Use Ornament"), this, "ornament_actionmenu");
+
+
+
+ new KAction
+ (i18n("Insert Rest"), Key_P, this, SLOT(slotInsertRest()),
+ actionCollection(), QString("insert_rest"));
+
+ new KAction
+ (i18n("Switch from Note to Rest"), Key_T, this,
+ SLOT(slotSwitchFromNoteToRest()),
+ actionCollection(), QString("switch_from_note_to_rest"));
+
+ new KAction
+ (i18n("Switch from Rest to Note"), Key_Y, this,
+ SLOT(slotSwitchFromRestToNote()),
+ actionCollection(), QString("switch_from_rest_to_note"));
+
+
+ // setup Notes menu & toolbar
+ QIconSet icon;
+
+ for (NoteActionDataMap::Iterator actionDataIter = m_noteActionDataMap->begin();
+ actionDataIter != m_noteActionDataMap->end();
+ ++actionDataIter) {
+
+ NoteActionData noteActionData = **actionDataIter;
+
+ icon = QIconSet
+ (NotePixmapFactory::toQPixmap(NotePixmapFactory::makeToolbarPixmap
+ (noteActionData.pixmapName)));
+ noteAction = new KRadioAction(noteActionData.title,
+ icon,
+ noteActionData.keycode,
+ this,
+ SLOT(slotNoteAction()),
+ actionCollection(),
+ noteActionData.actionName);
+ noteAction->setExclusiveGroup("notes");
+
+ if (noteActionData.noteType == Note::Crotchet &&
+ noteActionData.dots == 0 && !noteActionData.rest) {
+ m_selectDefaultNote = noteAction;
+ }
+ }
+
+ // Note duration change actions
+ for (NoteChangeActionDataMap::Iterator actionDataIter = m_noteChangeActionDataMap->begin();
+ actionDataIter != m_noteChangeActionDataMap->end();
+ ++actionDataIter) {
+
+ NoteChangeActionData data = **actionDataIter;
+
+ icon = QIconSet
+ (NotePixmapFactory::toQPixmap(NotePixmapFactory::makeToolbarPixmap
+ (data.pixmapName)));
+
+ KAction *action = new KAction(data.title,
+ icon,
+ data.keycode,
+ this,
+ SLOT(slotNoteChangeAction()),
+ actionCollection(),
+ data.actionName);
+ }
+
+ //
+ // Accidentals
+ //
+ static QString actionsAccidental[][4] =
+ {
+ { i18n("No accidental"), "1slotNoAccidental()", "no_accidental", "accidental-none" },
+ { i18n("Follow previous accidental"), "1slotFollowAccidental()", "follow_accidental", "accidental-follow" },
+ { i18n("Sharp"), "1slotSharp()", "sharp_accidental", "accidental-sharp" },
+ { i18n("Flat"), "1slotFlat()", "flat_accidental", "accidental-flat" },
+ { i18n("Natural"), "1slotNatural()", "natural_accidental", "accidental-natural" },
+ { i18n("Double sharp"), "1slotDoubleSharp()", "double_sharp_accidental", "accidental-doublesharp" },
+ { i18n("Double flat"), "1slotDoubleFlat()", "double_flat_accidental", "accidental-doubleflat" }
+ };
+
+ for (unsigned int i = 0;
+ i < sizeof(actionsAccidental) / sizeof(actionsAccidental[0]); ++i) {
+
+ icon = QIconSet(NotePixmapFactory::toQPixmap(NotePixmapFactory::makeToolbarPixmap
+ (actionsAccidental[i][3])));
+ noteAction = new KRadioAction(actionsAccidental[i][0], icon, 0, this,
+ actionsAccidental[i][1],
+ actionCollection(), actionsAccidental[i][2]);
+ noteAction->setExclusiveGroup("accidentals");
+ }
+
+
+ //
+ // Clefs
+ //
+
+ // Treble
+ icon = QIconSet(NotePixmapFactory::toQPixmap(NotePixmapFactory::makeToolbarPixmap("clef-treble")));
+ noteAction = new KRadioAction(i18n("&Treble Clef"), icon, 0, this,
+ SLOT(slotTrebleClef()),
+ actionCollection(), "treble_clef");
+ noteAction->setExclusiveGroup("notes");
+
+ // Alto
+ icon = QIconSet(NotePixmapFactory::toQPixmap(NotePixmapFactory::makeToolbarPixmap("clef-alto")));
+ noteAction = new KRadioAction(i18n("&Alto Clef"), icon, 0, this,
+ SLOT(slotAltoClef()),
+ actionCollection(), "alto_clef");
+ noteAction->setExclusiveGroup("notes");
+
+ // Tenor
+ icon = QIconSet(NotePixmapFactory::toQPixmap(NotePixmapFactory::makeToolbarPixmap("clef-tenor")));
+ noteAction = new KRadioAction(i18n("Te&nor Clef"), icon, 0, this,
+ SLOT(slotTenorClef()),
+ actionCollection(), "tenor_clef");
+ noteAction->setExclusiveGroup("notes");
+
+ // Bass
+ icon = QIconSet(NotePixmapFactory::toQPixmap(NotePixmapFactory::makeToolbarPixmap("clef-bass")));
+ noteAction = new KRadioAction(i18n("&Bass Clef"), icon, 0, this,
+ SLOT(slotBassClef()),
+ actionCollection(), "bass_clef");
+ noteAction->setExclusiveGroup("notes");
+
+
+ icon = QIconSet(NotePixmapFactory::toQPixmap(NotePixmapFactory::makeToolbarPixmap("text")));
+ noteAction = new KRadioAction(i18n("&Text"), icon, Key_F8, this,
+ SLOT(slotText()),
+ actionCollection(), "text");
+ noteAction->setExclusiveGroup("notes");
+
+ icon = QIconSet(NotePixmapFactory::toQPixmap(NotePixmapFactory::makeToolbarPixmap("guitarchord")));
+ noteAction = new KRadioAction(i18n("&Guitar Chord"), icon, Key_F9, this,
+ SLOT(slotGuitarChord()),
+ actionCollection(), "guitarchord");
+ noteAction->setExclusiveGroup("notes");
+
+ /* icon = QIconSet(NotePixmapFactory::toQPixmap(NotePixmapFactory::makeToolbarPixmap("lilypond")));
+ noteAction = new KRadioAction(i18n("Lil&ypond Directive"), icon, Key_F9, this,
+ SLOT(slotLilyPondDirective()),
+ actionCollection(), "lilypond_directive");
+ noteAction->setExclusiveGroup("notes"); */
+
+
+ //
+ // Edition tools (eraser, selector...)
+ //
+ noteAction = new KRadioAction(i18n("&Erase"), "eraser", Key_F4,
+ this, SLOT(slotEraseSelected()),
+ actionCollection(), "erase");
+ noteAction->setExclusiveGroup("notes");
+
+ icon = QIconSet(NotePixmapFactory::toQPixmap(NotePixmapFactory::makeToolbarPixmap("select")));
+ noteAction = new KRadioAction(i18n("&Select and Edit"), icon, Key_F2,
+ this, SLOT(slotSelectSelected()),
+ actionCollection(), "select");
+ noteAction->setExclusiveGroup("notes");
+
+ icon = QIconSet(NotePixmapFactory::toQPixmap(NotePixmapFactory::makeToolbarPixmap("step_by_step")));
+ new KToggleAction(i18n("Ste&p Recording"), icon, 0, this,
+ SLOT(slotToggleStepByStep()), actionCollection(),
+ "toggle_step_by_step");
+
+
+ // Edit menu
+ new KAction(i18n("Select from Sta&rt"), 0, this,
+ SLOT(slotEditSelectFromStart()), actionCollection(),
+ "select_from_start");
+
+ new KAction(i18n("Select to &End"), 0, this,
+ SLOT(slotEditSelectToEnd()), actionCollection(),
+ "select_to_end");
+
+ new KAction(i18n("Select Whole St&aff"), Key_A + CTRL, this,
+ SLOT(slotEditSelectWholeStaff()), actionCollection(),
+ "select_whole_staff");
+
+ new KAction(i18n("C&ut and Close"), CTRL + SHIFT + Key_X, this,
+ SLOT(slotEditCutAndClose()), actionCollection(),
+ "cut_and_close");
+
+ new KAction(i18n("Pa&ste..."), CTRL + SHIFT + Key_V, this,
+ SLOT(slotEditGeneralPaste()), actionCollection(),
+ "general_paste");
+
+ new KAction(i18n("De&lete"), Key_Delete, this,
+ SLOT(slotEditDelete()), actionCollection(),
+ "delete");
+
+ new KAction(i18n("Move to Staff Above"), 0, this,
+ SLOT(slotMoveEventsUpStaff()), actionCollection(),
+ "move_events_up_staff");
+
+ new KAction(i18n("Move to Staff Below"), 0, this,
+ SLOT(slotMoveEventsDownStaff()), actionCollection(),
+ "move_events_down_staff");
+
+ //
+ // Settings menu
+ //
+ int layoutMode = m_config->readNumEntry("layoutmode", 0);
+
+ QString pixmapDir = KGlobal::dirs()->findResource("appdata", "pixmaps/");
+
+ QCanvasPixmap pixmap(pixmapDir + "/toolbar/linear-layout.xpm");
+ icon = QIconSet(pixmap);
+ KRadioAction *linearModeAction = new KRadioAction
+ (i18n("&Linear Layout"), icon, 0, this, SLOT(slotLinearMode()),
+ actionCollection(), "linear_mode");
+ linearModeAction->setExclusiveGroup("layoutMode");
+ if (layoutMode == 0)
+ linearModeAction->setChecked(true);
+
+ pixmap.load(pixmapDir + "/toolbar/continuous-page-mode.xpm");
+ icon = QIconSet(pixmap);
+ KRadioAction *continuousPageModeAction = new KRadioAction
+ (i18n("&Continuous Page Layout"), icon, 0, this, SLOT(slotContinuousPageMode()),
+ actionCollection(), "continuous_page_mode");
+ continuousPageModeAction->setExclusiveGroup("layoutMode");
+ if (layoutMode == 1)
+ continuousPageModeAction->setChecked(true);
+
+ pixmap.load(pixmapDir + "/toolbar/multi-page-mode.xpm");
+ icon = QIconSet(pixmap);
+ KRadioAction *multiPageModeAction = new KRadioAction
+ (i18n("&Multiple Page Layout"), icon, 0, this, SLOT(slotMultiPageMode()),
+ actionCollection(), "multi_page_mode");
+ multiPageModeAction->setExclusiveGroup("layoutMode");
+ if (layoutMode == 2)
+ multiPageModeAction->setChecked(true);
+
+ new KToggleAction(i18n("Show Ch&ord Name Ruler"), 0, this,
+ SLOT(slotToggleChordsRuler()),
+ actionCollection(), "show_chords_ruler");
+
+ new KToggleAction(i18n("Show Ra&w Note Ruler"), 0, this,
+ SLOT(slotToggleRawNoteRuler()),
+ actionCollection(), "show_raw_note_ruler");
+
+ new KToggleAction(i18n("Show &Tempo Ruler"), 0, this,
+ SLOT(slotToggleTempoRuler()),
+ actionCollection(), "show_tempo_ruler");
+
+ new KToggleAction(i18n("Show &Annotations"), 0, this,
+ SLOT(slotToggleAnnotations()),
+ actionCollection(), "show_annotations");
+
+ new KToggleAction(i18n("Show Lily&Pond Directives"), 0, this,
+ SLOT(slotToggleLilyPondDirectives()),
+ actionCollection(), "show_lilypond_directives");
+
+ new KAction(i18n("Open L&yric Editor"), 0, this, SLOT(slotEditLyrics()),
+ actionCollection(), "lyric_editor");
+
+ //
+ // Group menu
+ //
+ icon = QIconSet
+ (NotePixmapFactory::toQPixmap(NotePixmapFactory::makeToolbarPixmap
+ ("group-beam")));
+
+ new KAction(BeamCommand::getGlobalName(), icon, Key_B + CTRL, this,
+ SLOT(slotGroupBeam()), actionCollection(), "beam");
+
+ new KAction(AutoBeamCommand::getGlobalName(), 0, this,
+ SLOT(slotGroupAutoBeam()), actionCollection(), "auto_beam");
+
+ icon = QIconSet
+ (NotePixmapFactory::toQPixmap(NotePixmapFactory::makeToolbarPixmap
+ ("group-unbeam")));
+
+ new KAction(BreakCommand::getGlobalName(), icon, Key_U + CTRL, this,
+ SLOT(slotGroupBreak()), actionCollection(), "break_group");
+
+ icon = QIconSet
+ (NotePixmapFactory::toQPixmap(NotePixmapFactory::makeToolbarPixmap
+ ("group-simple-tuplet")));
+
+ new KAction(TupletCommand::getGlobalName(true), icon, Key_R + CTRL, this,
+ SLOT(slotGroupSimpleTuplet()), actionCollection(), "simple_tuplet");
+
+ icon = QIconSet
+ (NotePixmapFactory::toQPixmap(NotePixmapFactory::makeToolbarPixmap
+ ("group-tuplet")));
+
+ new KAction(TupletCommand::getGlobalName(false), icon, Key_T + CTRL, this,
+ SLOT(slotGroupGeneralTuplet()), actionCollection(), "tuplet");
+
+ new KAction(UnTupletCommand::getGlobalName(), 0, this,
+ SLOT(slotGroupUnTuplet()), actionCollection(), "break_tuplets");
+
+ icon = QIconSet(NotePixmapFactory::toQPixmap
+ (NotePixmapFactory::makeToolbarPixmap("triplet")));
+ (new KToggleAction(i18n("Trip&let Insert Mode"), icon, Key_G,
+ this, SLOT(slotUpdateInsertModeStatus()),
+ actionCollection(), "triplet_mode"))->
+ setChecked(false);
+
+ icon = QIconSet(NotePixmapFactory::toQPixmap
+ (NotePixmapFactory::makeToolbarPixmap("chord")));
+ (new KToggleAction(i18n("C&hord Insert Mode"), icon, Key_H,
+ this, SLOT(slotUpdateInsertModeStatus()),
+ actionCollection(), "chord_mode"))->
+ setChecked(false);
+
+ icon = QIconSet(NotePixmapFactory::toQPixmap
+ (NotePixmapFactory::makeToolbarPixmap("group-grace")));
+ (new KToggleAction(i18n("Grace Insert Mode"), icon, 0,
+ this, SLOT(slotUpdateInsertModeStatus()),
+ actionCollection(), "grace_mode"))->
+ setChecked(false);
+/*!!!
+ icon = QIconSet
+ (NotePixmapFactory::toQPixmap(NotePixmapFactory::makeToolbarPixmap
+ ("group-grace")));
+
+ new KAction(GraceCommand::getGlobalName(), icon, 0, this,
+ SLOT(slotGroupGrace()), actionCollection(), "grace");
+
+ new KAction(UnGraceCommand::getGlobalName(), 0, this,
+ SLOT(slotGroupUnGrace()), actionCollection(), "ungrace");
+*/
+ icon = QIconSet
+ (NotePixmapFactory::toQPixmap(NotePixmapFactory::makeToolbarPixmap
+ ("group-slur")));
+
+ new KAction(AddIndicationCommand::getGlobalName
+ (Indication::Slur), icon, Key_ParenRight, this,
+ SLOT(slotGroupSlur()), actionCollection(), "slur");
+
+ new KAction(AddIndicationCommand::getGlobalName
+ (Indication::PhrasingSlur), 0, Key_ParenRight + CTRL, this,
+ SLOT(slotGroupPhrasingSlur()), actionCollection(), "phrasing_slur");
+
+ icon = QIconSet
+ (NotePixmapFactory::toQPixmap(NotePixmapFactory::makeToolbarPixmap
+ ("group-glissando")));
+
+ new KAction(AddIndicationCommand::getGlobalName
+ (Indication::Glissando), icon, 0, this,
+ SLOT(slotGroupGlissando()), actionCollection(), "glissando");
+
+ icon = QIconSet
+ (NotePixmapFactory::toQPixmap(NotePixmapFactory::makeToolbarPixmap
+ ("group-crescendo")));
+
+ new KAction(AddIndicationCommand::getGlobalName
+ (Indication::Crescendo), icon, Key_Less, this,
+ SLOT(slotGroupCrescendo()), actionCollection(), "crescendo");
+
+ icon = QIconSet
+ (NotePixmapFactory::toQPixmap(NotePixmapFactory::makeToolbarPixmap
+ ("group-decrescendo")));
+
+ new KAction(AddIndicationCommand::getGlobalName
+ (Indication::Decrescendo), icon, Key_Greater, this,
+ SLOT(slotGroupDecrescendo()), actionCollection(), "decrescendo");
+
+ new KAction(AddIndicationCommand::getGlobalName
+ (Indication::QuindicesimaUp), 0, 0, this,
+ SLOT(slotGroupOctave2Up()), actionCollection(), "octave_2up");
+
+ icon = QIconSet
+ (NotePixmapFactory::toQPixmap(NotePixmapFactory::makeToolbarPixmap
+ ("group-ottava")));
+
+ new KAction(AddIndicationCommand::getGlobalName
+ (Indication::OttavaUp), icon, 0, this,
+ SLOT(slotGroupOctaveUp()), actionCollection(), "octave_up");
+
+ new KAction(AddIndicationCommand::getGlobalName
+ (Indication::OttavaDown), 0, 0, this,
+ SLOT(slotGroupOctaveDown()), actionCollection(), "octave_down");
+
+ new KAction(AddIndicationCommand::getGlobalName
+ (Indication::QuindicesimaDown), 0, 0, this,
+ SLOT(slotGroupOctave2Down()), actionCollection(), "octave_2down");
+
+ icon = QIconSet
+ (NotePixmapFactory::toQPixmap(NotePixmapFactory::makeToolbarPixmap
+ ("group-chord")));
+ new KAction(MakeChordCommand::getGlobalName(), icon, 0, this,
+ SLOT(slotGroupMakeChord()), actionCollection(), "make_chord");
+
+ // setup Transforms menu
+ new KAction(NormalizeRestsCommand::getGlobalName(), Key_N + CTRL, this,
+ SLOT(slotTransformsNormalizeRests()), actionCollection(),
+ "normalize_rests");
+
+ new KAction(CollapseRestsCommand::getGlobalName(), 0, this,
+ SLOT(slotTransformsCollapseRests()), actionCollection(),
+ "collapse_rests_aggressively");
+
+ new KAction(CollapseNotesCommand::getGlobalName(), Key_Equal + CTRL, this,
+ SLOT(slotTransformsCollapseNotes()), actionCollection(),
+ "collapse_notes");
+
+ icon = QIconSet
+ (NotePixmapFactory::toQPixmap(NotePixmapFactory::makeToolbarPixmap
+ ("transforms-tie")));
+
+ new KAction(TieNotesCommand::getGlobalName(), icon, Key_AsciiTilde, this,
+ SLOT(slotTransformsTieNotes()), actionCollection(),
+ "tie_notes");
+
+ new KAction(UntieNotesCommand::getGlobalName(), 0, this,
+ SLOT(slotTransformsUntieNotes()), actionCollection(),
+ "untie_notes");
+
+ new KAction(MakeNotesViableCommand::getGlobalName(), 0, this,
+ SLOT(slotTransformsMakeNotesViable()), actionCollection(),
+ "make_notes_viable");
+
+ icon = QIconSet
+ (NotePixmapFactory::toQPixmap(NotePixmapFactory::makeToolbarPixmap
+ ("transforms-decounterpoint")));
+
+ new KAction(DeCounterpointCommand::getGlobalName(), icon, 0, this,
+ SLOT(slotTransformsDeCounterpoint()), actionCollection(),
+ "de_counterpoint");
+
+ new KAction(ChangeStemsCommand::getGlobalName(true),
+ 0, Key_PageUp + CTRL, this,
+ SLOT(slotTransformsStemsUp()), actionCollection(),
+ "stems_up");
+
+ new KAction(ChangeStemsCommand::getGlobalName(false),
+ 0, Key_PageDown + CTRL, this,
+ SLOT(slotTransformsStemsDown()), actionCollection(),
+ "stems_down");
+
+ new KAction(RestoreStemsCommand::getGlobalName(), 0, this,
+ SLOT(slotTransformsRestoreStems()), actionCollection(),
+ "restore_stems");
+
+ new KAction(ChangeSlurPositionCommand::getGlobalName(true),
+ 0, this,
+ SLOT(slotTransformsSlursAbove()), actionCollection(),
+ "slurs_above");
+
+ new KAction(ChangeSlurPositionCommand::getGlobalName(false),
+ 0, this,
+ SLOT(slotTransformsSlursBelow()), actionCollection(),
+ "slurs_below");
+
+ new KAction(RestoreSlursCommand::getGlobalName(), 0, this,
+ SLOT(slotTransformsRestoreSlurs()), actionCollection(),
+ "restore_slurs");
+
+ new KAction(ChangeTiePositionCommand::getGlobalName(true),
+ 0, this,
+ SLOT(slotTransformsTiesAbove()), actionCollection(),
+ "ties_above");
+
+ new KAction(ChangeTiePositionCommand::getGlobalName(false),
+ 0, this,
+ SLOT(slotTransformsTiesBelow()), actionCollection(),
+ "ties_below");
+
+ new KAction(RestoreTiesCommand::getGlobalName(), 0, this,
+ SLOT(slotTransformsRestoreTies()), actionCollection(),
+ "restore_ties");
+
+ icon = QIconSet
+ (NotePixmapFactory::toQPixmap(NotePixmapFactory::makeToolbarPixmap
+ ("accmenu-doubleflat")));
+
+ new KAction(RespellCommand::getGlobalName
+ (RespellCommand::Set, Accidentals::DoubleFlat),
+ icon, 0, this,
+ SLOT(slotRespellDoubleFlat()), actionCollection(),
+ "respell_doubleflat");
+
+ icon = QIconSet
+ (NotePixmapFactory::toQPixmap(NotePixmapFactory::makeToolbarPixmap
+ ("accmenu-flat")));
+
+ new KAction(RespellCommand::getGlobalName
+ (RespellCommand::Set, Accidentals::Flat),
+ icon, 0, this,
+ SLOT(slotRespellFlat()), actionCollection(),
+ "respell_flat");
+
+ icon = QIconSet
+ (NotePixmapFactory::toQPixmap(NotePixmapFactory::makeToolbarPixmap
+ ("accmenu-natural")));
+
+ new KAction(RespellCommand::getGlobalName
+ (RespellCommand::Set, Accidentals::Natural),
+ icon, 0, this,
+ SLOT(slotRespellNatural()), actionCollection(),
+ "respell_natural");
+
+ icon = QIconSet
+ (NotePixmapFactory::toQPixmap(NotePixmapFactory::makeToolbarPixmap
+ ("accmenu-sharp")));
+
+ new KAction(RespellCommand::getGlobalName
+ (RespellCommand::Set, Accidentals::Sharp),
+ icon, 0, this,
+ SLOT(slotRespellSharp()), actionCollection(),
+ "respell_sharp");
+
+ icon = QIconSet
+ (NotePixmapFactory::toQPixmap(NotePixmapFactory::makeToolbarPixmap
+ ("accmenu-doublesharp")));
+
+ new KAction(RespellCommand::getGlobalName
+ (RespellCommand::Set, Accidentals::DoubleSharp),
+ icon, 0, this,
+ SLOT(slotRespellDoubleSharp()), actionCollection(),
+ "respell_doublesharp");
+
+ new KAction(RespellCommand::getGlobalName
+ (RespellCommand::Up, Accidentals::NoAccidental),
+ Key_Up + CTRL + SHIFT, this,
+ SLOT(slotRespellUp()), actionCollection(),
+ "respell_up");
+
+ new KAction(RespellCommand::getGlobalName
+ (RespellCommand::Down, Accidentals::NoAccidental),
+ Key_Down + CTRL + SHIFT, this,
+ SLOT(slotRespellDown()), actionCollection(),
+ "respell_down");
+
+ new KAction(RespellCommand::getGlobalName
+ (RespellCommand::Restore, Accidentals::NoAccidental),
+ 0, this,
+ SLOT(slotRespellRestore()), actionCollection(),
+ "respell_restore");
+
+ new KAction(MakeAccidentalsCautionaryCommand::getGlobalName(true),
+ 0, this,
+ SLOT(slotShowCautionary()), actionCollection(),
+ "show_cautionary");
+
+ new KAction(MakeAccidentalsCautionaryCommand::getGlobalName(false),
+ 0, this,
+ SLOT(slotCancelCautionary()), actionCollection(),
+ "cancel_cautionary");
+
+ icon = QIconSet
+ (NotePixmapFactory::toQPixmap(NotePixmapFactory::makeToolbarPixmap
+ ("quantize")));
+
+ new KAction(EventQuantizeCommand::getGlobalName(), icon, Key_Equal, this,
+ SLOT(slotTransformsQuantize()), actionCollection(),
+ "quantize");
+
+ new KAction(FixNotationQuantizeCommand::getGlobalName(), 0,
+ this, SLOT(slotTransformsFixQuantization()), actionCollection(),
+ "fix_quantization");
+
+ new KAction(RemoveNotationQuantizeCommand::getGlobalName(), 0,
+ this, SLOT(slotTransformsRemoveQuantization()), actionCollection(),
+ "remove_quantization");
+
+ new KAction(InterpretCommand::getGlobalName(), 0,
+ this, SLOT(slotTransformsInterpret()), actionCollection(),
+ "interpret");
+
+ new KAction(i18n("&Dump selected events to stderr"), 0, this,
+ SLOT(slotDebugDump()), actionCollection(), "debug_dump");
+
+ for (MarkActionDataMap::Iterator i = m_markActionDataMap->begin();
+ i != m_markActionDataMap->end(); ++i) {
+
+ const MarkActionData &markActionData = **i;
+
+ icon = QIconSet(NotePixmapFactory::toQPixmap
+ (NotePixmapFactory::makeMarkMenuPixmap(markActionData.mark)));
+
+ new KAction(markActionData.title,
+ icon,
+ markActionData.keycode,
+ this,
+ SLOT(slotAddMark()),
+ actionCollection(),
+ markActionData.actionName);
+ }
+
+ icon = QIconSet
+ (NotePixmapFactory::toQPixmap(NotePixmapFactory::makeToolbarPixmap
+ ("text-mark")));
+
+ new KAction(AddTextMarkCommand::getGlobalName(), icon, 0, this,
+ SLOT(slotMarksAddTextMark()), actionCollection(),
+ "add_text_mark");
+
+ new KAction(AddFingeringMarkCommand::getGlobalName("0"), 0, Key_0 + ALT, this,
+ SLOT(slotMarksAddFingeringMarkFromAction()), actionCollection(),
+ "add_fingering_0");
+
+ new KAction(AddFingeringMarkCommand::getGlobalName("1"), 0, Key_1 + ALT, this,
+ SLOT(slotMarksAddFingeringMarkFromAction()), actionCollection(),
+ "add_fingering_1");
+
+ new KAction(AddFingeringMarkCommand::getGlobalName("2"), 0, Key_2 + ALT, this,
+ SLOT(slotMarksAddFingeringMarkFromAction()), actionCollection(),
+ "add_fingering_2");
+
+ new KAction(AddFingeringMarkCommand::getGlobalName("3"), 0, Key_3 + ALT, this,
+ SLOT(slotMarksAddFingeringMarkFromAction()), actionCollection(),
+ "add_fingering_3");
+
+ new KAction(AddFingeringMarkCommand::getGlobalName("4"), 0, Key_4 + ALT, this,
+ SLOT(slotMarksAddFingeringMarkFromAction()), actionCollection(),
+ "add_fingering_4");
+
+ new KAction(AddFingeringMarkCommand::getGlobalName("5"), 0, Key_5 + ALT, this,
+ SLOT(slotMarksAddFingeringMarkFromAction()), actionCollection(),
+ "add_fingering_5");
+
+ new KAction(AddFingeringMarkCommand::getGlobalName("+"), 0, Key_9 + ALT, this,
+ SLOT(slotMarksAddFingeringMarkFromAction()), actionCollection(),
+ "add_fingering_plus");
+
+ new KAction(AddFingeringMarkCommand::getGlobalName(), 0, 0, this,
+ SLOT(slotMarksAddFingeringMark()), actionCollection(),
+ "add_fingering_mark");
+
+ new KAction(RemoveMarksCommand::getGlobalName(), 0, this,
+ SLOT(slotMarksRemoveMarks()), actionCollection(),
+ "remove_marks");
+
+ new KAction(RemoveFingeringMarksCommand::getGlobalName(), 0, this,
+ SLOT(slotMarksRemoveFingeringMarks()), actionCollection(),
+ "remove_fingering_marks");
+
+ new KAction(i18n("Ma&ke Ornament..."), 0, this,
+ SLOT(slotMakeOrnament()), actionCollection(),
+ "make_ornament");
+
+ new KAction(i18n("Trigger &Ornament..."), 0, this,
+ SLOT(slotUseOrnament()), actionCollection(),
+ "use_ornament");
+
+ new KAction(i18n("Remove Ornament..."), 0, this,
+ SLOT(slotRemoveOrnament()), actionCollection(),
+ "remove_ornament");
+
+ static QString slashTitles[] = {
+ i18n("&None"), "&1", "&2", "&3", "&4", "&5"
+ };
+ for (int i = 0; i <= 5; ++i) {
+ new KAction(slashTitles[i], 0, this,
+ SLOT(slotAddSlashes()), actionCollection(),
+ QString("slashes_%1").arg(i));
+ }
+
+ new KAction(ClefInsertionCommand::getGlobalName(), 0, this,
+ SLOT(slotEditAddClef()), actionCollection(),
+ "add_clef");
+
+ new KAction(KeyInsertionCommand::getGlobalName(), 0, this,
+ SLOT(slotEditAddKeySignature()), actionCollection(),
+ "add_key_signature");
+
+ new KAction(SustainInsertionCommand::getGlobalName(true), 0, this,
+ SLOT(slotEditAddSustainDown()), actionCollection(),
+ "add_sustain_down");
+
+ new KAction(SustainInsertionCommand::getGlobalName(false), 0, this,
+ SLOT(slotEditAddSustainUp()), actionCollection(),
+ "add_sustain_up");
+
+ new KAction(TransposeCommand::getDiatonicGlobalName(false), 0, this,
+ SLOT(slotEditTranspose()), actionCollection(),
+ "transpose_segment");
+
+ new KAction(i18n("Convert Notation For..."), 0, this,
+ SLOT(slotEditSwitchPreset()), actionCollection(),
+ "switch_preset");
+
+
+ // setup Settings menu
+ static QString actionsToolbars[][4] =
+ {
+ { i18n("Show T&ools Toolbar"), "1slotToggleToolsToolBar()", "show_tools_toolbar", "palette-tools" },
+ { i18n("Show &Notes Toolbar"), "1slotToggleNotesToolBar()", "show_notes_toolbar", "palette-notes" },
+ { i18n("Show &Rests Toolbar"), "1slotToggleRestsToolBar()", "show_rests_toolbar", "palette-rests" },
+ { i18n("Show &Accidentals Toolbar"), "1slotToggleAccidentalsToolBar()", "show_accidentals_toolbar", "palette-accidentals" },
+ { i18n("Show Cle&fs Toolbar"), "1slotToggleClefsToolBar()", "show_clefs_toolbar",
+ "palette-clefs" },
+ { i18n("Show &Marks Toolbar"), "1slotToggleMarksToolBar()", "show_marks_toolbar",
+ "palette-marks" },
+ { i18n("Show &Group Toolbar"), "1slotToggleGroupToolBar()", "show_group_toolbar",
+ "palette-group" },
+ { i18n("Show &Layout Toolbar"), "1slotToggleLayoutToolBar()", "show_layout_toolbar",
+ "palette-font" },
+ { i18n("Show Trans&port Toolbar"), "1slotToggleTransportToolBar()", "show_transport_toolbar",
+ "palette-transport" },
+ { i18n("Show M&eta Toolbar"), "1slotToggleMetaToolBar()", "show_meta_toolbar",
+ "palette-meta" }
+ };
+
+ for (unsigned int i = 0;
+ i < sizeof(actionsToolbars) / sizeof(actionsToolbars[0]); ++i) {
+
+ icon = QIconSet(NotePixmapFactory::toQPixmap(NotePixmapFactory::makeToolbarPixmap(actionsToolbars[i][3])));
+
+ new KToggleAction(actionsToolbars[i][0], icon, 0,
+ this, actionsToolbars[i][1],
+ actionCollection(), actionsToolbars[i][2]);
+ }
+
+ new KAction(i18n("Cursor &Back"), 0, Key_Left, this,
+ SLOT(slotStepBackward()), actionCollection(),
+ "cursor_back");
+
+ new KAction(i18n("Cursor &Forward"), 0, Key_Right, this,
+ SLOT(slotStepForward()), actionCollection(),
+ "cursor_forward");
+
+ new KAction(i18n("Cursor Ba&ck Bar"), 0, Key_Left + CTRL, this,
+ SLOT(slotJumpBackward()), actionCollection(),
+ "cursor_back_bar");
+
+ new KAction(i18n("Cursor For&ward Bar"), 0, Key_Right + CTRL, this,
+ SLOT(slotJumpForward()), actionCollection(),
+ "cursor_forward_bar");
+
+ new KAction(i18n("Cursor Back and Se&lect"), SHIFT + Key_Left, this,
+ SLOT(slotExtendSelectionBackward()), actionCollection(),
+ "extend_selection_backward");
+
+ new KAction(i18n("Cursor Forward and &Select"), SHIFT + Key_Right, this,
+ SLOT(slotExtendSelectionForward()), actionCollection(),
+ "extend_selection_forward");
+
+ new KAction(i18n("Cursor Back Bar and Select"), SHIFT + CTRL + Key_Left, this,
+ SLOT(slotExtendSelectionBackwardBar()), actionCollection(),
+ "extend_selection_backward_bar");
+
+ new KAction(i18n("Cursor Forward Bar and Select"), SHIFT + CTRL + Key_Right, this,
+ SLOT(slotExtendSelectionForwardBar()), actionCollection(),
+ "extend_selection_forward_bar");
+
+ /*!!! not here yet
+ new KAction(i18n("Move Selection Left"), Key_Minus, this,
+ SLOT(slotMoveSelectionLeft()), actionCollection(),
+ "move_selection_left");
+ */
+
+ new KAction(i18n("Cursor to St&art"), 0,
+ /* #1025717: conflicting meanings for ctrl+a - dupe with Select All
+ Key_A + CTRL, */ this,
+ SLOT(slotJumpToStart()), actionCollection(),
+ "cursor_start");
+
+ new KAction(i18n("Cursor to &End"), 0, Key_E + CTRL, this,
+ SLOT(slotJumpToEnd()), actionCollection(),
+ "cursor_end");
+
+ new KAction(i18n("Cursor &Up Staff"), 0, Key_Up + SHIFT, this,
+ SLOT(slotCurrentStaffUp()), actionCollection(),
+ "cursor_up_staff");
+
+ new KAction(i18n("Cursor &Down Staff"), 0, Key_Down + SHIFT, this,
+ SLOT(slotCurrentStaffDown()), actionCollection(),
+ "cursor_down_staff");
+
+ new KAction(i18n("Cursor Pre&vious Segment"), 0, Key_Prior + ALT, this,
+ SLOT(slotCurrentSegmentPrior()), actionCollection(),
+ "cursor_prior_segment");
+
+ new KAction(i18n("Cursor Ne&xt Segment"), 0, Key_Next + ALT, this,
+ SLOT(slotCurrentSegmentNext()), actionCollection(),
+ "cursor_next_segment");
+
+ icon = QIconSet(NotePixmapFactory::toQPixmap(NotePixmapFactory::makeToolbarPixmap
+ ("transport-cursor-to-pointer")));
+ new KAction(i18n("Cursor to &Playback Pointer"), icon, 0, this,
+ SLOT(slotJumpCursorToPlayback()), actionCollection(),
+ "cursor_to_playback_pointer");
+
+ icon = QIconSet(NotePixmapFactory::toQPixmap(NotePixmapFactory::makeToolbarPixmap
+ ("transport-play")));
+ KAction *play = new KAction(i18n("&Play"), icon, Key_Enter, this,
+ SIGNAL(play()), actionCollection(), "play");
+ // Alternative shortcut for Play
+ KShortcut playShortcut = play->shortcut();
+ playShortcut.append( KKey(Key_Return + CTRL) );
+ play->setShortcut(playShortcut);
+
+ icon = QIconSet(NotePixmapFactory::toQPixmap(NotePixmapFactory::makeToolbarPixmap
+ ("transport-stop")));
+ new KAction(i18n("&Stop"), icon, Key_Insert, this,
+ SIGNAL(stop()), actionCollection(), "stop");
+
+ icon = QIconSet(NotePixmapFactory::toQPixmap(NotePixmapFactory::makeToolbarPixmap
+ ("transport-rewind")));
+ new KAction(i18n("Re&wind"), icon, Key_End, this,
+ SIGNAL(rewindPlayback()), actionCollection(),
+ "playback_pointer_back_bar");
+
+ icon = QIconSet(NotePixmapFactory::toQPixmap(NotePixmapFactory::makeToolbarPixmap
+ ("transport-ffwd")));
+ new KAction(i18n("&Fast Forward"), icon, Key_PageDown, this,
+ SIGNAL(fastForwardPlayback()), actionCollection(),
+ "playback_pointer_forward_bar");
+
+ icon = QIconSet(NotePixmapFactory::toQPixmap(NotePixmapFactory::makeToolbarPixmap
+ ("transport-rewind-end")));
+ new KAction(i18n("Rewind to &Beginning"), icon, 0, this,
+ SIGNAL(rewindPlaybackToBeginning()), actionCollection(),
+ "playback_pointer_start");
+
+ icon = QIconSet(NotePixmapFactory::toQPixmap(NotePixmapFactory::makeToolbarPixmap
+ ("transport-ffwd-end")));
+ new KAction(i18n("Fast Forward to &End"), icon, 0, this,
+ SIGNAL(fastForwardPlaybackToEnd()), actionCollection(),
+ "playback_pointer_end");
+
+ icon = QIconSet(NotePixmapFactory::toQPixmap(NotePixmapFactory::makeToolbarPixmap
+ ("transport-pointer-to-cursor")));
+ new KAction(i18n("Playback Pointer to &Cursor"), icon, 0, this,
+ SLOT(slotJumpPlaybackToCursor()), actionCollection(),
+ "playback_pointer_to_cursor");
+
+ icon = QIconSet(NotePixmapFactory::toQPixmap(NotePixmapFactory::makeToolbarPixmap
+ ("transport-solo")));
+ new KToggleAction(i18n("&Solo"), icon, 0, this,
+ SLOT(slotToggleSolo()), actionCollection(),
+ "toggle_solo");
+
+ icon = QIconSet(NotePixmapFactory::toQPixmap(NotePixmapFactory::makeToolbarPixmap
+ ("transport-tracking")));
+ (new KToggleAction(i18n("Scro&ll to Follow Playback"), icon, Key_Pause, this,
+ SLOT(slotToggleTracking()), actionCollection(),
+ "toggle_tracking"))->setChecked(m_playTracking);
+
+ icon = QIconSet(NotePixmapFactory::toQPixmap(NotePixmapFactory::makeToolbarPixmap
+ ("transport-panic")));
+ new KAction(i18n("Panic"), icon, Key_P + CTRL + ALT, this,
+ SIGNAL(panic()), actionCollection(), "panic");
+
+ new KAction(i18n("Set Loop to Selection"), Key_Semicolon + CTRL, this,
+ SLOT(slotPreviewSelection()), actionCollection(),
+ "preview_selection");
+
+ new KAction(i18n("Clear L&oop"), Key_Colon + CTRL, this,
+ SLOT(slotClearLoop()), actionCollection(),
+ "clear_loop");
+
+ new KAction(i18n("Clear Selection"), Key_Escape, this,
+ SLOT(slotClearSelection()), actionCollection(),
+ "clear_selection");
+
+ // QString pixmapDir =
+ // KGlobal::dirs()->findResource("appdata", "pixmaps/");
+ // icon = QIconSet(QCanvasPixmap(pixmapDir + "/toolbar/eventfilter.xpm"));
+ new KAction(i18n("&Filter Selection"), "filter", Key_F + CTRL, this,
+ SLOT(slotFilterSelection()), actionCollection(),
+ "filter_selection");
+
+ new KAction(i18n("Push &Left"), 0, this,
+ SLOT(slotFinePositionLeft()), actionCollection(),
+ "fine_position_left");
+
+ new KAction(i18n("Push &Right"), 0, this,
+ SLOT(slotFinePositionRight()), actionCollection(),
+ "fine_position_right");
+
+ new KAction(i18n("Push &Up"), 0, this,
+ SLOT(slotFinePositionUp()), actionCollection(),
+ "fine_position_up");
+
+ new KAction(i18n("Push &Down"), 0, this,
+ SLOT(slotFinePositionDown()), actionCollection(),
+ "fine_position_down");
+
+ new KAction(i18n("&Restore Positions"), 0, this,
+ SLOT(slotFinePositionRestore()), actionCollection(),
+ "fine_position_restore");
+
+ new KAction(i18n("Make &Invisible"), 0, this,
+ SLOT(slotMakeInvisible()), actionCollection(),
+ "make_invisible");
+
+ new KAction(i18n("Make &Visible"), 0, this,
+ SLOT(slotMakeVisible()), actionCollection(),
+ "make_visible");
+
+ new KAction(i18n("Toggle Dot"), Key_Period, this,
+ SLOT(slotToggleDot()), actionCollection(),
+ "toggle_dot");
+
+ new KAction(i18n("Add Dot"), Key_Period + CTRL, this,
+ SLOT(slotAddDot()), actionCollection(),
+ "add_dot");
+
+ new KAction(i18n("Add Dot"), Key_Period + CTRL + ALT, this,
+ SLOT(slotAddDotNotationOnly()), actionCollection(),
+ "add_notation_dot");
+
+ createGUI(getRCFileName(), false);
+}
+
+bool
+NotationView::isInChordMode()
+{
+ return ((KToggleAction *)actionCollection()->action("chord_mode"))->
+ isChecked();
+}
+
+bool
+NotationView::isInTripletMode()
+{
+ return ((KToggleAction *)actionCollection()->action("triplet_mode"))->
+ isChecked();
+}
+
+bool
+NotationView::isInGraceMode()
+{
+ return ((KToggleAction *)actionCollection()->action("grace_mode"))->
+ isChecked();
+}
+
+void
+NotationView::setupFontSizeMenu(std::string oldFontName)
+{
+ if (oldFontName != "") {
+
+ std::vector<int> sizes = NoteFontFactory::getScreenSizes(oldFontName);
+
+ for (unsigned int i = 0; i < sizes.size(); ++i) {
+ KAction *action =
+ actionCollection()->action
+ (QString("note_font_size_%1").arg(sizes[i]));
+ m_fontSizeActionMenu->remove
+ (action);
+
+ // Don't delete -- that could cause a crash when this
+ // function is called from the action itself. Instead
+ // we reuse and reinsert existing actions below.
+ }
+ }
+
+ std::vector<int> sizes = NoteFontFactory::getScreenSizes(m_fontName);
+
+ for (unsigned int i = 0; i < sizes.size(); ++i) {
+
+ QString actionName = QString("note_font_size_%1").arg(sizes[i]);
+
+ KToggleAction *sizeAction = dynamic_cast<KToggleAction *>
+ (actionCollection()->action(actionName));
+
+ if (!sizeAction) {
+ sizeAction =
+ new KToggleAction(i18n("1 pixel", "%n pixels", sizes[i]),
+ 0, this,
+ SLOT(slotChangeFontSizeFromAction()),
+ actionCollection(), actionName);
+ }
+
+ sizeAction->setChecked(sizes[i] == m_fontSize);
+ m_fontSizeActionMenu->insert(sizeAction);
+ }
+}
+
+LinedStaff *
+NotationView::getLinedStaff(int i)
+{
+ return getNotationStaff(i);
+}
+
+LinedStaff *
+NotationView::getLinedStaff(const Segment &segment)
+{
+ return getNotationStaff(segment);
+}
+
+NotationStaff *
+NotationView::getNotationStaff(const Segment &segment)
+{
+ for (unsigned int i = 0; i < m_staffs.size(); ++i) {
+ if (&(m_staffs[i]->getSegment()) == &segment)
+ return m_staffs[i];
+ }
+ return 0;
+}
+
+bool NotationView::isCurrentStaff(int i)
+{
+ return getCurrentSegment() == &(m_staffs[i]->getSegment());
+}
+
+void NotationView::initLayoutToolbar()
+{
+ KToolBar *layoutToolbar = toolBar("Layout Toolbar");
+
+ if (!layoutToolbar) {
+ std::cerr
+ << "NotationView::initLayoutToolbar() : layout toolbar not found"
+ << std::endl;
+ return ;
+ }
+
+ new QLabel(i18n(" Font: "), layoutToolbar, "font label");
+
+ //
+ // font combo
+ //
+ m_fontCombo = new KComboBox(layoutToolbar);
+ m_fontCombo->setEditable(false);
+
+ std::set
+ <std::string> fs(NoteFontFactory::getFontNames());
+ std::vector<std::string> f(fs.begin(), fs.end());
+ std::sort(f.begin(), f.end());
+
+ bool foundFont = false;
+
+ for (std::vector<std::string>::iterator i = f.begin(); i != f.end(); ++i) {
+
+ QString fontQName(strtoqstr(*i));
+
+ m_fontCombo->insertItem(fontQName);
+ if (fontQName.lower() == strtoqstr(m_fontName).lower()) {
+ m_fontCombo->setCurrentItem(m_fontCombo->count() - 1);
+ foundFont = true;
+ }
+ }
+
+ if (!foundFont) {
+ KMessageBox::sorry
+ (this, i18n("Unknown font \"%1\", using default").arg
+ (strtoqstr(m_fontName)));
+ m_fontName = NoteFontFactory::getDefaultFontName();
+ }
+
+ connect(m_fontCombo, SIGNAL(activated(const QString &)),
+ this, SLOT(slotChangeFont(const QString &)));
+
+ new QLabel(i18n(" Size: "), layoutToolbar, "size label");
+
+ QString value;
+
+ //
+ // font size combo
+ //
+ std::vector<int> sizes = NoteFontFactory::getScreenSizes(m_fontName);
+ m_fontSizeCombo = new KComboBox(layoutToolbar, "font size combo");
+
+ for (std::vector<int>::iterator i = sizes.begin(); i != sizes.end(); ++i) {
+
+ value.setNum(*i);
+ m_fontSizeCombo->insertItem(value);
+ }
+ // set combo's current value to default
+ value.setNum(m_fontSize);
+ m_fontSizeCombo->setCurrentText(value);
+
+ connect(m_fontSizeCombo, SIGNAL(activated(const QString&)),
+ this, SLOT(slotChangeFontSizeFromStringValue(const QString&)));
+
+ new QLabel(i18n(" Spacing: "), layoutToolbar, "spacing label");
+
+ //
+ // spacing combo
+ //
+ int defaultSpacing = m_hlayout->getSpacing();
+ std::vector<int> spacings = NotationHLayout::getAvailableSpacings();
+
+ m_spacingCombo = new KComboBox(layoutToolbar, "spacing combo");
+ for (std::vector<int>::iterator i = spacings.begin(); i != spacings.end(); ++i) {
+
+ value.setNum(*i);
+ value += "%";
+ m_spacingCombo->insertItem(value);
+ }
+ // set combo's current value to default
+ value.setNum(defaultSpacing);
+ value += "%";
+ m_spacingCombo->setCurrentText(value);
+
+ connect(m_spacingCombo, SIGNAL(activated(const QString&)),
+ this, SLOT(slotChangeSpacingFromStringValue(const QString&)));
+}
+
+void NotationView::initStatusBar()
+{
+ KStatusBar* sb = statusBar();
+
+ m_hoveredOverNoteName = new QLabel(sb);
+ m_hoveredOverNoteName->setMinimumWidth(32);
+
+ m_hoveredOverAbsoluteTime = new QLabel(sb);
+ m_hoveredOverAbsoluteTime->setMinimumWidth(160);
+
+ sb->addWidget(m_hoveredOverAbsoluteTime);
+ sb->addWidget(m_hoveredOverNoteName);
+
+ QHBox *hbox = new QHBox(sb);
+ m_currentNotePixmap = new QLabel(hbox);
+ m_currentNotePixmap->setMinimumWidth(20);
+ m_insertModeLabel = new QLabel(hbox);
+ m_annotationsLabel = new QLabel(hbox);
+ m_lilyPondDirectivesLabel = new QLabel(hbox);
+ sb->addWidget(hbox);
+
+ sb->insertItem(KTmpStatusMsg::getDefaultMsg(),
+ KTmpStatusMsg::getDefaultId(), 1);
+ sb->setItemAlignment(KTmpStatusMsg::getDefaultId(),
+ AlignLeft | AlignVCenter);
+
+ m_selectionCounter = new QLabel(sb);
+ sb->addWidget(m_selectionCounter);
+
+ m_progressBar = new ProgressBar(100, true, sb);
+ m_progressBar->setMinimumWidth(100);
+ sb->addWidget(m_progressBar);
+}
+
+QSize NotationView::getViewSize()
+{
+ return canvas()->size();
+}
+
+void NotationView::setViewSize(QSize s)
+{
+ canvas()->resize(s.width(), s.height());
+
+ if ( (m_pageMode == LinedStaff::LinearMode)
+ && (m_showHeadersGroup != HeadersGroup::ShowNever)) {
+ m_headersGroup->completeToHeight(s.height());
+ }
+}
+
+void
+NotationView::setPageMode(LinedStaff::PageMode pageMode)
+{
+ m_pageMode = pageMode;
+
+ if (pageMode != LinedStaff::LinearMode) {
+ if (m_topStandardRuler)
+ m_topStandardRuler->hide();
+ if (m_bottomStandardRuler)
+ m_bottomStandardRuler->hide();
+ if (m_chordNameRuler)
+ m_chordNameRuler->hide();
+ if (m_rawNoteRuler)
+ m_rawNoteRuler->hide();
+ if (m_tempoRuler)
+ m_tempoRuler->hide();
+ hideHeadersGroup();
+ } else {
+ if (m_topStandardRuler)
+ m_topStandardRuler->show();
+ if (m_bottomStandardRuler)
+ m_bottomStandardRuler->show();
+ if (m_chordNameRuler && getToggleAction("show_chords_ruler")->isChecked())
+ m_chordNameRuler->show();
+ if (m_rawNoteRuler && getToggleAction("show_raw_note_ruler")->isChecked())
+ m_rawNoteRuler->show();
+ if (m_tempoRuler && getToggleAction("show_tempo_ruler")->isChecked())
+ m_tempoRuler->show();
+ showHeadersGroup();
+ }
+
+ stateChanged("linear_mode",
+ (pageMode == LinedStaff::LinearMode ? KXMLGUIClient::StateNoReverse :
+ KXMLGUIClient::StateReverse));
+
+ int pageWidth = getPageWidth();
+ int topMargin = 0, leftMargin = 0;
+ getPageMargins(leftMargin, topMargin);
+
+ m_hlayout->setPageMode(pageMode != LinedStaff::LinearMode);
+ m_hlayout->setPageWidth(pageWidth - leftMargin * 2);
+
+ NOTATION_DEBUG << "NotationView::setPageMode: set layout's page width to "
+ << (pageWidth - leftMargin * 2) << endl;
+
+ positionStaffs();
+
+ bool layoutApplied = applyLayout();
+ if (!layoutApplied)
+ KMessageBox::sorry(0, "Couldn't apply layout");
+ else {
+ for (unsigned int i = 0; i < m_staffs.size(); ++i) {
+ m_staffs[i]->markChanged();
+ }
+ }
+
+ if (!m_printMode) {
+ // Layout is done : Time related to left of canvas should now
+ // correctly be determined and track headers contents be drawn.
+ m_headersGroup->slotUpdateAllHeaders(0, 0, true);
+ }
+
+ positionPages();
+
+ if (!m_printMode) {
+ updateView();
+ slotSetInsertCursorPosition(getInsertionTime(), false, false);
+ slotSetPointerPosition(getDocument()->getComposition().getPosition(), false);
+ }
+
+ Profiles::getInstance()->dump();
+}
+
+int
+NotationView::getPageWidth()
+{
+ if (m_pageMode != LinedStaff::MultiPageMode) {
+
+ if (isInPrintMode() && getCanvasView() && getCanvasView()->canvas())
+ return getCanvasView()->canvas()->width();
+
+ if (getCanvasView()) {
+ return
+ getCanvasView()->width() -
+ getCanvasView()->verticalScrollBar()->width() -
+ m_leftGutter - 10;
+ }
+
+ return width() - 50;
+
+ } else {
+
+ //!!! For the moment we use A4 for this calculation
+
+ double printSizeMm = 25.4 * ((double)m_printSize / 72.0);
+ double mmPerPixel = printSizeMm / (double)m_notePixmapFactory->getSize();
+ return (int)(210.0 / mmPerPixel);
+ }
+}
+
+int
+NotationView::getPageHeight()
+{
+ if (m_pageMode != LinedStaff::MultiPageMode) {
+
+ if (isInPrintMode() && getCanvasView() && getCanvasView()->canvas())
+ return getCanvasView()->canvas()->height();
+
+ if (getCanvasView()) {
+ return getCanvasView()->height();
+ }
+
+ return (height() > 200 ? height() - 100 : height());
+
+ } else {
+
+ //!!! For the moment we use A4 for this calculation
+
+ double printSizeMm = 25.4 * ((double)m_printSize / 72.0);
+ double mmPerPixel = printSizeMm / (double)m_notePixmapFactory->getSize();
+ return (int)(297.0 / mmPerPixel);
+ }
+}
+
+void
+NotationView::getPageMargins(int &left, int &top)
+{
+ if (m_pageMode != LinedStaff::MultiPageMode) {
+
+ left = 0;
+ top = 0;
+
+ } else {
+
+ //!!! For the moment we use A4 for this calculation
+
+ double printSizeMm = 25.4 * ((double)m_printSize / 72.0);
+ double mmPerPixel = printSizeMm / (double)m_notePixmapFactory->getSize();
+ left = (int)(20.0 / mmPerPixel);
+ top = (int)(15.0 / mmPerPixel);
+ }
+}
+
+void
+NotationView::scrollToTime(timeT t)
+{
+
+ double notationViewLayoutCoord = m_hlayout->getXForTime(t);
+
+ // Doesn't appear to matter which staff we use
+ //!!! actually it probably does matter, if they don't have the same extents
+ double notationViewCanvasCoord =
+ getLinedStaff(0)->getCanvasCoordsForLayoutCoords
+ (notationViewLayoutCoord, 0).first;
+
+ // HK: I could have sworn I saw a hard-coded scroll happen somewhere
+ // (i.e. a default extra scroll to make up for the staff not beginning on
+ // the left edge) but now I can't find it.
+ getCanvasView()->slotScrollHorizSmallSteps
+ (int(notationViewCanvasCoord)); // + DEFAULT_STAFF_OFFSET);
+}
+
+RulerScale*
+NotationView::getHLayout()
+{
+ return m_hlayout;
+}
+
+void
+NotationView::paintEvent(QPaintEvent *e)
+{
+ m_inPaintEvent = true;
+
+ // This is duplicated here from EditViewBase, because (a) we need
+ // to know about the segment being removed before we try to check
+ // the staff names etc., and (b) it's not safe to call close()
+ // from EditViewBase::paintEvent if we're then going to try to do
+ // some more work afterwards in this function
+
+ if (isCompositionModified()) {
+
+ // Check if one of the segments we display has been removed
+ // from the composition.
+ //
+ // For the moment we'll have to close the view if any of the
+ // segments we handle has been deleted.
+
+ for (unsigned int i = 0; i < m_segments.size(); ++i) {
+
+ if (!m_segments[i]->getComposition()) {
+ // oops, I think we've been deleted
+ close();
+ return ;
+ }
+ }
+ }
+
+ int topMargin = 0, leftMargin = 0;
+ getPageMargins(leftMargin, topMargin);
+
+ if (m_pageMode == LinedStaff::ContinuousPageMode) {
+ // relayout if the window width changes significantly in continuous page mode
+ int diff = int(getPageWidth() - leftMargin * 2 - m_hlayout->getPageWidth());
+ if (diff < -10 || diff > 10) {
+ setPageMode(m_pageMode);
+ refreshSegment(0, 0, 0);
+ }
+
+ } else if (m_pageMode == LinedStaff::LinearMode) {
+ // resize canvas again if the window height has changed significantly
+ if (getCanvasView() && getCanvasView()->canvas()) {
+ int diff = int(getPageHeight() - getCanvasView()->canvas()->height());
+ if (diff > 10) {
+ readjustCanvasSize();
+ }
+ }
+ }
+
+ // check for staff name changes
+ for (unsigned int i = 0; i < m_staffs.size(); ++i) {
+ if (!m_staffs[i]->isStaffNameUpToDate()) {
+ refreshSegment(0);
+ break;
+ }
+ }
+
+ m_inPaintEvent = false;
+
+ EditView::paintEvent(e);
+
+ m_inPaintEvent = false;
+
+ // now deal with any backlog of insertable notes that appeared
+ // during paint (because it's not safe to modify a segment from
+ // within a sub-event-loop in a processEvents call from a paint)
+ if (!m_pendingInsertableNotes.empty()) {
+ std::vector<std::pair<int, int> > notes = m_pendingInsertableNotes;
+ m_pendingInsertableNotes.clear();
+ for (unsigned int i = 0; i < notes.size(); ++i) {
+ slotInsertableNoteEventReceived(notes[i].first, notes[i].second, true);
+ }
+ }
+
+ slotSetOperationNameAndStatus(i18n(" Ready."));
+}
+
+bool NotationView::applyLayout(int staffNo, timeT startTime, timeT endTime)
+{
+ slotSetOperationNameAndStatus(i18n("Laying out score..."));
+ ProgressDialog::processEvents();
+
+ m_hlayout->setStaffCount(m_staffs.size());
+
+ Profiler profiler("NotationView::applyLayout");
+ unsigned int i;
+
+ for (i = 0; i < m_staffs.size(); ++i) {
+
+ if (staffNo >= 0 && (int)i != staffNo)
+ continue;
+
+ slotSetOperationNameAndStatus(i18n("Laying out staff %1...").arg(i + 1));
+ ProgressDialog::processEvents();
+
+ m_hlayout->resetStaff(*m_staffs[i], startTime, endTime);
+ m_vlayout->resetStaff(*m_staffs[i], startTime, endTime);
+ m_hlayout->scanStaff(*m_staffs[i], startTime, endTime);
+ m_vlayout->scanStaff(*m_staffs[i], startTime, endTime);
+ }
+
+ slotSetOperationNameAndStatus(i18n("Reconciling staffs..."));
+ ProgressDialog::processEvents();
+
+ m_hlayout->finishLayout(startTime, endTime);
+ m_vlayout->finishLayout(startTime, endTime);
+
+ // find the last finishing staff for future use
+
+ timeT lastFinishingStaffEndTime = 0;
+ bool haveEndTime = false;
+ m_lastFinishingStaff = -1;
+
+ timeT firstStartingStaffStartTime = 0;
+ bool haveStartTime = false;
+ int firstStartingStaff = -1;
+
+ for (i = 0; i < m_staffs.size(); ++i) {
+
+ timeT thisStartTime = m_staffs[i]->getSegment().getStartTime();
+ if (thisStartTime < firstStartingStaffStartTime || !haveStartTime) {
+ firstStartingStaffStartTime = thisStartTime;
+ haveStartTime = true;
+ firstStartingStaff = i;
+ }
+
+ timeT thisEndTime = m_staffs[i]->getSegment().getEndTime();
+ if (thisEndTime > lastFinishingStaffEndTime || !haveEndTime) {
+ lastFinishingStaffEndTime = thisEndTime;
+ haveEndTime = true;
+ m_lastFinishingStaff = i;
+ }
+ }
+
+ readjustCanvasSize();
+ if (m_topStandardRuler) {
+ m_topStandardRuler->update();
+ }
+ if (m_bottomStandardRuler) {
+ m_bottomStandardRuler->update();
+ }
+ if (m_tempoRuler && m_tempoRuler->isVisible()) {
+ m_tempoRuler->update();
+ }
+ if (m_rawNoteRuler && m_rawNoteRuler->isVisible()) {
+ m_rawNoteRuler->update();
+ }
+
+ return true;
+}
+
+void NotationView::setCurrentSelectedNote(const char *pixmapName,
+ bool rest, Note::Type n, int dots)
+{
+ NoteInserter* inserter = 0;
+
+ if (rest)
+ inserter = dynamic_cast<NoteInserter*>(m_toolBox->getTool(RestInserter::ToolName));
+ else
+ inserter = dynamic_cast<NoteInserter*>(m_toolBox->getTool(NoteInserter::ToolName));
+
+ inserter->slotSetNote(n);
+ inserter->slotSetDots(dots);
+
+ setTool(inserter);
+
+ m_currentNotePixmap->setPixmap
+ (NotePixmapFactory::toQPixmap
+ (NotePixmapFactory::makeToolbarPixmap(pixmapName, true)));
+
+ emit changeCurrentNote(rest, n);
+}
+
+void NotationView::setCurrentSelectedNote(const NoteActionData &noteAction)
+{
+ setCurrentSelectedNote(noteAction.pixmapName,
+ noteAction.rest,
+ noteAction.noteType,
+ noteAction.dots);
+}
+
+void NotationView::setCurrentSelection(EventSelection* s, bool preview,
+ bool redrawNow)
+{
+ //!!! rather too much here shared with matrixview -- could much of
+ // this be in editview?
+
+ if (m_currentEventSelection == s)
+ return ;
+ NOTATION_DEBUG << "XXX " << endl;
+
+ EventSelection *oldSelection = m_currentEventSelection;
+ m_currentEventSelection = s;
+
+ // positionElements is overkill here, but we hope it's not too
+ // much overkill (if that's not a contradiction)
+
+ timeT startA, endA, startB, endB;
+
+ if (oldSelection) {
+ startA = oldSelection->getStartTime();
+ endA = oldSelection->getEndTime();
+ startB = s ? s->getStartTime() : startA;
+ endB = s ? s->getEndTime() : endA;
+ } else {
+ // we know they can't both be null -- first thing we tested above
+ startA = startB = s->getStartTime();
+ endA = endB = s->getEndTime();
+ }
+
+ // refreshSegment takes start==end to mean refresh everything
+ if (startA == endA)
+ ++endA;
+ if (startB == endB)
+ ++endB;
+
+ bool updateRequired = true;
+
+ // play previews if appropriate -- also permits an optimisation
+ // for the case where the selection is unchanged (quite likely
+ // when sweeping)
+
+ if (s && preview) {
+
+ bool foundNewEvent = false;
+
+ for (EventSelection::eventcontainer::iterator i =
+ s->getSegmentEvents().begin();
+ i != s->getSegmentEvents().end(); ++i) {
+
+ if (oldSelection && oldSelection->getSegment() == s->getSegment()
+ && oldSelection->contains(*i))
+ continue;
+
+ foundNewEvent = true;
+
+ long pitch;
+ if (!(*i)->get
+ <Int>(BaseProperties::PITCH,
+ pitch)) continue;
+
+ long velocity = -1;
+ (void)(*i)->get
+ <Int>(BaseProperties::VELOCITY,
+ velocity);
+
+ if (!((*i)->has(BaseProperties::TIED_BACKWARD) &&
+ (*i)->get
+ <Bool>
+ (BaseProperties::TIED_BACKWARD)))
+ playNote(s->getSegment(), pitch, velocity);
+ }
+
+ if (!foundNewEvent) {
+ if (oldSelection &&
+ oldSelection->getSegment() == s->getSegment() &&
+ oldSelection->getSegmentEvents().size() ==
+ s->getSegmentEvents().size())
+ updateRequired = false;
+ }
+ }
+
+ if (updateRequired) {
+
+ if (!s || !oldSelection ||
+ (endA >= startB && endB >= startA &&
+ oldSelection->getSegment() == s->getSegment())) {
+
+ // the regions overlap: use their union and just do one refresh
+
+ Segment &segment(s ? s->getSegment() :
+ oldSelection->getSegment());
+
+ if (redrawNow) {
+ // recolour the events now
+ getLinedStaff(segment)->positionElements(std::min(startA, startB),
+ std::max(endA, endB));
+ } else {
+ // mark refresh status and then request a repaint
+ segment.getRefreshStatus
+ (m_segmentsRefreshStatusIds
+ [getLinedStaff(segment)->getId()]).
+ push(std::min(startA, startB), std::max(endA, endB));
+ }
+
+ } else {
+ // do two refreshes, one for each -- here we know neither is null
+
+ if (redrawNow) {
+ // recolour the events now
+ getLinedStaff(oldSelection->getSegment())->positionElements(startA,
+ endA);
+
+ getLinedStaff(s->getSegment())->positionElements(startB, endB);
+ } else {
+ // mark refresh status and then request a repaint
+
+ oldSelection->getSegment().getRefreshStatus
+ (m_segmentsRefreshStatusIds
+ [getLinedStaff(oldSelection->getSegment())->getId()]).
+ push(startA, endA);
+
+ s->getSegment().getRefreshStatus
+ (m_segmentsRefreshStatusIds
+ [getLinedStaff(s->getSegment())->getId()]).
+ push(startB, endB);
+ }
+ }
+
+ if (s) {
+ // make the staff containing the selection current
+ int staffId = getLinedStaff(s->getSegment())->getId();
+ if (staffId != m_currentStaff)
+ slotSetCurrentStaff(staffId);
+ }
+ }
+
+ delete oldSelection;
+
+ statusBar()->changeItem(KTmpStatusMsg::getDefaultMsg(),
+ KTmpStatusMsg::getDefaultId());
+
+ if (s) {
+ int eventsSelected = s->getSegmentEvents().size();
+ m_selectionCounter->setText
+ (i18n(" 1 event selected ",
+ " %n events selected ", eventsSelected));
+ } else {
+ m_selectionCounter->setText(i18n(" No selection "));
+ }
+ m_selectionCounter->update();
+
+ setMenuStates();
+
+ if (redrawNow)
+ updateView();
+ else
+ update();
+
+ NOTATION_DEBUG << "XXX " << endl;
+}
+
+void NotationView::setSingleSelectedEvent(int staffNo, Event *event,
+ bool preview, bool redrawNow)
+{
+ setSingleSelectedEvent(getStaff(staffNo)->getSegment(), event,
+ preview, redrawNow);
+}
+
+void NotationView::setSingleSelectedEvent(Segment &segment, Event *event,
+ bool preview, bool redrawNow)
+{
+ EventSelection *selection = new EventSelection(segment);
+ selection->addEvent(event);
+ setCurrentSelection(selection, preview, redrawNow);
+}
+
+bool NotationView::canPreviewAnotherNote()
+{
+ static time_t lastCutOff = 0;
+ static int sinceLastCutOff = 0;
+
+ time_t now = time(0);
+ ++sinceLastCutOff;
+
+ if ((now - lastCutOff) > 0) {
+ sinceLastCutOff = 0;
+ lastCutOff = now;
+ NOTATION_DEBUG << "NotationView::canPreviewAnotherNote: reset" << endl;
+ } else {
+ if (sinceLastCutOff >= 20) {
+ // don't permit more than 20 notes per second or so, to
+ // avoid gungeing up the sound drivers
+ NOTATION_DEBUG << "Rejecting preview (too busy)" << endl;
+ return false;
+ }
+ NOTATION_DEBUG << "NotationView::canPreviewAnotherNote: ok" << endl;
+ }
+
+ return true;
+}
+
+void NotationView::playNote(Segment &s, int pitch, int velocity)
+{
+ Composition &comp = getDocument()->getComposition();
+ Studio &studio = getDocument()->getStudio();
+ Track *track = comp.getTrackById(s.getTrack());
+
+ Instrument *ins =
+ studio.getInstrumentById(track->getInstrument());
+
+ // check for null instrument
+ //
+ if (ins == 0)
+ return ;
+
+ if (!canPreviewAnotherNote())
+ return ;
+
+ if (velocity < 0)
+ velocity = MidiMaxValue;
+
+ MappedEvent mE(ins->getId(),
+ MappedEvent::MidiNoteOneShot,
+ pitch + s.getTranspose(),
+ velocity,
+ RealTime::zeroTime,
+ RealTime(0, 250000000),
+ RealTime::zeroTime);
+
+ StudioControl::sendMappedEvent(mE);
+}
+
+void NotationView::showPreviewNote(int staffNo, double layoutX,
+ int pitch, int height,
+ const Note &note, bool grace,
+ int velocity)
+{
+ m_staffs[staffNo]->showPreviewNote(layoutX, height, note, grace);
+ playNote(m_staffs[staffNo]->getSegment(), pitch, velocity);
+}
+
+void NotationView::clearPreviewNote()
+{
+ for (unsigned int i = 0; i < m_staffs.size(); ++i) {
+ m_staffs[i]->clearPreviewNote();
+ }
+}
+
+void NotationView::setNotePixmapFactory(NotePixmapFactory* f)
+{
+ delete m_notePixmapFactory;
+ m_notePixmapFactory = f;
+ if (m_hlayout)
+ m_hlayout->setNotePixmapFactory(m_notePixmapFactory);
+ if (m_vlayout)
+ m_vlayout->setNotePixmapFactory(m_notePixmapFactory);
+}
+
+Segment *
+NotationView::getCurrentSegment()
+{
+ Staff *staff = getCurrentStaff();
+ return (staff ? &staff->getSegment() : 0);
+}
+
+bool
+NotationView::hasSegment(Segment *segment)
+{
+ for (unsigned int i = 0; i < m_segments.size(); ++i) {
+ if (segment == m_segments[i]) return true;
+ }
+ return false;
+}
+
+
+LinedStaff *
+NotationView::getCurrentLinedStaff()
+{
+ return getLinedStaff(m_currentStaff);
+}
+
+LinedStaff *
+NotationView::getStaffAbove()
+{
+ if (m_staffs.size() < 2) return 0;
+
+ Composition *composition =
+ m_staffs[m_currentStaff]->getSegment().getComposition();
+
+ Track *track = composition->
+ getTrackById(m_staffs[m_currentStaff]->getSegment().getTrack());
+ if (!track) return 0;
+
+ int position = track->getPosition();
+ Track *newTrack = 0;
+
+ while ((newTrack = composition->getTrackByPosition(--position))) {
+ for (unsigned int i = 0; i < m_staffs.size(); ++i) {
+ if (m_staffs[i]->getSegment().getTrack() == newTrack->getId()) {
+ return m_staffs[i];
+ }
+ }
+ }
+
+ return 0;
+}
+
+LinedStaff *
+NotationView::getStaffBelow()
+{
+ if (m_staffs.size() < 2) return 0;
+
+ Composition *composition =
+ m_staffs[m_currentStaff]->getSegment().getComposition();
+
+ Track *track = composition->
+ getTrackById(m_staffs[m_currentStaff]->getSegment().getTrack());
+ if (!track) return 0;
+
+ int position = track->getPosition();
+ Track *newTrack = 0;
+
+ while ((newTrack = composition->getTrackByPosition(++position))) {
+ for (unsigned int i = 0; i < m_staffs.size(); ++i) {
+ if (m_staffs[i]->getSegment().getTrack() == newTrack->getId()) {
+ return m_staffs[i];
+ }
+ }
+ }
+
+ return 0;
+}
+
+timeT
+NotationView::getInsertionTime()
+{
+ return m_insertionTime;
+}
+
+timeT
+NotationView::getInsertionTime(Clef &clef,
+ Rosegarden::Key &key)
+{
+ // This fuss is solely to recover the clef and key: we already
+ // set m_insertionTime to the right value when we first placed
+ // the insert cursor. We could get clef and key directly from
+ // the segment but the staff has a more efficient lookup
+
+ LinedStaff *staff = m_staffs[m_currentStaff];
+ double layoutX = staff->getLayoutXOfInsertCursor();
+ if (layoutX < 0) layoutX = 0;
+ Event *clefEvt = 0, *keyEvt = 0;
+ (void)staff->getElementUnderLayoutX(layoutX, clefEvt, keyEvt);
+
+ if (clefEvt) clef = Clef(*clefEvt);
+ else clef = Clef();
+
+ if (keyEvt) key = Rosegarden::Key(*keyEvt);
+ else key = Rosegarden::Key();
+
+ return m_insertionTime;
+}
+
+LinedStaff*
+NotationView::getStaffForCanvasCoords(int x, int y) const
+{
+ // (i) Do not change staff, if mouse was clicked within the current staff.
+ LinedStaff *s = m_staffs[m_currentStaff];
+ if (s->containsCanvasCoords(x, y)) {
+ LinedStaff::LinedStaffCoords coords =
+ s->getLayoutCoordsForCanvasCoords(x, y);
+
+ timeT t = m_hlayout->getTimeForX(coords.first);
+ // In order to find the correct starting and ending bar of the segment,
+ // make infinitesimal shifts (+1 and -1) towards its center.
+ timeT t0 = getDocument()->getComposition().getBarStartForTime(m_staffs[m_currentStaff]->getSegment().getStartTime()+1);
+ timeT t1 = getDocument()->getComposition().getBarEndForTime(m_staffs[m_currentStaff]->getSegment().getEndTime()-1);
+ if (t >= t0 && t < t1) {
+ return m_staffs[m_currentStaff];
+ }
+ }
+ // (ii) Find staff under cursor, if clicked outside the current staff.
+ for (unsigned int i = 0; i < m_staffs.size(); ++i) {
+
+ LinedStaff *s = m_staffs[i];
+
+ if (s->containsCanvasCoords(x, y)) {
+
+ LinedStaff::LinedStaffCoords coords =
+ s->getLayoutCoordsForCanvasCoords(x, y);
+
+ timeT t = m_hlayout->getTimeForX(coords.first);
+ // In order to find the correct starting and ending bar of the segment,
+ // make infinitesimal shifts (+1 and -1) towards its center.
+ timeT t0 = getDocument()->getComposition().getBarStartForTime(m_staffs[i]->getSegment().getStartTime()+1);
+ timeT t1 = getDocument()->getComposition().getBarEndForTime(m_staffs[i]->getSegment().getEndTime()-1);
+ if (t >= t0 && t < t1) {
+ return m_staffs[i];
+ }
+ }
+ }
+
+ return 0;
+}
+
+void NotationView::updateView()
+{
+ slotCheckRendered
+ (getCanvasView()->contentsX(),
+ getCanvasView()->contentsX() + getCanvasView()->visibleWidth());
+ canvas()->update();
+}
+
+void NotationView::print(bool previewOnly)
+{
+ if (m_staffs.size() == 0) {
+ KMessageBox::error(0, "Nothing to print");
+ return ;
+ }
+
+ Profiler profiler("NotationView::print");
+
+ // We need to be in multi-page mode at this point
+
+ int pageWidth = getPageWidth();
+ int pageHeight = getPageHeight();
+ int leftMargin = 0, topMargin = 0;
+ getPageMargins(leftMargin, topMargin);
+ int maxPageCount = 1;
+
+ for (unsigned int i = 0; i < m_staffs.size(); ++i) {
+ int pageCount = m_staffs[i]->getPageCount();
+ NOTATION_DEBUG << "NotationView::print(): staff " << i << " reports " << pageCount << " pages " << endl;
+ if (pageCount > maxPageCount)
+ maxPageCount = pageCount;
+ }
+
+ KPrinter printer(true, QPrinter::HighResolution);
+
+ printer.setPageSelection(KPrinter::ApplicationSide);
+ printer.setMinMax(1, maxPageCount + 1);
+
+ if (previewOnly)
+ printer.setPreviewOnly(true);
+ else if (!printer.setup((QWidget *)parent()))
+ return ;
+
+ QPaintDeviceMetrics pdm(&printer);
+ QPainter printpainter(&printer);
+
+ // Ideally we should aim to retain the aspect ratio and to move the
+ // staffs so as to be centred after scaling. But because we haven't
+ // got around to the latter, let's lose the former too and just
+ // expand to fit.
+
+ // Retain aspect ratio when scaling
+ double ratioX = (double)pdm.width() / (double)(pageWidth - leftMargin * 2),
+ ratioY = (double)pdm.height() / (double)(pageHeight - topMargin * 2);
+ double ratio = std::min(ratioX, ratioY);
+ printpainter.scale(ratio, ratio);
+
+ // printpainter.scale((double)pdm.width() / (double)(pageWidth - leftMargin*2),
+ // (double)pdm.height() / (double)(pageHeight - topMargin*2));
+ printpainter.translate( -leftMargin, -topMargin);
+
+ QValueList<int> pages = printer.pageList();
+
+ for (QValueList<int>::Iterator pli = pages.begin();
+ pli != pages.end(); ) { // incremented just below
+
+ int page = *pli - 1;
+ ++pli;
+ if (page < 0 || page >= maxPageCount)
+ continue;
+
+ NOTATION_DEBUG << "Printing page " << page << endl;
+
+ QRect pageRect(m_leftGutter + leftMargin + pageWidth * page,
+ topMargin,
+ pageWidth - leftMargin,
+ pageHeight - topMargin);
+
+ for (size_t i = 0; i < m_staffs.size(); ++i) {
+
+ LinedStaff *staff = m_staffs[i];
+
+ LinedStaff::LinedStaffCoords cc0 = staff->getLayoutCoordsForCanvasCoords
+ (pageRect.x(), pageRect.y());
+
+ LinedStaff::LinedStaffCoords cc1 = staff->getLayoutCoordsForCanvasCoords
+ (pageRect.x() + pageRect.width(), pageRect.y() + pageRect.height());
+
+ timeT t0 = m_hlayout->getTimeForX(cc0.first);
+ timeT t1 = m_hlayout->getTimeForX(cc1.first);
+
+ m_staffs[i]->setPrintPainter(&printpainter);
+ m_staffs[i]->checkRendered(t0, t1);
+ }
+
+ // Supplying doublebuffer==true to this method appears to
+ // slow down printing considerably but without it we get
+ // all sorts of horrible artifacts (possibly related to
+ // mishandling of pixmap masks?) in qt-3.0. Let's permit
+ // it as a "hidden" option.
+
+ m_config->setGroup(NotationViewConfigGroup);
+
+ NOTATION_DEBUG << "NotationView::print: calling QCanvas::drawArea" << endl;
+
+ {
+ Profiler profiler("NotationView::print(QCanvas::drawArea)");
+
+ if (m_config->readBoolEntry("forcedoublebufferprinting", false)) {
+ getCanvasView()->canvas()->drawArea(pageRect, &printpainter, true);
+ } else {
+#if QT_VERSION >= 0x030100
+ getCanvasView()->canvas()->drawArea(pageRect, &printpainter, false);
+#else
+
+ getCanvasView()->canvas()->drawArea(pageRect, &printpainter, true);
+#endif
+
+ }
+
+ }
+
+ NOTATION_DEBUG << "NotationView::print: QCanvas::drawArea done" << endl;
+
+ for (size_t i = 0; i < m_staffs.size(); ++i) {
+
+ LinedStaff *staff = m_staffs[i];
+
+ LinedStaff::LinedStaffCoords cc0 = staff->getLayoutCoordsForCanvasCoords
+ (pageRect.x(), pageRect.y());
+
+ LinedStaff::LinedStaffCoords cc1 = staff->getLayoutCoordsForCanvasCoords
+ (pageRect.x() + pageRect.width(), pageRect.y() + pageRect.height());
+
+ timeT t0 = m_hlayout->getTimeForX(cc0.first);
+ timeT t1 = m_hlayout->getTimeForX(cc1.first);
+
+ m_staffs[i]->renderPrintable(t0, t1);
+ }
+
+ printpainter.translate( -pageWidth, 0);
+
+ if (pli != pages.end() && *pli - 1 < maxPageCount)
+ printer.newPage();
+
+ for (size_t i = 0; i < m_staffs.size(); ++i) {
+ m_staffs[i]->markChanged(); // recover any memory used for this page
+ PixmapArrayGC::deleteAll();
+ }
+ }
+
+ for (size_t i = 0; i < m_staffs.size(); ++i) {
+ for (Segment::iterator j = m_staffs[i]->getSegment().begin();
+ j != m_staffs[i]->getSegment().end(); ++j) {
+ removeViewLocalProperties(*j);
+ }
+ delete m_staffs[i];
+ }
+ m_staffs.clear();
+
+ printpainter.end();
+
+ Profiles::getInstance()->dump();
+}
+
+void
+NotationView::updateThumbnails(bool complete)
+{
+ if (m_pageMode != LinedStaff::MultiPageMode)
+ return ;
+
+ int pageWidth = getPageWidth();
+ int pageHeight = getPageHeight();
+ int leftMargin = 0, topMargin = 0;
+ getPageMargins(leftMargin, topMargin);
+ int maxPageCount = 1;
+
+ for (unsigned int i = 0; i < m_staffs.size(); ++i) {
+ int pageCount = m_staffs[i]->getPageCount();
+ if (pageCount > maxPageCount)
+ maxPageCount = pageCount;
+ }
+
+ int thumbScale = 20;
+ QPixmap thumbnail(canvas()->width() / thumbScale,
+ canvas()->height() / thumbScale);
+ thumbnail.fill(Qt::white);
+ QPainter thumbPainter(&thumbnail);
+
+ if (complete) {
+
+ thumbPainter.scale(1.0 / double(thumbScale), 1.0 / double(thumbScale));
+ thumbPainter.setPen(Qt::black);
+ thumbPainter.setBrush(Qt::white);
+
+ /*
+ QCanvas *canvas = getCanvasView()->canvas();
+ canvas->drawArea(QRect(0, 0, canvas->width(), canvas->height()),
+ &thumbPainter, false);
+ */
+ // hide small texts, as we get a crash in Xft when trying to
+ // render them at this scale
+ if (m_title)
+ m_title->hide();
+ if (m_subtitle)
+ m_subtitle->hide();
+ if (m_composer)
+ m_composer->hide();
+ if (m_copyright)
+ m_copyright->hide();
+
+ for (size_t page = 0; page < static_cast<size_t>(maxPageCount); ++page) {
+
+ bool havePageNumber = ((m_pageNumbers.size() > page) &&
+ (m_pageNumbers[page] != 0));
+ if (havePageNumber)
+ m_pageNumbers[page]->hide();
+
+ QRect pageRect(m_leftGutter + leftMargin * 2 + pageWidth * page,
+ topMargin * 2,
+ pageWidth - leftMargin*3,
+ pageHeight - topMargin*3);
+
+ QCanvas *canvas = getCanvasView()->canvas();
+ canvas->drawArea(pageRect, &thumbPainter, false);
+
+ if (havePageNumber)
+ m_pageNumbers[page]->show();
+ }
+
+ if (m_title)
+ m_title->show();
+ if (m_subtitle)
+ m_subtitle->show();
+ if (m_composer)
+ m_composer->show();
+ if (m_copyright)
+ m_copyright->show();
+
+ } else {
+
+ thumbPainter.setPen(Qt::black);
+
+ for (int page = 0; page < maxPageCount; ++page) {
+
+ int x = m_leftGutter + pageWidth * page + leftMargin / 4;
+ int y = 20;
+ int w = pageWidth - leftMargin / 2;
+ int h = pageHeight;
+
+ QString str = QString("%1").arg(page + 1);
+
+ thumbPainter.drawRect(x / thumbScale, y / thumbScale,
+ w / thumbScale, h / thumbScale);
+
+ int tx = (x + w / 2) / thumbScale, ty = (y + h / 2) / thumbScale;
+ tx -= thumbPainter.fontMetrics().width(str) / 2;
+ thumbPainter.drawText(tx, ty, str);
+ }
+ }
+
+ thumbPainter.end();
+ if (m_pannerDialog)
+ m_pannerDialog->scrollbox()->setThumbnail(thumbnail);
+}
+
+void NotationView::refreshSegment(Segment *segment,
+ timeT startTime, timeT endTime)
+{
+ NOTATION_DEBUG << "*** " << endl;
+
+ if (m_inhibitRefresh)
+ return ;
+ NOTATION_DEBUG << "NotationView::refreshSegment(" << segment << "," << startTime << "," << endTime << ")" << endl;
+ Profiler foo("NotationView::refreshSegment");
+
+ emit usedSelection();
+
+ if (segment) {
+ LinedStaff *staff = getLinedStaff(*segment);
+ if (staff)
+ applyLayout(staff->getId(), startTime, endTime);
+ } else {
+ applyLayout( -1, startTime, endTime);
+ }
+
+ for (unsigned int i = 0; i < m_staffs.size(); ++i) {
+
+ Segment *ssegment = &m_staffs[i]->getSegment();
+ bool thisStaff = (ssegment == segment || segment == 0);
+ m_staffs[i]->markChanged(startTime, endTime, !thisStaff);
+ }
+
+ PixmapArrayGC::deleteAll();
+
+ statusBar()->changeItem(KTmpStatusMsg::getDefaultMsg(),
+ KTmpStatusMsg::getDefaultId());
+
+ Event::dumpStats(std::cerr);
+ if (m_deferredCursorMove == NoCursorMoveNeeded) {
+ slotSetInsertCursorPosition(getInsertionTime(), false, false);
+ } else {
+ doDeferredCursorMove();
+ }
+ slotSetPointerPosition(getDocument()->getComposition().getPosition(), false);
+
+ if (m_currentEventSelection &&
+ m_currentEventSelection->getSegmentEvents().size() == 0) {
+ delete m_currentEventSelection;
+ m_currentEventSelection = 0;
+ //!!!??? was that the right thing to do?
+ }
+
+ setMenuStates();
+ slotSetOperationNameAndStatus(i18n(" Ready."));
+ NOTATION_DEBUG << "*** " << endl;
+}
+
+void NotationView::setMenuStates()
+{
+ // 1. set selection-related states
+
+ // Clear states first, then enter only those ones that apply
+ // (so as to avoid ever clearing one after entering another, in
+ // case the two overlap at all)
+ stateChanged("have_selection", KXMLGUIClient::StateReverse);
+ stateChanged("have_notes_in_selection", KXMLGUIClient::StateReverse);
+ stateChanged("have_rests_in_selection", KXMLGUIClient::StateReverse);
+
+ if (m_currentEventSelection) {
+
+ NOTATION_DEBUG << "NotationView::setMenuStates: Have selection; it's " << m_currentEventSelection << " covering range from " << m_currentEventSelection->getStartTime() << " to " << m_currentEventSelection->getEndTime() << " (" << m_currentEventSelection->getSegmentEvents().size() << " events)" << endl;
+
+ stateChanged("have_selection", KXMLGUIClient::StateNoReverse);
+ if (m_currentEventSelection->contains
+ (Note::EventType)) {
+ stateChanged("have_notes_in_selection",
+ KXMLGUIClient::StateNoReverse);
+ }
+ if (m_currentEventSelection->contains
+ (Note::EventRestType)) {
+ stateChanged("have_rests_in_selection",
+ KXMLGUIClient::StateNoReverse);
+ }
+ }
+
+ // 2. set inserter-related states
+
+ // #1372863 -- RestInserter is a subclass of NoteInserter, so we
+ // need to test dynamic_cast<RestInserter *> before
+ // dynamic_cast<NoteInserter *> (which will succeed for both)
+
+ if (dynamic_cast<RestInserter *>(m_tool)) {
+ NOTATION_DEBUG << "Have rest inserter " << endl;
+ stateChanged("note_insert_tool_current", StateReverse);
+ stateChanged("rest_insert_tool_current", StateNoReverse);
+ } else if (dynamic_cast<NoteInserter *>(m_tool)) {
+ NOTATION_DEBUG << "Have note inserter " << endl;
+ stateChanged("note_insert_tool_current", StateNoReverse);
+ stateChanged("rest_insert_tool_current", StateReverse);
+ } else {
+ NOTATION_DEBUG << "Have neither inserter " << endl;
+ stateChanged("note_insert_tool_current", StateReverse);
+ stateChanged("rest_insert_tool_current", StateReverse);
+ }
+}
+
+#define UPDATE_PROGRESS(n) \
+ progressCount += (n); \
+ if (progressTotal > 0) { \
+ emit setProgress(progressCount * 100 / progressTotal); \
+ ProgressDialog::processEvents(); \
+ }
+
+void NotationView::readjustCanvasSize()
+{
+ Profiler profiler("NotationView::readjustCanvasSize");
+
+ double maxWidth = 0.0;
+ int maxHeight = 0;
+
+ slotSetOperationNameAndStatus(i18n("Sizing and allocating canvas..."));
+ ProgressDialog::processEvents();
+
+ int progressTotal = m_staffs.size() + 2;
+ int progressCount = 0;
+
+ for (unsigned int i = 0; i < m_staffs.size(); ++i) {
+
+ LinedStaff &staff = *m_staffs[i];
+
+ staff.sizeStaff(*m_hlayout);
+ UPDATE_PROGRESS(1);
+
+ if (staff.getTotalWidth() + staff.getX() > maxWidth) {
+ maxWidth = staff.getTotalWidth() + staff.getX() + 1;
+ }
+
+ if (staff.getTotalHeight() + staff.getY() > maxHeight) {
+ maxHeight = staff.getTotalHeight() + staff.getY() + 1;
+ }
+ }
+
+ int topMargin = 0, leftMargin = 0;
+ getPageMargins(leftMargin, topMargin);
+
+ int pageWidth = getPageWidth();
+ int pageHeight = getPageHeight();
+
+ NOTATION_DEBUG << "NotationView::readjustCanvasSize: maxHeight is "
+ << maxHeight << ", page height is " << pageHeight << endl
+ << " - maxWidth is " << maxWidth << ", page width is " << pageWidth << endl;
+
+
+ if (m_pageMode == LinedStaff::LinearMode) {
+ maxWidth = ((maxWidth / pageWidth) + 1) * pageWidth;
+ if (maxHeight < pageHeight)
+ maxHeight = pageHeight;
+ } else {
+ if (maxWidth < pageWidth)
+ maxWidth = pageWidth;
+ if (maxHeight < pageHeight + topMargin*2)
+ maxHeight = pageHeight + topMargin * 2;
+ }
+
+ // now get the EditView to do the biz
+ readjustViewSize(QSize(int(maxWidth), maxHeight), true);
+
+ UPDATE_PROGRESS(2);
+
+ if (m_pannerDialog) {
+
+ if (m_pageMode != LinedStaff::MultiPageMode) {
+ m_pannerDialog->hide();
+
+ } else {
+
+ m_pannerDialog->show();
+
+ m_pannerDialog->setPageSize
+ (QSize(canvas()->width(),
+ canvas()->height()));
+
+ m_pannerDialog->scrollbox()->setViewSize
+ (QSize(getCanvasView()->width(),
+ getCanvasView()->height()));
+ }
+ }
+
+ // Give a correct vertical alignment to track headers
+ if ((m_pageMode == LinedStaff::LinearMode) && m_showHeadersGroup) {
+ m_headersGroupView->setContentsPos(0, getCanvasView()->contentsY());
+ }
+}
+
+void NotationView::slotNoteAction()
+{
+ const QObject* sigSender = sender();
+
+ NoteActionDataMap::Iterator noteAct =
+ m_noteActionDataMap->find(sigSender->name());
+
+ if (noteAct != m_noteActionDataMap->end()) {
+ m_lastNoteAction = sigSender->name();
+ setCurrentSelectedNote(**noteAct);
+ setMenuStates();
+ } else {
+ std::cerr << "NotationView::slotNoteAction() : couldn't find NoteActionData named '"
+ << sigSender->name() << "'\n";
+ }
+}
+
+void NotationView::slotLastNoteAction()
+{
+ KAction *action = actionCollection()->action(m_lastNoteAction);
+ if (!action)
+ action = actionCollection()->action("crotchet");
+
+ if (action) {
+ action->activate();
+ } else {
+ std::cerr << "NotationView::slotNoteAction() : couldn't find action named '"
+ << m_lastNoteAction << "' or 'crotchet'\n";
+ }
+}
+
+void NotationView::slotAddMark()
+{
+ const QObject *s = sender();
+ if (!m_currentEventSelection)
+ return ;
+
+ MarkActionDataMap::Iterator i = m_markActionDataMap->find(s->name());
+
+ if (i != m_markActionDataMap->end()) {
+ addCommandToHistory(new AddMarkCommand
+ ((**i).mark, *m_currentEventSelection));
+ }
+}
+
+void NotationView::slotNoteChangeAction()
+{
+ const QObject* sigSender = sender();
+
+ NoteChangeActionDataMap::Iterator noteAct =
+ m_noteChangeActionDataMap->find(sigSender->name());
+
+ if (noteAct != m_noteChangeActionDataMap->end()) {
+ slotSetNoteDurations((**noteAct).noteType, (**noteAct).notationOnly);
+ } else {
+ std::cerr << "NotationView::slotNoteChangeAction() : couldn't find NoteChangeAction named '"
+ << sigSender->name() << "'\n";
+ }
+}
+
+void NotationView::initActionDataMaps()
+{
+ static bool called = false;
+ static int keys[] =
+ { Key_0, Key_3, Key_6, Key_8, Key_4, Key_2, Key_1, Key_5 };
+
+ if (called)
+ return ;
+ called = true;
+
+ m_noteActionDataMap = new NoteActionDataMap;
+
+ for (int rest = 0; rest < 2; ++rest) {
+ for (int dots = 0; dots < 2; ++dots) {
+ for (int type = Note::Longest; type >= Note::Shortest; --type) {
+ if (dots && (type == Note::Longest))
+ continue;
+
+ QString refName
+ (NotationStrings::getReferenceName(Note(type, dots), rest == 1));
+
+ QString shortName(refName);
+ shortName.replace(QRegExp("-"), "_");
+
+ QString titleName
+ (NotationStrings::getNoteName(Note(type, dots)));
+
+ titleName = titleName.left(1).upper() +
+ titleName.right(titleName.length() - 1);
+
+ if (rest) {
+ titleName.replace(QRegExp(i18n("note")), i18n("rest"));
+ }
+
+ int keycode = keys[type - Note::Shortest];
+ if (dots) // keycode += CTRL; -- used below for note change action
+ keycode = 0;
+ if (rest) // keycode += SHIFT; -- can't do shift+numbers
+ keycode = 0;
+
+ m_noteActionDataMap->insert
+ (shortName, new NoteActionData
+ (titleName, shortName, refName, keycode,
+ rest > 0, type, dots));
+ }
+ }
+ }
+
+ m_noteChangeActionDataMap = new NoteChangeActionDataMap;
+
+ for (int notationOnly = 0; notationOnly <= 1; ++notationOnly) {
+ for (int type = Note::Longest; type >= Note::Shortest; --type) {
+
+ QString refName
+ (NotationStrings::getReferenceName(Note(type, 0), false));
+
+ QString shortName(QString("change_%1%2")
+ .arg(notationOnly ? "notation_" : "").arg(refName));
+ shortName.replace(QRegExp("-"), "_");
+
+ QString titleName
+ (NotationStrings::getNoteName(Note(type, 0)));
+
+ titleName = titleName.left(1).upper() +
+ titleName.right(titleName.length() - 1);
+
+ int keycode = keys[type - Note::Shortest];
+ keycode += CTRL;
+ if (notationOnly)
+ keycode += ALT;
+
+ m_noteChangeActionDataMap->insert
+ (shortName, new NoteChangeActionData
+ (titleName, shortName, refName, keycode,
+ notationOnly ? true : false, type));
+ }
+ }
+
+ m_markActionDataMap = new MarkActionDataMap;
+
+ std::vector<Mark> marks = Marks::getStandardMarks();
+ for (unsigned int i = 0; i < marks.size(); ++i) {
+
+ Mark mark = marks[i];
+ QString markName(strtoqstr(mark));
+ QString actionName = QString("add_%1").arg(markName);
+
+ m_markActionDataMap->insert
+ (actionName, new MarkActionData
+ (AddMarkCommand::getGlobalName(mark),
+ actionName, 0, mark));
+ }
+
+}
+
+void NotationView::setupProgress(KProgress* bar)
+{
+ if (bar) {
+ NOTATION_DEBUG << "NotationView::setupProgress(bar)\n";
+
+ connect(m_hlayout, SIGNAL(setProgress(int)),
+ bar, SLOT(setValue(int)));
+
+ connect(m_hlayout, SIGNAL(incrementProgress(int)),
+ bar, SLOT(advance(int)));
+
+ connect(this, SIGNAL(setProgress(int)),
+ bar, SLOT(setValue(int)));
+
+ connect(this, SIGNAL(incrementProgress(int)),
+ bar, SLOT(advance(int)));
+
+ for (unsigned int i = 0; i < m_staffs.size(); ++i) {
+ connect(m_staffs[i], SIGNAL(setProgress(int)),
+ bar, SLOT(setValue(int)));
+
+ connect(m_staffs[i], SIGNAL(incrementProgress(int)),
+ bar, SLOT(advance(int)));
+ }
+ }
+
+}
+
+void NotationView::setupProgress(ProgressDialog* dialog)
+{
+ NOTATION_DEBUG << "NotationView::setupProgress(dialog)" << endl;
+ disconnectProgress();
+
+ if (dialog) {
+ setupProgress(dialog->progressBar());
+
+ connect(dialog, SIGNAL(cancelClicked()),
+ m_hlayout, SLOT(slotCancel()));
+
+ for (unsigned int i = 0; i < m_staffs.size(); ++i) {
+ connect(m_staffs[i], SIGNAL(setOperationName(QString)),
+ this, SLOT(slotSetOperationNameAndStatus(QString)));
+
+ connect(dialog, SIGNAL(cancelClicked()),
+ m_staffs[i], SLOT(slotCancel()));
+ }
+
+ connect(this, SIGNAL(setOperationName(QString)),
+ dialog, SLOT(slotSetOperationName(QString)));
+ m_progressDisplayer = PROGRESS_DIALOG;
+ }
+
+}
+
+void NotationView::slotSetOperationNameAndStatus(QString name)
+{
+ emit setOperationName(name);
+ statusBar()->changeItem(QString(" %1").arg(name),
+ KTmpStatusMsg::getDefaultId());
+}
+
+void NotationView::disconnectProgress()
+{
+ NOTATION_DEBUG << "NotationView::disconnectProgress()" << endl;
+
+ m_hlayout->disconnect();
+ disconnect(SIGNAL(setProgress(int)));
+ disconnect(SIGNAL(incrementProgress(int)));
+ disconnect(SIGNAL(setOperationName(QString)));
+
+ for (unsigned int i = 0; i < m_staffs.size(); ++i) {
+ m_staffs[i]->disconnect();
+ }
+}
+
+void NotationView::setupDefaultProgress()
+{
+ if (m_progressDisplayer != PROGRESS_BAR) {
+ NOTATION_DEBUG << "NotationView::setupDefaultProgress()" << endl;
+ disconnectProgress();
+ setupProgress(m_progressBar);
+ m_progressDisplayer = PROGRESS_BAR;
+ }
+}
+
+void NotationView::updateViewCaption()
+{
+ if (m_segments.size() == 1) {
+
+ TrackId trackId = m_segments[0]->getTrack();
+ Track *track =
+ m_segments[0]->getComposition()->getTrackById(trackId);
+
+ int trackPosition = -1;
+ if (track)
+ trackPosition = track->getPosition();
+ // std::cout << std::endl << std::endl << std::endl << "DEBUG TITLE BAR: " << getDocument()->getTitle() << std::endl << std::endl << std::endl;
+ setCaption(i18n("%1 - Segment Track #%2 - Notation")
+ .arg(getDocument()->getTitle())
+ .arg(trackPosition + 1));
+
+ } else if (m_segments.size() == getDocument()->getComposition().getNbSegments()) {
+
+ setCaption(i18n("%1 - All Segments - Notation")
+ .arg(getDocument()->getTitle()));
+
+ } else {
+
+ setCaption(i18n("%1 - Segment - Notation", "%1 - %n Segments - Notation", m_segments.size())
+ .arg(getDocument()->getTitle()));
+
+ }
+}
+
+NotationView::NoteActionDataMap* NotationView::m_noteActionDataMap = 0;
+
+NotationView::NoteChangeActionDataMap* NotationView::m_noteChangeActionDataMap = 0;
+
+NotationView::MarkActionDataMap* NotationView::m_markActionDataMap = 0;
+
+
+/// SLOTS
+
+
+void
+NotationView::slotUpdateInsertModeStatus()
+{
+ QString tripletMessage = i18n("Triplet");
+ QString chordMessage = i18n("Chord");
+ QString graceMessage = i18n("Grace");
+ QString message;
+
+ if (isInTripletMode()) {
+ message = i18n("%1 %2").arg(message).arg(tripletMessage);
+ }
+
+ if (isInChordMode()) {
+ message = i18n("%1 %2").arg(message).arg(chordMessage);
+ }
+
+ if (isInGraceMode()) {
+ message = i18n("%1 %2").arg(message).arg(graceMessage);
+ }
+
+ m_insertModeLabel->setText(message);
+}
+
+void
+NotationView::slotUpdateAnnotationsStatus()
+{
+ if (!areAnnotationsVisible()) {
+ for (int i = 0; i < getStaffCount(); ++i) {
+ Segment &s = getStaff(i)->getSegment();
+ for (Segment::iterator j = s.begin(); j != s.end(); ++j) {
+ if ((*j)->isa(Text::EventType) &&
+ ((*j)->get<String>(Text::TextTypePropertyName)
+ == Text::Annotation)) {
+ m_annotationsLabel->setText(i18n("Hidden annotations"));
+ return ;
+ }
+ }
+ }
+ }
+ m_annotationsLabel->setText("");
+ getToggleAction("show_annotations")->setChecked(areAnnotationsVisible());
+}
+
+void
+NotationView::slotUpdateLilyPondDirectivesStatus()
+{
+ if (!areLilyPondDirectivesVisible()) {
+ for (int i = 0; i < getStaffCount(); ++i) {
+ Segment &s = getStaff(i)->getSegment();
+ for (Segment::iterator j = s.begin(); j != s.end(); ++j) {
+ if ((*j)->isa(Text::EventType) &&
+ ((*j)->get
+ <String>
+ (Text::TextTypePropertyName)
+ == Text::LilyPondDirective)) {
+ m_lilyPondDirectivesLabel->setText(i18n("Hidden LilyPond directives"));
+ return ;
+ }
+ }
+ }
+ }
+ m_lilyPondDirectivesLabel->setText("");
+ getToggleAction("show_lilypond_directives")->setChecked(areLilyPondDirectivesVisible());
+}
+
+void
+NotationView::slotChangeSpacingFromStringValue(const QString& spacingT)
+{
+ // spacingT has a '%' at the end
+ //
+ int spacing = spacingT.left(spacingT.length() - 1).toInt();
+ slotChangeSpacing(spacing);
+}
+
+void
+NotationView::slotChangeSpacingFromAction()
+{
+ const QObject *s = sender();
+ QString name = s->name();
+
+ if (name.left(8) == "spacing_") {
+ int spacing = name.right(name.length() - 8).toInt();
+
+ if (spacing > 0)
+ slotChangeSpacing(spacing);
+
+ } else {
+ KMessageBox::sorry
+ (this, i18n("Unknown spacing action %1").arg(name));
+ }
+}
+
+void
+NotationView::slotChangeSpacing(int spacing)
+{
+ if (m_hlayout->getSpacing() == spacing)
+ return ;
+
+ m_hlayout->setSpacing(spacing);
+
+ // m_spacingSlider->setSize(spacing);
+
+ KToggleAction *action = dynamic_cast<KToggleAction *>
+ (actionCollection()->action(QString("spacing_%1").arg(spacing)));
+ if (action)
+ action->setChecked(true);
+ else {
+ std::cerr
+ << "WARNING: Expected action \"spacing_" << spacing
+ << "\" to be a KToggleAction, but it isn't (or doesn't exist)"
+ << std::endl;
+ }
+
+ positionStaffs();
+ applyLayout();
+
+ for (unsigned int i = 0; i < m_staffs.size(); ++i) {
+ m_staffs[i]->markChanged();
+ }
+
+ positionPages();
+ updateControlRulers(true);
+ updateView();
+}
+
+void
+NotationView::slotChangeProportionFromIndex(int n)
+{
+ std::vector<int> proportions = m_hlayout->getAvailableProportions();
+ if (n >= (int)proportions.size())
+ n = proportions.size() - 1;
+ slotChangeProportion(proportions[n]);
+}
+
+void
+NotationView::slotChangeProportionFromAction()
+{
+ const QObject *s = sender();
+ QString name = s->name();
+
+ if (name.left(11) == "proportion_") {
+ int proportion = name.right(name.length() - 11).toInt();
+ slotChangeProportion(proportion);
+
+ } else {
+ KMessageBox::sorry
+ (this, i18n("Unknown proportion action %1").arg(name));
+ }
+}
+
+void
+NotationView::slotChangeProportion(int proportion)
+{
+ if (m_hlayout->getProportion() == proportion)
+ return ;
+
+ m_hlayout->setProportion(proportion);
+
+ // m_proportionSlider->setSize(proportion);
+
+ KToggleAction *action = dynamic_cast<KToggleAction *>
+ (actionCollection()->action(QString("proportion_%1").arg(proportion)));
+ if (action)
+ action->setChecked(true);
+ else {
+ std::cerr
+ << "WARNING: Expected action \"proportion_" << proportion
+ << "\" to be a KToggleAction, but it isn't (or doesn't exist)"
+ << std::endl;
+ }
+
+ positionStaffs();
+ applyLayout();
+
+ for (unsigned int i = 0; i < m_staffs.size(); ++i) {
+ m_staffs[i]->markChanged();
+ }
+
+ positionPages();
+ updateControlRulers(true);
+ updateView();
+}
+
+void
+NotationView::slotChangeFontFromAction()
+{
+ const QObject *s = sender();
+ QString name = s->name();
+ if (name.left(10) == "note_font_") {
+ name = name.right(name.length() - 10);
+ slotChangeFont(name);
+ } else {
+ KMessageBox::sorry
+ (this, i18n("Unknown font action %1").arg(name));
+ }
+}
+
+void
+NotationView::slotChangeFontSizeFromAction()
+{
+ const QObject *s = sender();
+ QString name = s->name();
+
+ if (name.left(15) == "note_font_size_") {
+ name = name.right(name.length() - 15);
+ bool ok = false;
+ int size = name.toInt(&ok);
+ if (ok)
+ slotChangeFont(m_fontName, size);
+ else {
+ KMessageBox::sorry
+ (this, i18n("Unknown font size %1").arg(name));
+ }
+ } else {
+ KMessageBox::sorry
+ (this, i18n("Unknown font size action %1").arg(name));
+ }
+}
+
+void
+NotationView::slotChangeFont(const QString &newName)
+{
+ NOTATION_DEBUG << "changeFont: " << newName << endl;
+ slotChangeFont(std::string(newName.utf8()));
+}
+
+void
+NotationView::slotChangeFont(std::string newName)
+{
+ int newSize = m_fontSize;
+
+ if (!NoteFontFactory::isAvailableInSize(newName, newSize)) {
+
+ int defaultSize = NoteFontFactory::getDefaultSize(newName);
+ newSize = m_config->readUnsignedNumEntry
+ ((getStaffCount() > 1 ?
+ "multistaffnotesize" : "singlestaffnotesize"), defaultSize);
+
+ if (!NoteFontFactory::isAvailableInSize(newName, newSize)) {
+ newSize = defaultSize;
+ }
+ }
+
+ slotChangeFont(newName, newSize);
+}
+
+void
+NotationView::slotChangeFontSize(int newSize)
+{
+ slotChangeFont(m_fontName, newSize);
+}
+
+void
+NotationView::slotChangeFontSizeFromStringValue(const QString& sizeT)
+{
+ int size = sizeT.toInt();
+ slotChangeFont(m_fontName, size);
+}
+
+void
+NotationView::slotZoomIn()
+{
+ std::vector<int> sizes = NoteFontFactory::getScreenSizes(m_fontName);
+ for (int i = 0; i + 1 < sizes.size(); ++i) {
+ if (sizes[i] == m_fontSize) {
+ slotChangeFontSize(sizes[i + 1]);
+ return ;
+ }
+ }
+}
+
+void
+NotationView::slotZoomOut()
+{
+ std::vector<int> sizes = NoteFontFactory::getScreenSizes(m_fontName);
+ for (int i = 1; i < sizes.size(); ++i) {
+ if (sizes[i] == m_fontSize) {
+ slotChangeFontSize(sizes[i - 1]);
+ return ;
+ }
+ }
+}
+
+void
+NotationView::slotChangeFont(std::string newName, int newSize)
+{
+ if (newName == m_fontName && newSize == m_fontSize)
+ return ;
+
+ NotePixmapFactory* npf = 0;
+
+ try {
+ npf = new NotePixmapFactory(newName, newSize);
+ } catch (...) {
+ return ;
+ }
+
+ bool changedFont = (newName != m_fontName || newSize != m_fontSize);
+
+ std::string oldName = m_fontName;
+ m_fontName = newName;
+ m_fontSize = newSize;
+ setNotePixmapFactory(npf);
+
+ // update the various GUI elements
+
+ std::set<std::string> fs(NoteFontFactory::getFontNames());
+ std::vector<std::string> f(fs.begin(), fs.end());
+ std::sort(f.begin(), f.end());
+
+ for (unsigned int i = 0; i < f.size(); ++i) {
+ bool thisOne = (f[i] == m_fontName);
+ if (thisOne)
+ m_fontCombo->setCurrentItem(i);
+ KToggleAction *action = dynamic_cast<KToggleAction *>
+ (actionCollection()->action("note_font_" + strtoqstr(f[i])));
+ NOTATION_DEBUG << "inspecting " << f[i] << (action ? ", have action" : ", no action") << endl;
+ if (action)
+ action->setChecked(thisOne);
+ else {
+ std::cerr
+ << "WARNING: Expected action \"note_font_" << f[i]
+ << "\" to be a KToggleAction, but it isn't (or doesn't exist)"
+ << std::endl;
+ }
+ }
+
+ NOTATION_DEBUG << "about to reinitialise sizes" << endl;
+
+ std::vector<int> sizes = NoteFontFactory::getScreenSizes(m_fontName);
+ m_fontSizeCombo->clear();
+ QString value;
+ for (std::vector<int>::iterator i = sizes.begin(); i != sizes.end(); ++i) {
+ value.setNum(*i);
+ m_fontSizeCombo->insertItem(value);
+ }
+ value.setNum(m_fontSize);
+ m_fontSizeCombo->setCurrentText(value);
+
+ setupFontSizeMenu(oldName);
+
+ if (!changedFont)
+ return ; // might have been called to initialise menus etc
+
+ NOTATION_DEBUG << "about to change font" << endl;
+
+ if (m_pageMode == LinedStaff::MultiPageMode) {
+
+ int pageWidth = getPageWidth();
+ int topMargin = 0, leftMargin = 0;
+ getPageMargins(leftMargin, topMargin);
+
+ m_hlayout->setPageWidth(pageWidth - leftMargin * 2);
+ }
+
+ for (unsigned int i = 0; i < m_staffs.size(); ++i) {
+ m_staffs[i]->changeFont(m_fontName, m_fontSize);
+ }
+
+ NOTATION_DEBUG << "about to position staffs" << endl;
+
+ positionStaffs();
+
+ bool layoutApplied = applyLayout();
+ if (!layoutApplied)
+ KMessageBox::sorry(0, "Couldn't apply layout");
+ else {
+ for (unsigned int i = 0; i < m_staffs.size(); ++i) {
+ m_staffs[i]->markChanged();
+ }
+ }
+
+ positionPages();
+ updateControlRulers(true);
+ updateView();
+}
+
+void
+NotationView::slotFilePrint()
+{
+ KTmpStatusMsg msg(i18n("Printing..."), this);
+
+ SetWaitCursor waitCursor;
+ NotationView printingView(getDocument(), m_segments,
+ (QWidget *)parent(), this);
+
+ if (!printingView.isOK()) {
+ NOTATION_DEBUG << "Print : operation cancelled\n";
+ return ;
+ }
+
+ printingView.print();
+}
+
+void
+NotationView::slotFilePrintPreview()
+{
+ KTmpStatusMsg msg(i18n("Previewing..."), this);
+
+ SetWaitCursor waitCursor;
+ NotationView printingView(getDocument(), m_segments,
+ (QWidget *)parent(), this);
+
+ if (!printingView.isOK()) {
+ NOTATION_DEBUG << "Print preview : operation cancelled\n";
+ return ;
+ }
+
+ printingView.print(true);
+}
+
+std::map<KProcess *, KTempFile *> NotationView::m_lilyTempFileMap;
+
+void NotationView::slotPrintLilyPond()
+{
+ KTmpStatusMsg msg(i18n("Printing LilyPond file..."), this);
+ KTempFile *file = new KTempFile(QString::null, ".ly");
+ file->setAutoDelete(true);
+ if (!file->name()) {
+ // CurrentProgressDialog::freeze();
+ KMessageBox::sorry(this, i18n("Failed to open a temporary file for LilyPond export."));
+ delete file;
+ }
+ if (!exportLilyPondFile(file->name(), true)) {
+ return ;
+ }
+ KProcess *proc = new KProcess;
+ *proc << "rosegarden-lilypondview";
+ *proc << "--graphical";
+ *proc << "--print";
+ *proc << file->name();
+ connect(proc, SIGNAL(processExited(KProcess *)),
+ this, SLOT(slotLilyPondViewProcessExited(KProcess *)));
+ m_lilyTempFileMap[proc] = file;
+ proc->start(KProcess::NotifyOnExit);
+}
+
+void NotationView::slotPreviewLilyPond()
+{
+ KTmpStatusMsg msg(i18n("Previewing LilyPond file..."), this);
+ KTempFile *file = new KTempFile(QString::null, ".ly");
+ file->setAutoDelete(true);
+ if (!file->name()) {
+ // CurrentProgressDialog::freeze();
+ KMessageBox::sorry(this, i18n("Failed to open a temporary file for LilyPond export."));
+ delete file;
+ }
+ if (!exportLilyPondFile(file->name(), true)) {
+ return ;
+ }
+ KProcess *proc = new KProcess;
+ *proc << "rosegarden-lilypondview";
+ *proc << "--graphical";
+ *proc << "--pdf";
+ *proc << file->name();
+ connect(proc, SIGNAL(processExited(KProcess *)),
+ this, SLOT(slotLilyPondViewProcessExited(KProcess *)));
+ m_lilyTempFileMap[proc] = file;
+ proc->start(KProcess::NotifyOnExit);
+}
+
+void NotationView::slotLilyPondViewProcessExited(KProcess *p)
+{
+ delete m_lilyTempFileMap[p];
+ m_lilyTempFileMap.erase(p);
+ delete p;
+}
+
+bool NotationView::exportLilyPondFile(QString file, bool forPreview)
+{
+ QString caption = "", heading = "";
+ if (forPreview) {
+ caption = i18n("LilyPond Preview Options");
+ heading = i18n("LilyPond preview options");
+ }
+
+ LilyPondOptionsDialog dialog(this, m_doc, caption, heading);
+ if (dialog.exec() != QDialog::Accepted) {
+ return false;
+ }
+
+ ProgressDialog progressDlg(i18n("Exporting LilyPond file..."),
+ 100,
+ this);
+
+ LilyPondExporter e(this, m_doc, std::string(QFile::encodeName(file)));
+
+ connect(&e, SIGNAL(setProgress(int)),
+ progressDlg.progressBar(), SLOT(setValue(int)));
+
+ connect(&e, SIGNAL(incrementProgress(int)),
+ progressDlg.progressBar(), SLOT(advance(int)));
+
+ if (!e.write()) {
+ // CurrentProgressDialog::freeze();
+ KMessageBox::sorry(this, i18n("Export failed. The file could not be opened for writing."));
+ return false;
+ }
+
+ return true;
+}
+
+void NotationView::slotEditCut()
+{
+ if (!m_currentEventSelection)
+ return ;
+ KTmpStatusMsg msg(i18n("Cutting selection to clipboard..."), this);
+
+ addCommandToHistory(new CutCommand(*m_currentEventSelection,
+ getDocument()->getClipboard()));
+}
+
+void NotationView::slotEditDelete()
+{
+ if (!m_currentEventSelection)
+ return ;
+ KTmpStatusMsg msg(i18n("Deleting selection..."), this);
+
+ addCommandToHistory(new EraseCommand(*m_currentEventSelection));
+}
+
+void NotationView::slotEditCopy()
+{
+ if (!m_currentEventSelection)
+ return ;
+ KTmpStatusMsg msg(i18n("Copying selection to clipboard..."), this);
+
+ addCommandToHistory(new CopyCommand(*m_currentEventSelection,
+ getDocument()->getClipboard()));
+}
+
+void NotationView::slotEditCutAndClose()
+{
+ if (!m_currentEventSelection)
+ return ;
+ KTmpStatusMsg msg(i18n("Cutting selection to clipboard..."), this);
+
+ addCommandToHistory(new CutAndCloseCommand(*m_currentEventSelection,
+ getDocument()->getClipboard()));
+}
+
+static const QString RESTRICTED_PASTE_FAILED_DESCRIPTION = i18n(
+ "The Restricted paste type requires enough empty " \
+ "space (containing only rests) at the paste position " \
+ "to hold all of the events to be pasted.\n" \
+ "Not enough space was found.\n" \
+ "If you want to paste anyway, consider using one of " \
+ "the other paste types from the \"Paste...\" option " \
+ "on the Edit menu. You can also change the default " \
+ "paste type to something other than Restricted if " \
+ "you wish."
+ );
+
+void NotationView::slotEditPaste()
+{
+ Clipboard * clipboard = getDocument()->getClipboard();
+
+ if (clipboard->isEmpty()) {
+ slotStatusHelpMsg(i18n("Clipboard is empty"));
+ return ;
+ }
+ if (!clipboard->isSingleSegment()) {
+ slotStatusHelpMsg(i18n("Can't paste multiple Segments into one"));
+ return ;
+ }
+
+ slotStatusHelpMsg(i18n("Inserting clipboard contents..."));
+
+ LinedStaff *staff = getCurrentLinedStaff();
+ Segment &segment = staff->getSegment();
+
+ // Paste at cursor position
+ //
+ timeT insertionTime = getInsertionTime();
+ timeT endTime = insertionTime +
+ (clipboard->getSingleSegment()->getEndTime() -
+ clipboard->getSingleSegment()->getStartTime());
+
+ KConfig *config = kapp->config();
+ config->setGroup(NotationViewConfigGroup);
+ PasteEventsCommand::PasteType defaultType = (PasteEventsCommand::PasteType)
+ config->readUnsignedNumEntry("pastetype",
+ PasteEventsCommand::Restricted);
+
+ PasteEventsCommand *command = new PasteEventsCommand
+ (segment, clipboard, insertionTime, defaultType);
+
+ if (!command->isPossible()) {
+ KMessageBox::detailedError
+ (this,
+ i18n("Couldn't paste at this point."), RESTRICTED_PASTE_FAILED_DESCRIPTION);
+ } else {
+ addCommandToHistory(command);
+ setCurrentSelection(new EventSelection(command->getPastedEvents()));
+ slotSetInsertCursorPosition(endTime, true, false);
+ }
+}
+
+void NotationView::slotEditGeneralPaste()
+{
+ Clipboard *clipboard = getDocument()->getClipboard();
+
+ if (clipboard->isEmpty()) {
+ slotStatusHelpMsg(i18n("Clipboard is empty"));
+ return ;
+ }
+
+ slotStatusHelpMsg(i18n("Inserting clipboard contents..."));
+
+ LinedStaff *staff = getCurrentLinedStaff();
+ Segment &segment = staff->getSegment();
+
+ KConfig *config = kapp->config();
+ config->setGroup(NotationViewConfigGroup);
+ PasteEventsCommand::PasteType defaultType = (PasteEventsCommand::PasteType)
+ config->readUnsignedNumEntry("pastetype",
+ PasteEventsCommand::Restricted);
+
+ PasteNotationDialog dialog(this, defaultType);
+
+ if (dialog.exec() == QDialog::Accepted) {
+
+ PasteEventsCommand::PasteType type = dialog.getPasteType();
+ if (dialog.setAsDefault()) {
+ config->setGroup(NotationViewConfigGroup);
+ config->writeEntry("pastetype", type);
+ }
+
+ timeT insertionTime = getInsertionTime();
+ timeT endTime = insertionTime +
+ (clipboard->getSingleSegment()->getEndTime() -
+ clipboard->getSingleSegment()->getStartTime());
+
+ PasteEventsCommand *command = new PasteEventsCommand
+ (segment, clipboard, insertionTime, type);
+
+ if (!command->isPossible()) {
+ KMessageBox::detailedError
+ (this,
+ i18n("Couldn't paste at this point."),
+ i18n(RESTRICTED_PASTE_FAILED_DESCRIPTION));
+ } else {
+ addCommandToHistory(command);
+ setCurrentSelection(new EventSelection
+ (segment, insertionTime, endTime));
+ slotSetInsertCursorPosition(endTime, true, false);
+ }
+ }
+}
+
+void
+NotationView::slotMoveEventsUpStaff()
+{
+ LinedStaff *targetStaff = getStaffAbove();
+ if (!targetStaff) return;
+ if (!m_currentEventSelection) return;
+ Segment &targetSegment = targetStaff->getSegment();
+
+ KMacroCommand *command = new KMacroCommand(i18n("Move Events to Staff Above"));
+
+ timeT insertionTime = m_currentEventSelection->getStartTime();
+
+ Clipboard *c = new Clipboard;
+ CopyCommand *cc = new CopyCommand(*m_currentEventSelection, c);
+ cc->execute();
+
+ command->addCommand(new EraseCommand(*m_currentEventSelection));;
+
+ command->addCommand(new PasteEventsCommand
+ (targetSegment, c,
+ insertionTime,
+ PasteEventsCommand::NoteOverlay));
+
+ addCommandToHistory(command);
+
+ delete c;
+}
+
+void
+NotationView::slotMoveEventsDownStaff()
+{
+ LinedStaff *targetStaff = getStaffBelow();
+ if (!targetStaff) return;
+ if (!m_currentEventSelection) return;
+ Segment &targetSegment = targetStaff->getSegment();
+
+ KMacroCommand *command = new KMacroCommand(i18n("Move Events to Staff Below"));
+
+ timeT insertionTime = m_currentEventSelection->getStartTime();
+
+ Clipboard *c = new Clipboard;
+ CopyCommand *cc = new CopyCommand(*m_currentEventSelection, c);
+ cc->execute();
+
+ command->addCommand(new EraseCommand(*m_currentEventSelection));;
+
+ command->addCommand(new PasteEventsCommand
+ (targetSegment, c,
+ insertionTime,
+ PasteEventsCommand::NoteOverlay));
+
+ addCommandToHistory(command);
+
+ delete c;
+}
+
+void NotationView::slotPreviewSelection()
+{
+ if (!m_currentEventSelection)
+ return ;
+
+ getDocument()->slotSetLoop(m_currentEventSelection->getStartTime(),
+ m_currentEventSelection->getEndTime());
+}
+
+void NotationView::slotClearLoop()
+{
+ getDocument()->slotSetLoop(0, 0);
+}
+
+void NotationView::slotClearSelection()
+{
+ // Actually we don't clear the selection immediately: if we're
+ // using some tool other than the select tool, then the first
+ // press switches us back to the select tool.
+
+ NotationSelector *selector = dynamic_cast<NotationSelector *>(m_tool);
+
+ if (!selector) {
+ slotSelectSelected();
+ } else {
+ setCurrentSelection(0);
+ }
+}
+
+void NotationView::slotEditSelectFromStart()
+{
+ timeT t = getInsertionTime();
+ Segment &segment = m_staffs[m_currentStaff]->getSegment();
+ setCurrentSelection(new EventSelection(segment,
+ segment.getStartTime(),
+ t));
+}
+
+void NotationView::slotEditSelectToEnd()
+{
+ timeT t = getInsertionTime();
+ Segment &segment = m_staffs[m_currentStaff]->getSegment();
+ setCurrentSelection(new EventSelection(segment,
+ t,
+ segment.getEndMarkerTime()));
+}
+
+void NotationView::slotEditSelectWholeStaff()
+{
+ Segment &segment = m_staffs[m_currentStaff]->getSegment();
+ setCurrentSelection(new EventSelection(segment,
+ segment.getStartTime(),
+ segment.getEndMarkerTime()));
+}
+
+void NotationView::slotFilterSelection()
+{
+ NOTATION_DEBUG << "NotationView::slotFilterSelection" << endl;
+
+ Segment *segment = getCurrentSegment();
+ EventSelection *existingSelection = m_currentEventSelection;
+ if (!segment || !existingSelection)
+ return ;
+
+ EventFilterDialog dialog(this);
+ if (dialog.exec() == QDialog::Accepted) {
+ NOTATION_DEBUG << "slotFilterSelection- accepted" << endl;
+
+ bool haveEvent = false;
+
+ EventSelection *newSelection = new EventSelection(*segment);
+ EventSelection::eventcontainer &ec =
+ existingSelection->getSegmentEvents();
+ for (EventSelection::eventcontainer::iterator i =
+ ec.begin(); i != ec.end(); ++i) {
+ if (dialog.keepEvent(*i)) {
+ haveEvent = true;
+ newSelection->addEvent(*i);
+ }
+ }
+
+ if (haveEvent)
+ setCurrentSelection(newSelection);
+ else
+ setCurrentSelection(0);
+ }
+}
+
+void NotationView::slotFinePositionLeft()
+{
+ if (!m_currentEventSelection)
+ return ;
+ KTmpStatusMsg msg(i18n("Pushing selection left..."), this);
+
+ // half a note body width
+ addCommandToHistory(new IncrementDisplacementsCommand
+ (*m_currentEventSelection, -500, 0));
+}
+
+void NotationView::slotFinePositionRight()
+{
+ if (!m_currentEventSelection)
+ return ;
+ KTmpStatusMsg msg(i18n("Pushing selection right..."), this);
+
+ // half a note body width
+ addCommandToHistory(new IncrementDisplacementsCommand
+ (*m_currentEventSelection, 500, 0));
+}
+
+void NotationView::slotFinePositionUp()
+{
+ if (!m_currentEventSelection)
+ return ;
+ KTmpStatusMsg msg(i18n("Pushing selection up..."), this);
+
+ // half line height
+ addCommandToHistory(new IncrementDisplacementsCommand
+ (*m_currentEventSelection, 0, -500));
+}
+
+void NotationView::slotFinePositionDown()
+{
+ if (!m_currentEventSelection)
+ return ;
+ KTmpStatusMsg msg(i18n("Pushing selection down..."), this);
+
+ // half line height
+ addCommandToHistory(new IncrementDisplacementsCommand
+ (*m_currentEventSelection, 0, 500));
+}
+
+void NotationView::slotFinePositionRestore()
+{
+ if (!m_currentEventSelection)
+ return ;
+ KTmpStatusMsg msg(i18n("Restoring computed positions..."), this);
+
+ addCommandToHistory(new ResetDisplacementsCommand(*m_currentEventSelection));
+}
+
+void NotationView::slotMakeVisible()
+{
+ if (!m_currentEventSelection)
+ return ;
+ KTmpStatusMsg msg(i18n("Making visible..."), this);
+
+ addCommandToHistory(new SetVisibilityCommand(*m_currentEventSelection, true));
+}
+
+void NotationView::slotMakeInvisible()
+{
+ if (!m_currentEventSelection)
+ return ;
+ KTmpStatusMsg msg(i18n("Making invisible..."), this);
+
+ addCommandToHistory(new SetVisibilityCommand(*m_currentEventSelection, false));
+}
+
+void NotationView::slotToggleToolsToolBar()
+{
+ toggleNamedToolBar("Tools Toolbar");
+}
+
+void NotationView::slotToggleNotesToolBar()
+{
+ toggleNamedToolBar("Notes Toolbar");
+}
+
+void NotationView::slotToggleRestsToolBar()
+{
+ toggleNamedToolBar("Rests Toolbar");
+}
+
+void NotationView::slotToggleAccidentalsToolBar()
+{
+ toggleNamedToolBar("Accidentals Toolbar");
+}
+
+void NotationView::slotToggleClefsToolBar()
+{
+ toggleNamedToolBar("Clefs Toolbar");
+}
+
+void NotationView::slotToggleMetaToolBar()
+{
+ toggleNamedToolBar("Meta Toolbar");
+}
+
+void NotationView::slotToggleMarksToolBar()
+{
+ toggleNamedToolBar("Marks Toolbar");
+}
+
+void NotationView::slotToggleGroupToolBar()
+{
+ toggleNamedToolBar("Group Toolbar");
+}
+
+void NotationView::slotToggleLayoutToolBar()
+{
+ toggleNamedToolBar("Layout Toolbar");
+}
+
+void NotationView::slotToggleTransportToolBar()
+{
+ toggleNamedToolBar("Transport Toolbar");
+}
+
+void NotationView::toggleNamedToolBar(const QString& toolBarName, bool* force)
+{
+ KToolBar *namedToolBar = toolBar(toolBarName);
+
+ if (!namedToolBar) {
+ NOTATION_DEBUG << "NotationView::toggleNamedToolBar() : toolBar "
+ << toolBarName << " not found" << endl;
+ return ;
+ }
+
+ if (!force) {
+
+ if (namedToolBar->isVisible())
+ namedToolBar->hide();
+ else
+ namedToolBar->show();
+ } else {
+
+ if (*force)
+ namedToolBar->show();
+ else
+ namedToolBar->hide();
+ }
+
+ setSettingsDirty();
+
+}
+
+void NotationView::slotGroupBeam()
+{
+ if (!m_currentEventSelection)
+ return ;
+ KTmpStatusMsg msg(i18n("Beaming group..."), this);
+
+ addCommandToHistory(new BeamCommand
+ (*m_currentEventSelection));
+}
+
+void NotationView::slotGroupAutoBeam()
+{
+ if (!m_currentEventSelection)
+ return ;
+ KTmpStatusMsg msg(i18n("Auto-beaming selection..."), this);
+
+ addCommandToHistory(new AutoBeamCommand
+ (*m_currentEventSelection));
+}
+
+void NotationView::slotGroupBreak()
+{
+ if (!m_currentEventSelection)
+ return ;
+ KTmpStatusMsg msg(i18n("Breaking groups..."), this);
+
+ addCommandToHistory(new BreakCommand
+ (*m_currentEventSelection));
+}
+
+void NotationView::slotGroupSimpleTuplet()
+{
+ slotGroupTuplet(true);
+}
+
+void NotationView::slotGroupGeneralTuplet()
+{
+ slotGroupTuplet(false);
+}
+
+void NotationView::slotGroupTuplet(bool simple)
+{
+ timeT t = 0;
+ timeT unit = 0;
+ int tupled = 2;
+ int untupled = 3;
+ Segment *segment = 0;
+ bool hasTimingAlready = false;
+
+ if (m_currentEventSelection) {
+
+ t = m_currentEventSelection->getStartTime();
+
+ timeT duration = m_currentEventSelection->getTotalDuration();
+ Note::Type unitType =
+ Note::getNearestNote(duration / 3, 0).getNoteType();
+ unit = Note(unitType).getDuration();
+
+ if (!simple) {
+ TupletDialog dialog(this, unitType, duration);
+ if (dialog.exec() != QDialog::Accepted)
+ return ;
+ unit = Note(dialog.getUnitType()).getDuration();
+ tupled = dialog.getTupledCount();
+ untupled = dialog.getUntupledCount();
+ hasTimingAlready = dialog.hasTimingAlready();
+ }
+
+ segment = &m_currentEventSelection->getSegment();
+
+ } else {
+
+ t = getInsertionTime();
+
+ NoteInserter *currentInserter = dynamic_cast<NoteInserter *>
+ (m_toolBox->getTool(NoteInserter::ToolName));
+
+ Note::Type unitType;
+
+ if (currentInserter) {
+ unitType = currentInserter->getCurrentNote().getNoteType();
+ } else {
+ unitType = Note::Quaver;
+ }
+
+ unit = Note(unitType).getDuration();
+
+ if (!simple) {
+ TupletDialog dialog(this, unitType);
+ if (dialog.exec() != QDialog::Accepted)
+ return ;
+ unit = Note(dialog.getUnitType()).getDuration();
+ tupled = dialog.getTupledCount();
+ untupled = dialog.getUntupledCount();
+ hasTimingAlready = dialog.hasTimingAlready();
+ }
+
+ segment = &m_staffs[m_currentStaff]->getSegment();
+ }
+
+ addCommandToHistory(new TupletCommand
+ (*segment, t, unit, untupled, tupled, hasTimingAlready));
+
+ if (!hasTimingAlready) {
+ slotSetInsertCursorPosition(t + (unit * tupled), true, false);
+ }
+}
+
+void NotationView::slotGroupUnTuplet()
+{
+ if (!m_currentEventSelection)
+ return ;
+ KTmpStatusMsg msg(i18n("Untupleting..."), this);
+
+ addCommandToHistory(new UnTupletCommand
+ (*m_currentEventSelection));
+}
+
+void NotationView::slotGroupSlur()
+{
+ KTmpStatusMsg msg(i18n("Adding slur..."), this);
+ slotAddIndication(Indication::Slur, i18n("slur"));
+}
+
+void NotationView::slotGroupPhrasingSlur()
+{
+ KTmpStatusMsg msg(i18n("Adding phrasing slur..."), this);
+ slotAddIndication(Indication::PhrasingSlur, i18n("phrasing slur"));
+}
+
+void NotationView::slotGroupGlissando()
+{
+ KTmpStatusMsg msg(i18n("Adding glissando..."), this);
+ slotAddIndication(Indication::Glissando, i18n("glissando"));
+}
+
+void NotationView::slotGroupCrescendo()
+{
+ KTmpStatusMsg msg(i18n("Adding crescendo..."), this);
+ slotAddIndication(Indication::Crescendo, i18n("dynamic"));
+}
+
+void NotationView::slotGroupDecrescendo()
+{
+ KTmpStatusMsg msg(i18n("Adding decrescendo..."), this);
+ slotAddIndication(Indication::Decrescendo, i18n("dynamic"));
+}
+
+void NotationView::slotGroupOctave2Up()
+{
+ KTmpStatusMsg msg(i18n("Adding octave..."), this);
+ slotAddIndication(Indication::QuindicesimaUp, i18n("ottava"));
+}
+
+void NotationView::slotGroupOctaveUp()
+{
+ KTmpStatusMsg msg(i18n("Adding octave..."), this);
+ slotAddIndication(Indication::OttavaUp, i18n("ottava"));
+}
+
+void NotationView::slotGroupOctaveDown()
+{
+ KTmpStatusMsg msg(i18n("Adding octave..."), this);
+ slotAddIndication(Indication::OttavaDown, i18n("ottava"));
+}
+
+void NotationView::slotGroupOctave2Down()
+{
+ KTmpStatusMsg msg(i18n("Adding octave..."), this);
+ slotAddIndication(Indication::QuindicesimaDown, i18n("ottava"));
+}
+
+void NotationView::slotAddIndication(std::string type, QString desc)
+{
+ if (!m_currentEventSelection)
+ return ;
+
+ AddIndicationCommand *command =
+ new AddIndicationCommand(type, *m_currentEventSelection);
+
+ if (command->canExecute()) {
+ addCommandToHistory(command);
+ setSingleSelectedEvent(m_currentEventSelection->getSegment(),
+ command->getLastInsertedEvent());
+ } else {
+ KMessageBox::sorry(this, i18n("Can't add overlapping %1 indications").arg(desc)); // TODO PLURAL - how many 'indications' ?
+ delete command;
+ }
+}
+
+void NotationView::slotGroupMakeChord()
+{
+ if (!m_currentEventSelection)
+ return ;
+ KTmpStatusMsg msg(i18n("Making chord..."), this);
+
+ MakeChordCommand *command =
+ new MakeChordCommand(*m_currentEventSelection);
+
+ addCommandToHistory(command);
+}
+
+void NotationView::slotTransformsNormalizeRests()
+{
+ if (!m_currentEventSelection)
+ return ;
+ KTmpStatusMsg msg(i18n("Normalizing rests..."), this);
+
+ addCommandToHistory(new NormalizeRestsCommand
+ (*m_currentEventSelection));
+}
+
+void NotationView::slotTransformsCollapseRests()
+{
+ if (!m_currentEventSelection)
+ return ;
+ KTmpStatusMsg msg(i18n("Collapsing rests..."), this);
+
+ addCommandToHistory(new CollapseRestsCommand
+ (*m_currentEventSelection));
+}
+
+void NotationView::slotTransformsCollapseNotes()
+{
+ if (!m_currentEventSelection)
+ return ;
+ KTmpStatusMsg msg(i18n("Collapsing notes..."), this);
+
+ addCommandToHistory(new CollapseNotesCommand
+ (*m_currentEventSelection));
+}
+
+void NotationView::slotTransformsTieNotes()
+{
+ if (!m_currentEventSelection)
+ return ;
+ KTmpStatusMsg msg(i18n("Tying notes..."), this);
+
+ addCommandToHistory(new TieNotesCommand
+ (*m_currentEventSelection));
+}
+
+void NotationView::slotTransformsUntieNotes()
+{
+ if (!m_currentEventSelection)
+ return ;
+ KTmpStatusMsg msg(i18n("Untying notes..."), this);
+
+ addCommandToHistory(new UntieNotesCommand
+ (*m_currentEventSelection));
+}
+
+void NotationView::slotTransformsMakeNotesViable()
+{
+ if (!m_currentEventSelection)
+ return ;
+ KTmpStatusMsg msg(i18n("Making notes viable..."), this);
+
+ addCommandToHistory(new MakeNotesViableCommand
+ (*m_currentEventSelection));
+}
+
+void NotationView::slotTransformsDeCounterpoint()
+{
+ if (!m_currentEventSelection)
+ return ;
+ KTmpStatusMsg msg(i18n("Removing counterpoint..."), this);
+
+ addCommandToHistory(new DeCounterpointCommand
+ (*m_currentEventSelection));
+}
+
+void NotationView::slotTransformsStemsUp()
+{
+ if (!m_currentEventSelection)
+ return ;
+ KTmpStatusMsg msg(i18n("Pointing stems up..."), this);
+
+ addCommandToHistory(new ChangeStemsCommand
+ (true, *m_currentEventSelection));
+}
+
+void NotationView::slotTransformsStemsDown()
+{
+ if (!m_currentEventSelection)
+ return ;
+ KTmpStatusMsg msg(i18n("Pointing stems down..."), this);
+
+ addCommandToHistory(new ChangeStemsCommand
+ (false, *m_currentEventSelection));
+
+}
+
+void NotationView::slotTransformsRestoreStems()
+{
+ if (!m_currentEventSelection)
+ return ;
+ KTmpStatusMsg msg(i18n("Restoring computed stem directions..."), this);
+
+ addCommandToHistory(new RestoreStemsCommand
+ (*m_currentEventSelection));
+}
+
+void NotationView::slotTransformsSlursAbove()
+{
+ if (!m_currentEventSelection)
+ return ;
+ KTmpStatusMsg msg(i18n("Positioning slurs..."), this);
+
+ addCommandToHistory(new ChangeSlurPositionCommand
+ (true, *m_currentEventSelection));
+}
+
+void NotationView::slotTransformsSlursBelow()
+{
+ if (!m_currentEventSelection)
+ return ;
+ KTmpStatusMsg msg(i18n("Positioning slurs..."), this);
+
+ addCommandToHistory(new ChangeSlurPositionCommand
+ (false, *m_currentEventSelection));
+
+}
+
+void NotationView::slotTransformsRestoreSlurs()
+{
+ if (!m_currentEventSelection)
+ return ;
+ KTmpStatusMsg msg(i18n("Restoring slur positions..."), this);
+
+ addCommandToHistory(new RestoreSlursCommand
+ (*m_currentEventSelection));
+}
+
+void NotationView::slotTransformsTiesAbove()
+{
+ if (!m_currentEventSelection)
+ return ;
+ KTmpStatusMsg msg(i18n("Positioning ties..."), this);
+
+ addCommandToHistory(new ChangeTiePositionCommand
+ (true, *m_currentEventSelection));
+}
+
+void NotationView::slotTransformsTiesBelow()
+{
+ if (!m_currentEventSelection)
+ return ;
+ KTmpStatusMsg msg(i18n("Positioning ties..."), this);
+
+ addCommandToHistory(new ChangeTiePositionCommand
+ (false, *m_currentEventSelection));
+
+}
+
+void NotationView::slotTransformsRestoreTies()
+{
+ if (!m_currentEventSelection)
+ return ;
+ KTmpStatusMsg msg(i18n("Restoring tie positions..."), this);
+
+ addCommandToHistory(new RestoreTiesCommand
+ (*m_currentEventSelection));
+}
+
+void NotationView::slotTransformsFixQuantization()
+{
+ if (!m_currentEventSelection)
+ return ;
+ KTmpStatusMsg msg(i18n("Fixing notation quantization..."), this);
+
+ addCommandToHistory(new FixNotationQuantizeCommand
+ (*m_currentEventSelection));
+}
+
+void NotationView::slotTransformsRemoveQuantization()
+{
+ if (!m_currentEventSelection)
+ return ;
+ KTmpStatusMsg msg(i18n("Removing notation quantization..."), this);
+
+ addCommandToHistory(new RemoveNotationQuantizeCommand
+ (*m_currentEventSelection));
+}
+
+void NotationView::slotSetStyleFromAction()
+{
+ const QObject *s = sender();
+ QString name = s->name();
+
+ if (!m_currentEventSelection)
+ return ;
+
+ if (name.left(6) == "style_") {
+ name = name.right(name.length() - 6);
+
+ KTmpStatusMsg msg(i18n("Changing to %1 style...").arg(name),
+ this);
+
+ addCommandToHistory(new ChangeStyleCommand
+ (NoteStyleName(qstrtostr(name)),
+ *m_currentEventSelection));
+ } else {
+ KMessageBox::sorry
+ (this, i18n("Unknown style action %1").arg(name));
+ }
+}
+
+void NotationView::slotInsertNoteFromAction()
+{
+ const QObject *s = sender();
+ QString name = s->name();
+
+ Segment &segment = m_staffs[m_currentStaff]->getSegment();
+
+ NoteInserter *noteInserter = dynamic_cast<NoteInserter *>(m_tool);
+ if (!noteInserter) {
+ KMessageBox::sorry(this, i18n("No note duration selected"));
+ return ;
+ }
+
+ int pitch = 0;
+ Accidental accidental =
+ Accidentals::NoAccidental;
+
+ timeT time(getInsertionTime());
+ Rosegarden::Key key = segment.getKeyAtTime(time);
+ Clef clef = segment.getClefAtTime(time);
+
+ try {
+
+ pitch = getPitchFromNoteInsertAction(name, accidental, clef, key);
+
+ } catch (...) {
+
+ KMessageBox::sorry
+ (this, i18n("Unknown note insert action %1").arg(name));
+ return ;
+ }
+
+ KTmpStatusMsg msg(i18n("Inserting note"), this);
+
+ NOTATION_DEBUG << "Inserting note at pitch " << pitch << endl;
+
+ noteInserter->insertNote(segment, time, pitch, accidental);
+}
+
+void NotationView::slotInsertRest()
+{
+ Segment &segment = m_staffs[m_currentStaff]->getSegment();
+ timeT time = getInsertionTime();
+
+ RestInserter *restInserter = dynamic_cast<RestInserter *>(m_tool);
+
+ if (!restInserter) {
+
+ NoteInserter *noteInserter = dynamic_cast<NoteInserter *>(m_tool);
+ if (!noteInserter) {
+ KMessageBox::sorry(this, i18n("No note duration selected"));
+ return ;
+ }
+
+ Note note(noteInserter->getCurrentNote());
+
+ restInserter = dynamic_cast<RestInserter*>
+ (m_toolBox->getTool(RestInserter::ToolName));
+
+ restInserter->slotSetNote(note.getNoteType());
+ restInserter->slotSetDots(note.getDots());
+ }
+
+ restInserter->insertNote(segment, time,
+ 0, Accidentals::NoAccidental, true);
+}
+
+void NotationView::slotSwitchFromRestToNote()
+{
+ RestInserter *restInserter = dynamic_cast<RestInserter *>(m_tool);
+ if (!restInserter) {
+ KMessageBox::sorry(this, i18n("No rest duration selected"));
+ return ;
+ }
+
+ Note note(restInserter->getCurrentNote());
+
+ QString actionName = NotationStrings::getReferenceName(note, false);
+ actionName = actionName.replace("-", "_");
+
+ KRadioAction *action = dynamic_cast<KRadioAction *>
+ (actionCollection()->action(actionName));
+
+ if (!action) {
+ std::cerr << "WARNING: Failed to find note action \""
+ << actionName << "\"" << std::endl;
+ } else {
+ action->activate();
+ }
+
+ NoteInserter *noteInserter = dynamic_cast<NoteInserter*>
+ (m_toolBox->getTool(NoteInserter::ToolName));
+
+ if (noteInserter) {
+ noteInserter->slotSetNote(note.getNoteType());
+ noteInserter->slotSetDots(note.getDots());
+ setTool(noteInserter);
+ }
+
+ setMenuStates();
+}
+
+void NotationView::slotSwitchFromNoteToRest()
+{
+ NoteInserter *noteInserter = dynamic_cast<NoteInserter *>(m_tool);
+ if (!noteInserter) {
+ KMessageBox::sorry(this, i18n("No note duration selected"));
+ return ;
+ }
+
+ Note note(noteInserter->getCurrentNote());
+
+ QString actionName = NotationStrings::getReferenceName(note, true);
+ actionName = actionName.replace("-", "_");
+
+ KRadioAction *action = dynamic_cast<KRadioAction *>
+ (actionCollection()->action(actionName));
+
+ if (!action) {
+ std::cerr << "WARNING: Failed to find rest action \""
+ << actionName << "\"" << std::endl;
+ } else {
+ action->activate();
+ }
+
+ RestInserter *restInserter = dynamic_cast<RestInserter*>
+ (m_toolBox->getTool(RestInserter::ToolName));
+
+ if (restInserter) {
+ restInserter->slotSetNote(note.getNoteType());
+ restInserter->slotSetDots(note.getDots());
+ setTool(restInserter);
+ }
+
+ setMenuStates();
+}
+
+void NotationView::slotToggleDot()
+{
+ NoteInserter *noteInserter = dynamic_cast<NoteInserter *>(m_tool);
+ if (noteInserter) {
+ Note note(noteInserter->getCurrentNote());
+ if (note.getNoteType() == Note::Shortest ||
+ note.getNoteType() == Note::Longest)
+ return ;
+ noteInserter->slotSetDots(note.getDots() ? 0 : 1);
+ setTool(noteInserter);
+ } else {
+ RestInserter *restInserter = dynamic_cast<RestInserter *>(m_tool);
+ if (restInserter) {
+ Note note(restInserter->getCurrentNote());
+ if (note.getNoteType() == Note::Shortest ||
+ note.getNoteType() == Note::Longest)
+ return ;
+ restInserter->slotSetDots(note.getDots() ? 0 : 1);
+ setTool(restInserter);
+ } else {
+ KMessageBox::sorry(this, i18n("No note or rest duration selected"));
+ }
+ }
+
+ setMenuStates();
+}
+
+void NotationView::slotRespellDoubleFlat()
+{
+ if (!m_currentEventSelection)
+ return ;
+ KTmpStatusMsg msg(i18n("Forcing accidentals..."), this);
+
+ addCommandToHistory(new RespellCommand(RespellCommand::Set,
+ Accidentals::DoubleFlat,
+ *m_currentEventSelection));
+}
+
+void NotationView::slotRespellFlat()
+{
+ if (!m_currentEventSelection)
+ return ;
+ KTmpStatusMsg msg(i18n("Forcing accidentals..."), this);
+
+ addCommandToHistory(new RespellCommand(RespellCommand::Set,
+ Accidentals::Flat,
+ *m_currentEventSelection));
+}
+
+void NotationView::slotRespellNatural()
+{
+ if (!m_currentEventSelection)
+ return ;
+ KTmpStatusMsg msg(i18n("Forcing accidentals..."), this);
+
+ addCommandToHistory(new RespellCommand(RespellCommand::Set,
+ Accidentals::Natural,
+ *m_currentEventSelection));
+}
+
+void NotationView::slotRespellSharp()
+{
+ if (!m_currentEventSelection)
+ return ;
+ KTmpStatusMsg msg(i18n("Forcing accidentals..."), this);
+
+ addCommandToHistory(new RespellCommand(RespellCommand::Set,
+ Accidentals::Sharp,
+ *m_currentEventSelection));
+}
+
+void NotationView::slotRespellDoubleSharp()
+{
+ if (!m_currentEventSelection)
+ return ;
+ KTmpStatusMsg msg(i18n("Forcing accidentals..."), this);
+
+ addCommandToHistory(new RespellCommand(RespellCommand::Set,
+ Accidentals::DoubleSharp,
+ *m_currentEventSelection));
+}
+
+void NotationView::slotRespellUp()
+{
+ if (!m_currentEventSelection)
+ return ;
+ KTmpStatusMsg msg(i18n("Forcing accidentals..."), this);
+
+ addCommandToHistory(new RespellCommand(RespellCommand::Up,
+ Accidentals::NoAccidental,
+ *m_currentEventSelection));
+}
+
+void NotationView::slotRespellDown()
+{
+ if (!m_currentEventSelection)
+ return ;
+ KTmpStatusMsg msg(i18n("Forcing accidentals..."), this);
+
+ addCommandToHistory(new RespellCommand(RespellCommand::Down,
+ Accidentals::NoAccidental,
+ *m_currentEventSelection));
+}
+
+void NotationView::slotRespellRestore()
+{
+ if (!m_currentEventSelection)
+ return ;
+ KTmpStatusMsg msg(i18n("Restoring accidentals..."), this);
+
+ addCommandToHistory(new RespellCommand(RespellCommand::Restore,
+ Accidentals::NoAccidental,
+ *m_currentEventSelection));
+}
+
+void NotationView::slotShowCautionary()
+{
+ if (!m_currentEventSelection)
+ return ;
+ KTmpStatusMsg msg(i18n("Showing cautionary accidentals..."), this);
+
+ addCommandToHistory(new MakeAccidentalsCautionaryCommand
+ (true, *m_currentEventSelection));
+}
+
+void NotationView::slotCancelCautionary()
+{
+ if (!m_currentEventSelection)
+ return ;
+ KTmpStatusMsg msg(i18n("Cancelling cautionary accidentals..."), this);
+
+ addCommandToHistory(new MakeAccidentalsCautionaryCommand
+ (false, *m_currentEventSelection));
+}
+
+void NotationView::slotTransformsQuantize()
+{
+ if (!m_currentEventSelection)
+ return ;
+
+ QuantizeDialog dialog(this, true);
+
+ if (dialog.exec() == QDialog::Accepted) {
+ KTmpStatusMsg msg(i18n("Quantizing..."), this);
+ addCommandToHistory(new EventQuantizeCommand
+ (*m_currentEventSelection,
+ dialog.getQuantizer()));
+ }
+}
+
+void NotationView::slotTransformsInterpret()
+{
+ if (!m_currentEventSelection)
+ return ;
+
+ InterpretDialog dialog(this);
+
+ if (dialog.exec() == QDialog::Accepted) {
+ KTmpStatusMsg msg(i18n("Interpreting selection..."), this);
+ addCommandToHistory(new InterpretCommand
+ (*m_currentEventSelection,
+ getDocument()->getComposition().getNotationQuantizer(),
+ dialog.getInterpretations()));
+ }
+}
+
+void NotationView::slotSetNoteDurations(Note::Type type, bool notationOnly)
+{
+ if (!m_currentEventSelection)
+ return ;
+ KTmpStatusMsg msg(i18n("Setting note durations..."), this);
+ addCommandToHistory(new SetNoteTypeCommand(*m_currentEventSelection, type, notationOnly));
+}
+
+void NotationView::slotAddDot()
+{
+ if (!m_currentEventSelection)
+ return ;
+ KTmpStatusMsg msg(i18n("Adding dot..."), this);
+ addCommandToHistory(new AddDotCommand(*m_currentEventSelection, false));
+}
+
+void NotationView::slotAddDotNotationOnly()
+{
+ if (!m_currentEventSelection)
+ return ;
+ KTmpStatusMsg msg(i18n("Adding dot..."), this);
+ addCommandToHistory(new AddDotCommand(*m_currentEventSelection, true));
+}
+
+void NotationView::slotAddSlashes()
+{
+ const QObject *s = sender();
+ if (!m_currentEventSelection)
+ return ;
+
+ QString name = s->name();
+ int slashes = name.right(1).toInt();
+
+ addCommandToHistory(new AddSlashesCommand
+ (slashes, *m_currentEventSelection));
+}
+
+void NotationView::slotMarksAddTextMark()
+{
+ if (m_currentEventSelection) {
+ bool pressedOK = false;
+
+ QString txt = KLineEditDlg::getText(i18n("Text: "), "", &pressedOK, this);
+
+ if (pressedOK) {
+ addCommandToHistory(new AddTextMarkCommand
+ (qstrtostr(txt), *m_currentEventSelection));
+ }
+ }
+}
+
+void NotationView::slotMarksAddFingeringMark()
+{
+ if (m_currentEventSelection) {
+ bool pressedOK = false;
+
+ QString txt = KLineEditDlg::getText(i18n("Fingering: "), "", &pressedOK, this);
+
+ if (pressedOK) {
+ addCommandToHistory(new AddFingeringMarkCommand
+ (qstrtostr(txt), *m_currentEventSelection));
+ }
+ }
+}
+
+void NotationView::slotMarksAddFingeringMarkFromAction()
+{
+ const QObject *s = sender();
+ QString name = s->name();
+
+ if (name.left(14) == "add_fingering_") {
+
+ QString fingering = name.right(name.length() - 14);
+
+ if (fingering == "plus")
+ fingering = "+";
+
+ if (m_currentEventSelection) {
+ addCommandToHistory(new AddFingeringMarkCommand
+ (qstrtostr(fingering), *m_currentEventSelection));
+ }
+ }
+}
+
+void NotationView::slotMarksRemoveMarks()
+{
+ if (m_currentEventSelection)
+ addCommandToHistory(new RemoveMarksCommand
+ (*m_currentEventSelection));
+}
+
+void NotationView::slotMarksRemoveFingeringMarks()
+{
+ if (m_currentEventSelection)
+ addCommandToHistory(new RemoveFingeringMarksCommand
+ (*m_currentEventSelection));
+}
+
+void
+NotationView::slotMakeOrnament()
+{
+ if (!m_currentEventSelection)
+ return ;
+
+ EventSelection::eventcontainer &ec =
+ m_currentEventSelection->getSegmentEvents();
+
+ int basePitch = -1;
+ int baseVelocity = -1;
+ NoteStyle *style = NoteStyleFactory::getStyle(NoteStyleFactory::DefaultStyle);
+
+ for (EventSelection::eventcontainer::iterator i =
+ ec.begin(); i != ec.end(); ++i) {
+ if ((*i)->isa(Note::EventType)) {
+ if ((*i)->has(BaseProperties::PITCH)) {
+ basePitch = (*i)->get
+ <Int>
+ (BaseProperties::PITCH);
+ style = NoteStyleFactory::getStyleForEvent(*i);
+ if (baseVelocity != -1)
+ break;
+ }
+ if ((*i)->has(BaseProperties::VELOCITY)) {
+ baseVelocity = (*i)->get
+ <Int>
+ (BaseProperties::VELOCITY);
+ if (basePitch != -1)
+ break;
+ }
+ }
+ }
+
+ Staff *staff = getCurrentStaff();
+ Segment &segment = staff->getSegment();
+
+ timeT absTime = m_currentEventSelection->getStartTime();
+ timeT duration = m_currentEventSelection->getTotalDuration();
+ Note note(Note::getNearestNote(duration));
+
+ Track *track =
+ segment.getComposition()->getTrackById(segment.getTrack());
+ QString name;
+ int barNo = segment.getComposition()->getBarNumber(absTime);
+ if (track) {
+ name = QString(i18n("Ornament track %1 bar %2").arg(track->getPosition() + 1).arg(barNo + 1));
+ } else {
+ name = QString(i18n("Ornament bar %1").arg(barNo + 1));
+ }
+
+ MakeOrnamentDialog dialog(this, name, basePitch);
+ if (dialog.exec() != QDialog::Accepted)
+ return ;
+
+ name = dialog.getName();
+ basePitch = dialog.getBasePitch();
+
+ KMacroCommand *command = new KMacroCommand(i18n("Make Ornament"));
+
+ command->addCommand(new CutCommand
+ (*m_currentEventSelection,
+ getDocument()->getClipboard()));
+
+ command->addCommand(new PasteToTriggerSegmentCommand
+ (&getDocument()->getComposition(),
+ getDocument()->getClipboard(),
+ name, basePitch));
+
+ command->addCommand(new InsertTriggerNoteCommand
+ (segment, absTime, note, basePitch, baseVelocity,
+ style->getName(),
+ getDocument()->getComposition().getNextTriggerSegmentId(),
+ true,
+ BaseProperties::TRIGGER_SEGMENT_ADJUST_SQUISH,
+ Marks::NoMark)); //!!!
+
+ addCommandToHistory(command);
+}
+
+void
+NotationView::slotUseOrnament()
+{
+ // Take an existing note and match an ornament to it.
+
+ if (!m_currentEventSelection)
+ return ;
+
+ UseOrnamentDialog dialog(this, &getDocument()->getComposition());
+ if (dialog.exec() != QDialog::Accepted)
+ return ;
+
+ addCommandToHistory(new SetTriggerCommand(*m_currentEventSelection,
+ dialog.getId(),
+ true,
+ dialog.getRetune(),
+ dialog.getTimeAdjust(),
+ dialog.getMark(),
+ i18n("Use Ornament")));
+}
+
+void
+NotationView::slotRemoveOrnament()
+{
+ if (!m_currentEventSelection)
+ return ;
+
+ addCommandToHistory(new ClearTriggersCommand(*m_currentEventSelection,
+ i18n("Remove Ornaments")));
+}
+
+void NotationView::slotEditAddClef()
+{
+ Staff *staff = getCurrentStaff();
+ Segment &segment = staff->getSegment();
+ static Clef lastClef;
+ Clef clef;
+ Rosegarden::Key key;
+ timeT insertionTime = getInsertionTime(clef, key);
+
+ ClefDialog dialog(this, m_notePixmapFactory, lastClef);
+
+ if (dialog.exec() == QDialog::Accepted) {
+
+ ClefDialog::ConversionType conversion = dialog.getConversionType();
+
+ bool shouldChangeOctave = (conversion != ClefDialog::NoConversion);
+ bool shouldTranspose = (conversion == ClefDialog::Transpose);
+
+ addCommandToHistory
+ (new ClefInsertionCommand
+ (segment, insertionTime, dialog.getClef(),
+ shouldChangeOctave, shouldTranspose));
+
+ lastClef = dialog.getClef();
+ }
+}
+
+void NotationView::slotEditAddKeySignature()
+{
+ Staff *staff = getCurrentStaff();
+ Segment &segment = staff->getSegment();
+ Clef clef;
+ Rosegarden::Key key;
+ timeT insertionTime = getInsertionTime(clef, key);
+
+ //!!! experimental:
+ CompositionTimeSliceAdapter adapter
+ (&getDocument()->getComposition(), insertionTime,
+ getDocument()->getComposition().getDuration());
+ AnalysisHelper helper;
+ key = helper.guessKey(adapter);
+
+ KeySignatureDialog dialog
+ (this, m_notePixmapFactory, clef, key, true, true,
+ i18n("Estimated key signature shown"));
+
+ if (dialog.exec() == QDialog::Accepted &&
+ dialog.isValid()) {
+
+ KeySignatureDialog::ConversionType conversion =
+ dialog.getConversionType();
+
+ bool transposeKey = dialog.shouldBeTransposed();
+ bool applyToAll = dialog.shouldApplyToAll();
+ bool ignorePercussion = dialog.shouldIgnorePercussion();
+
+ if (applyToAll) {
+ addCommandToHistory
+ (new MultiKeyInsertionCommand
+ (getDocument(),
+ insertionTime, dialog.getKey(),
+ conversion == KeySignatureDialog::Convert,
+ conversion == KeySignatureDialog::Transpose,
+ transposeKey,
+ ignorePercussion));
+ } else {
+ addCommandToHistory
+ (new KeyInsertionCommand
+ (segment,
+ insertionTime, dialog.getKey(),
+ conversion == KeySignatureDialog::Convert,
+ conversion == KeySignatureDialog::Transpose,
+ transposeKey,
+ false));
+ }
+ }
+}
+
+void NotationView::slotEditAddSustain(bool down)
+{
+ Staff *staff = getCurrentStaff();
+ Segment &segment = staff->getSegment();
+ timeT insertionTime = getInsertionTime();
+
+ Studio *studio = &getDocument()->getStudio();
+ Track *track = segment.getComposition()->getTrackById(segment.getTrack());
+
+ if (track) {
+
+ Instrument *instrument = studio->getInstrumentById
+ (track->getInstrument());
+ if (instrument) {
+ MidiDevice *device = dynamic_cast<MidiDevice *>
+ (instrument->getDevice());
+ if (device) {
+ for (ControlList::const_iterator i =
+ device->getControlParameters().begin();
+ i != device->getControlParameters().end(); ++i) {
+
+ if (i->getType() == Controller::EventType &&
+ (i->getName() == "Sustain" ||
+ strtoqstr(i->getName()) == i18n("Sustain"))) {
+
+ addCommandToHistory
+ (new SustainInsertionCommand(segment, insertionTime, down,
+ i->getControllerValue()));
+ return ;
+ }
+ }
+ } else if (instrument->getDevice() &&
+ instrument->getDevice()->getType() == Device::SoftSynth) {
+ addCommandToHistory
+ (new SustainInsertionCommand(segment, insertionTime, down, 64));
+ }
+ }
+ }
+
+ KMessageBox::sorry(this, i18n("There is no sustain controller defined for this device.\nPlease ensure the device is configured correctly in the Manage MIDI Devices dialog in the main window."));
+}
+
+void NotationView::slotEditAddSustainDown()
+{
+ slotEditAddSustain(true);
+}
+
+void NotationView::slotEditAddSustainUp()
+{
+ slotEditAddSustain(false);
+}
+
+void NotationView::slotEditTranspose()
+{
+ IntervalDialog intervalDialog(this, true, true);
+ int ok = intervalDialog.exec();
+
+ int semitones = intervalDialog.getChromaticDistance();
+ int steps = intervalDialog.getDiatonicDistance();
+
+ if (!ok || (semitones == 0 && steps == 0)) return;
+
+ // TODO combine commands into one
+ for (int i = 0; i < m_segments.size(); i++)
+ {
+ addCommandToHistory(new SegmentTransposeCommand(*(m_segments[i]),
+ intervalDialog.getChangeKey(), steps, semitones,
+ intervalDialog.getTransposeSegmentBack()));
+ }
+}
+
+void NotationView::slotEditSwitchPreset()
+{
+ PresetHandlerDialog dialog(this, true);
+
+ if (dialog.exec() != QDialog::Accepted) return;
+
+ if (dialog.getConvertAllSegments()) {
+ // get all segments for this track and convert them.
+ Composition& comp = getDocument()->getComposition();
+ TrackId selectedTrack = getCurrentSegment()->getTrack();
+
+ // satisfy #1885251 the way that seems most reasonble to me at the
+ // moment, only changing track parameters when acting on all segments on
+ // this track from the notation view
+ //
+ //!!! This won't be undoable, and I'm not sure if that's seriously
+ // wrong, or just mildly wrong, but I'm betting somebody will tell me
+ // about it if this was inappropriate
+ Track *track = comp.getTrackById(selectedTrack);
+ track->setPresetLabel(dialog.getName());
+ track->setClef(dialog.getClef());
+ track->setTranspose(dialog.getTranspose());
+ track->setLowestPlayable(dialog.getLowRange());
+ track->setHighestPlayable(dialog.getHighRange());
+
+ addCommandToHistory(new SegmentSyncCommand(comp.getSegments(), selectedTrack,
+ dialog.getTranspose(),
+ dialog.getLowRange(),
+ dialog.getHighRange(),
+ clefIndexToClef(dialog.getClef())));
+ } else {
+ addCommandToHistory(new SegmentSyncCommand(m_segments,
+ dialog.getTranspose(),
+ dialog.getLowRange(),
+ dialog.getHighRange(),
+ clefIndexToClef(dialog.getClef())));
+ }
+
+ m_doc->slotDocumentModified();
+ emit updateView();
+}
+
+void NotationView::slotEditElement(NotationStaff *staff,
+ NotationElement *element, bool advanced)
+{
+ if (advanced) {
+
+ EventEditDialog dialog(this, *element->event(), true);
+
+ if (dialog.exec() == QDialog::Accepted &&
+ dialog.isModified()) {
+
+ EventEditCommand *command = new EventEditCommand
+ (staff->getSegment(),
+ element->event(),
+ dialog.getEvent());
+
+ addCommandToHistory(command);
+ }
+
+ } else if (element->event()->isa(Clef::EventType)) {
+
+ try {
+ ClefDialog dialog(this, m_notePixmapFactory,
+ Clef(*element->event()));
+
+ if (dialog.exec() == QDialog::Accepted) {
+
+ ClefDialog::ConversionType conversion = dialog.getConversionType();
+ bool shouldChangeOctave = (conversion != ClefDialog::NoConversion);
+ bool shouldTranspose = (conversion == ClefDialog::Transpose);
+ addCommandToHistory
+ (new ClefInsertionCommand
+ (staff->getSegment(), element->event()->getAbsoluteTime(),
+ dialog.getClef(), shouldChangeOctave, shouldTranspose));
+ }
+ } catch (Exception e) {
+ std::cerr << e.getMessage() << std::endl;
+ }
+
+ return ;
+
+ } else if (element->event()->isa(Rosegarden::Key::EventType)) {
+
+ try {
+ Clef clef(staff->getSegment().getClefAtTime
+ (element->event()->getAbsoluteTime()));
+ KeySignatureDialog dialog
+ (this, m_notePixmapFactory, clef, Rosegarden::Key(*element->event()),
+ false, true);
+
+ if (dialog.exec() == QDialog::Accepted &&
+ dialog.isValid()) {
+
+ KeySignatureDialog::ConversionType conversion =
+ dialog.getConversionType();
+
+ addCommandToHistory
+ (new KeyInsertionCommand
+ (staff->getSegment(),
+ element->event()->getAbsoluteTime(), dialog.getKey(),
+ conversion == KeySignatureDialog::Convert,
+ conversion == KeySignatureDialog::Transpose,
+ dialog.shouldBeTransposed(),
+ dialog.shouldIgnorePercussion()));
+ }
+
+ } catch (Exception e) {
+ std::cerr << e.getMessage() << std::endl;
+ }
+
+ return ;
+
+ } else if (element->event()->isa(Text::EventType)) {
+
+ try {
+ TextEventDialog dialog
+ (this, m_notePixmapFactory, Text(*element->event()));
+ if (dialog.exec() == QDialog::Accepted) {
+ TextInsertionCommand *command = new TextInsertionCommand
+ (staff->getSegment(),
+ element->event()->getAbsoluteTime(),
+ dialog.getText());
+ KMacroCommand *macroCommand = new KMacroCommand(command->name());
+ macroCommand->addCommand(new EraseEventCommand(staff->getSegment(),
+ element->event(), false));
+ macroCommand->addCommand(command);
+ addCommandToHistory(macroCommand);
+ }
+ } catch (Exception e) {
+ std::cerr << e.getMessage() << std::endl;
+ }
+
+ return ;
+
+ } else if (element->isNote() &&
+ element->event()->has(BaseProperties::TRIGGER_SEGMENT_ID)) {
+
+ int id = element->event()->get
+ <Int>
+ (BaseProperties::TRIGGER_SEGMENT_ID);
+ emit editTriggerSegment(id);
+ return ;
+
+ } else {
+
+ SimpleEventEditDialog dialog(this, getDocument(), *element->event(), false);
+
+ if (dialog.exec() == QDialog::Accepted &&
+ dialog.isModified()) {
+
+ EventEditCommand *command = new EventEditCommand
+ (staff->getSegment(),
+ element->event(),
+ dialog.getEvent());
+
+ addCommandToHistory(command);
+ }
+ }
+}
+
+void NotationView::slotBeginLilyPondRepeat()
+{}
+
+void NotationView::slotDebugDump()
+{
+ if (m_currentEventSelection) {
+ EventSelection::eventcontainer &ec =
+ m_currentEventSelection->getSegmentEvents();
+ int n = 0;
+ for (EventSelection::eventcontainer::iterator i =
+ ec.begin();
+ i != ec.end(); ++i) {
+ std::cerr << "\n" << n++ << " [" << (*i) << "]" << std::endl;
+ (*i)->dump(std::cerr);
+ }
+ }
+}
+
+void
+NotationView::slotSetPointerPosition(timeT time)
+{
+ slotSetPointerPosition(time, m_playTracking);
+}
+
+void
+NotationView::slotSetPointerPosition(timeT time, bool scroll)
+{
+ Composition &comp = getDocument()->getComposition();
+ int barNo = comp.getBarNumber(time);
+
+ int minCy = 0;
+ double cx = 0;
+ bool haveMinCy = false;
+
+ for (unsigned int i = 0; i < m_staffs.size(); ++i) {
+
+ double layoutX = m_hlayout->getXForTimeByEvent(time);
+ Segment &seg = m_staffs[i]->getSegment();
+
+ bool good = true;
+
+ if (barNo >= m_hlayout->getLastVisibleBarOnStaff(*m_staffs[i])) {
+ if (seg.isRepeating() && time < seg.getRepeatEndTime()) {
+ timeT mappedTime =
+ seg.getStartTime() +
+ ((time - seg.getStartTime()) %
+ (seg.getEndMarkerTime() - seg.getStartTime()));
+ layoutX = m_hlayout->getXForTimeByEvent(mappedTime);
+ } else {
+ good = false;
+ }
+ } else if (barNo < m_hlayout->getFirstVisibleBarOnStaff(*m_staffs[i])) {
+ good = false;
+ }
+
+ if (!good) {
+
+ m_staffs[i]->hidePointer();
+
+ } else {
+
+ m_staffs[i]->setPointerPosition(layoutX);
+
+ int cy;
+ m_staffs[i]->getPointerPosition(cx, cy);
+
+ if (!haveMinCy || cy < minCy) {
+ minCy = cy;
+ haveMinCy = true;
+ }
+ }
+ }
+
+ if (m_pageMode == LinedStaff::LinearMode) {
+ // be careful not to prevent user from scrolling up and down
+ haveMinCy = false;
+ }
+
+ if (scroll) {
+ getCanvasView()->slotScrollHoriz(int(cx));
+ if (haveMinCy) {
+ getCanvasView()->slotScrollVertToTop(minCy);
+ }
+ }
+
+ updateView();
+}
+
+void
+NotationView::slotUpdateRecordingSegment(Segment *segment,
+ timeT updateFrom)
+{
+ NOTATION_DEBUG << "NotationView::slotUpdateRecordingSegment: segment " << segment << ", updateFrom " << updateFrom << ", end time " << segment->getEndMarkerTime() << endl;
+ if (updateFrom >= segment->getEndMarkerTime())
+ return ;
+ for (unsigned int i = 0; i < m_staffs.size(); ++i) {
+ if (&m_staffs[i]->getSegment() == segment) {
+ refreshSegment(segment, 0, 0);
+ }
+ }
+ NOTATION_DEBUG << "NotationView::slotUpdateRecordingSegment: don't have segment " << segment << endl;
+}
+
+void
+NotationView::slotSetCurrentStaff(double x, int y)
+{
+ unsigned int staffNo;
+ for (staffNo = 0; staffNo < m_staffs.size(); ++staffNo) {
+ if (m_staffs[staffNo]->containsCanvasCoords(x, y))
+ break;
+ }
+
+ if (staffNo < m_staffs.size()) {
+ slotSetCurrentStaff(staffNo);
+ }
+}
+
+void
+NotationView::slotSetCurrentStaff(int staffNo)
+{
+ NOTATION_DEBUG << "NotationView::slotSetCurrentStaff(" << staffNo << ")" << endl;
+
+ if (m_currentStaff != staffNo) {
+
+ m_staffs[m_currentStaff]->setCurrent(false);
+
+ m_currentStaff = staffNo;
+
+ m_staffs[m_currentStaff]->setCurrent(true);
+
+ Segment *segment = &m_staffs[m_currentStaff]->getSegment();
+
+ m_chordNameRuler->setCurrentSegment(segment);
+ m_rawNoteRuler->setCurrentSegment(segment);
+ m_rawNoteRuler->repaint();
+ setControlRulersCurrentSegment();
+
+ updateView();
+
+ slotSetInsertCursorPosition(getInsertionTime(), false, false);
+
+ m_headersGroup->setCurrent(
+ m_staffs[staffNo]->getSegment().getTrack());
+ }
+}
+
+void
+NotationView::slotCurrentStaffUp()
+{
+ LinedStaff *staff = getStaffAbove();
+ if (!staff) return;
+ slotSetCurrentStaff(staff->getId());
+}
+
+void
+NotationView::slotCurrentStaffDown()
+{
+ LinedStaff *staff = getStaffBelow();
+ if (!staff) return;
+ slotSetCurrentStaff(staff->getId());
+}
+
+void
+NotationView::slotCurrentSegmentPrior()
+{
+ if (m_staffs.size() < 2)
+ return ;
+
+ Composition *composition =
+ m_staffs[m_currentStaff]->getSegment().getComposition();
+
+ Track *track = composition->
+ getTrackById(m_staffs[m_currentStaff]->getSegment().getTrack());
+ if (!track)
+ return ;
+
+ int lastStaffOnTrack = -1;
+
+ //
+ // TODO: Cycle segments through rather in time order?
+ // Cycle only segments in the field of view?
+ //
+ for (int i = m_staffs.size()-1; i >= 0; --i) {
+ if (m_staffs[i]->getSegment().getTrack() == track->getId()) {
+ if (lastStaffOnTrack < 0) {
+ lastStaffOnTrack = i;
+ }
+ if (i < m_currentStaff) {
+ slotSetCurrentStaff(i);
+ slotEditSelectWholeStaff();
+ return ;
+ }
+ }
+ }
+ if (lastStaffOnTrack >= 0) {
+ slotSetCurrentStaff(lastStaffOnTrack);
+ slotEditSelectWholeStaff();
+ return ;
+ }
+}
+
+void
+NotationView::slotCurrentSegmentNext()
+{
+ if (m_staffs.size() < 2)
+ return ;
+
+ Composition *composition =
+ m_staffs[m_currentStaff]->getSegment().getComposition();
+
+ Track *track = composition->
+ getTrackById(m_staffs[m_currentStaff]->getSegment().getTrack());
+ if (!track)
+ return ;
+
+ int firstStaffOnTrack = -1;
+
+ //
+ // TODO: Cycle segments through rather in time order?
+ // Cycle only segments in the field of view?
+ //
+ for (unsigned int i = 0; i < m_staffs.size(); ++i) {
+ if (m_staffs[i]->getSegment().getTrack() == track->getId()) {
+ if (firstStaffOnTrack < 0) {
+ firstStaffOnTrack = i;
+ }
+ if (i > m_currentStaff) {
+ slotSetCurrentStaff(i);
+ slotEditSelectWholeStaff();
+ return ;
+ }
+ }
+ }
+ if (firstStaffOnTrack >= 0) {
+ slotSetCurrentStaff(firstStaffOnTrack);
+ slotEditSelectWholeStaff();
+ return ;
+ }
+}
+
+void
+NotationView::slotSetInsertCursorPosition(double x, int y, bool scroll,
+ bool updateNow)
+{
+ NOTATION_DEBUG << "NotationView::slotSetInsertCursorPosition: x " << x << ", y " << y << ", scroll " << scroll << ", now " << updateNow << endl;
+
+ slotSetCurrentStaff(x, y);
+
+ LinedStaff *staff = getLinedStaff(m_currentStaff);
+ Event *clefEvt, *keyEvt;
+ NotationElementList::iterator i =
+ staff->getElementUnderCanvasCoords(x, y, clefEvt, keyEvt);
+
+ if (i == staff->getViewElementList()->end()) {
+ slotSetInsertCursorPosition(staff->getSegment().getEndTime(), scroll,
+ updateNow);
+ } else {
+ slotSetInsertCursorPosition((*i)->getViewAbsoluteTime(), scroll,
+ updateNow);
+ }
+}
+
+void
+NotationView::slotSetInsertCursorPosition(timeT t, bool scroll, bool updateNow)
+{
+ NOTATION_DEBUG << "NotationView::slotSetInsertCursorPosition: time " << t << ", scroll " << scroll << ", now " << updateNow << endl;
+
+ m_insertionTime = t;
+ if (scroll) {
+ m_deferredCursorMove = CursorMoveAndMakeVisible;
+ } else {
+ m_deferredCursorMove = CursorMoveOnly;
+ }
+ if (updateNow)
+ doDeferredCursorMove();
+}
+
+void
+NotationView::slotSetInsertCursorAndRecentre(timeT t, double cx, int,
+ bool updateNow)
+{
+ NOTATION_DEBUG << "NotationView::slotSetInsertCursorAndRecentre: time " << t << ", cx " << cx << ", now " << updateNow << ", contentsx" << getCanvasView()->contentsX() << ", w " << getCanvasView()->visibleWidth() << endl;
+
+ m_insertionTime = t;
+
+ // We only do the scroll bit if cx is in the right two-thirds of
+ // the window
+
+ if (cx < (getCanvasView()->contentsX() +
+ getCanvasView()->visibleWidth() / 3)) {
+
+ m_deferredCursorMove = CursorMoveOnly;
+ } else {
+ m_deferredCursorMove = CursorMoveAndScrollToPosition;
+ m_deferredCursorScrollToX = cx;
+ }
+
+ if (updateNow)
+ doDeferredCursorMove();
+}
+
+void
+NotationView::doDeferredCursorMove()
+{
+ NOTATION_DEBUG << "NotationView::doDeferredCursorMove: m_deferredCursorMove == " << m_deferredCursorMove << endl;
+
+ if (m_deferredCursorMove == NoCursorMoveNeeded) {
+ return ;
+ }
+
+ DeferredCursorMoveType type = m_deferredCursorMove;
+ m_deferredCursorMove = NoCursorMoveNeeded;
+
+ timeT t = m_insertionTime;
+
+ if (m_staffs.size() == 0)
+ return ;
+ LinedStaff *staff = getCurrentLinedStaff();
+ Segment &segment = staff->getSegment();
+
+ if (t < segment.getStartTime()) {
+ t = segment.getStartTime();
+ }
+ if (t > segment.getEndTime()) {
+ t = segment.getEndTime();
+ }
+
+ NotationElementList::iterator i =
+ staff->getViewElementList()->findNearestTime(t);
+
+ while (i != staff->getViewElementList()->end() &&
+ !static_cast<NotationElement*>(*i)->getCanvasItem())
+ ++i;
+
+ if (i == staff->getViewElementList()->end()) {
+ //!!! ???
+ if (m_insertionTime >= staff->getSegment().getStartTime()) {
+ i = staff->getViewElementList()->begin();
+ }
+ m_insertionTime = staff->getSegment().getStartTime();
+ } else {
+ m_insertionTime = static_cast<NotationElement*>(*i)->getViewAbsoluteTime();
+ }
+
+ if (i == staff->getViewElementList()->end() ||
+ t == segment.getEndTime() ||
+ t == segment.getBarStartForTime(t)) {
+
+ staff->setInsertCursorPosition(*m_hlayout, t);
+
+ if (type == CursorMoveAndMakeVisible) {
+ double cx;
+ int cy;
+ staff->getInsertCursorPosition(cx, cy);
+ getCanvasView()->slotScrollHoriz(int(cx));
+ getCanvasView()->slotScrollVertSmallSteps(cy);
+ }
+
+ } else {
+
+ // prefer a note or rest, if there is one, to a non-spacing event
+ if (!static_cast<NotationElement*>(*i)->isNote() &&
+ !static_cast<NotationElement*>(*i)->isRest()) {
+ NotationElementList::iterator j = i;
+ while (j != staff->getViewElementList()->end()) {
+ if (static_cast<NotationElement*>(*j)->getViewAbsoluteTime() !=
+ static_cast<NotationElement*>(*i)->getViewAbsoluteTime())
+ break;
+ if (static_cast<NotationElement*>(*j)->getCanvasItem()) {
+ if (static_cast<NotationElement*>(*j)->isNote() ||
+ static_cast<NotationElement*>(*j)->isRest()) {
+ i = j;
+ break;
+ }
+ }
+ ++j;
+ }
+ }
+
+ if (static_cast<NotationElement*>(*i)->getCanvasItem()) {
+
+ staff->setInsertCursorPosition
+ (static_cast<NotationElement*>(*i)->getCanvasX() - 2,
+ int(static_cast<NotationElement*>(*i)->getCanvasY()));
+
+ if (type == CursorMoveAndMakeVisible) {
+ getCanvasView()->slotScrollHoriz
+ (int(static_cast<NotationElement*>(*i)->getCanvasX()) - 4);
+ }
+ } else {
+ std::cerr << "WARNING: No canvas item for this notation element:";
+ (*i)->event()->dump(std::cerr);
+ }
+ }
+
+ if (type == CursorMoveAndScrollToPosition) {
+
+ // get current canvas x of insert cursor, which might not be
+ // what we just set
+
+ double ccx = 0.0;
+
+ NotationElementList::iterator i =
+ staff->getViewElementList()->findTime(t);
+
+ if (i == staff->getViewElementList()->end()) {
+ if (i == staff->getViewElementList()->begin())
+ return ;
+ double lx, lwidth;
+ --i;
+ if (static_cast<NotationElement*>(*i)->getCanvasItem()) {
+ ccx = static_cast<NotationElement*>(*i)->getCanvasX();
+ static_cast<NotationElement*>(*i)->getLayoutAirspace(lx, lwidth);
+ } else {
+ std::cerr << "WARNING: No canvas item for this notation element*:";
+ (*i)->event()->dump(std::cerr);
+ }
+ ccx += lwidth;
+ } else {
+ if (static_cast<NotationElement*>(*i)->getCanvasItem()) {
+ ccx = static_cast<NotationElement*>(*i)->getCanvasX();
+ } else {
+ std::cerr << "WARNING: No canvas item for this notation element*:";
+ (*i)->event()->dump(std::cerr);
+ }
+ }
+
+ QScrollBar* hbar = getCanvasView()->horizontalScrollBar();
+ hbar->setValue(int(hbar->value() - (m_deferredCursorScrollToX - ccx)));
+ }
+
+ updateView();
+}
+
+void
+NotationView::slotJumpCursorToPlayback()
+{
+ slotSetInsertCursorPosition(getDocument()->getComposition().getPosition());
+}
+
+void
+NotationView::slotJumpPlaybackToCursor()
+{
+ emit jumpPlaybackTo(getInsertionTime());
+}
+
+void
+NotationView::slotToggleTracking()
+{
+ m_playTracking = !m_playTracking;
+}
+
+void NotationView::slotNoAccidental()
+{
+ emit changeAccidental(Accidentals::NoAccidental, false);
+}
+
+void NotationView::slotFollowAccidental()
+{
+ emit changeAccidental(Accidentals::NoAccidental, true);
+}
+
+void NotationView::slotSharp()
+{
+ emit changeAccidental(Accidentals::Sharp, false);
+}
+
+void NotationView::slotFlat()
+{
+ emit changeAccidental(Accidentals::Flat, false);
+}
+
+void NotationView::slotNatural()
+{
+ emit changeAccidental(Accidentals::Natural, false);
+}
+
+void NotationView::slotDoubleSharp()
+{
+ emit changeAccidental(Accidentals::DoubleSharp, false);
+}
+
+void NotationView::slotDoubleFlat()
+{
+ emit changeAccidental(Accidentals::DoubleFlat, false);
+}
+
+void NotationView::slotTrebleClef()
+{
+ m_currentNotePixmap->setPixmap
+ (NotePixmapFactory::toQPixmap(NotePixmapFactory::makeToolbarPixmap("clef-treble")));
+ setTool(m_toolBox->getTool(ClefInserter::ToolName));
+
+ dynamic_cast<ClefInserter*>(m_tool)->setClef(Clef::Treble);
+ setMenuStates();
+}
+
+void NotationView::slotAltoClef()
+{
+ m_currentNotePixmap->setPixmap
+ (NotePixmapFactory::toQPixmap(NotePixmapFactory::makeToolbarPixmap("clef-alto")));
+ setTool(m_toolBox->getTool(ClefInserter::ToolName));
+
+ dynamic_cast<ClefInserter*>(m_tool)->setClef(Clef::Alto);
+ setMenuStates();
+}
+
+void NotationView::slotTenorClef()
+{
+ m_currentNotePixmap->setPixmap
+ (NotePixmapFactory::toQPixmap(NotePixmapFactory::makeToolbarPixmap("clef-tenor")));
+ setTool(m_toolBox->getTool(ClefInserter::ToolName));
+
+ dynamic_cast<ClefInserter*>(m_tool)->setClef(Clef::Tenor);
+ setMenuStates();
+}
+
+void NotationView::slotBassClef()
+{
+ m_currentNotePixmap->setPixmap
+ (NotePixmapFactory::toQPixmap(NotePixmapFactory::makeToolbarPixmap("clef-bass")));
+ setTool(m_toolBox->getTool(ClefInserter::ToolName));
+
+ dynamic_cast<ClefInserter*>(m_tool)->setClef(Clef::Bass);
+ setMenuStates();
+}
+
+void NotationView::slotText()
+{
+ m_currentNotePixmap->setPixmap
+ (NotePixmapFactory::toQPixmap(NotePixmapFactory::makeToolbarPixmap("text")));
+ setTool(m_toolBox->getTool(TextInserter::ToolName));
+ setMenuStates();
+}
+
+void NotationView::slotGuitarChord()
+{
+ m_currentNotePixmap->setPixmap
+ (NotePixmapFactory::toQPixmap(NotePixmapFactory::makeToolbarPixmap("guitarchord")));
+ setTool(m_toolBox->getTool(GuitarChordInserter::ToolName));
+ setMenuStates();
+}
+
+void NotationView::slotEraseSelected()
+{
+ NOTATION_DEBUG << "NotationView::slotEraseSelected()" << endl;
+ setTool(m_toolBox->getTool(NotationEraser::ToolName));
+ setMenuStates();
+}
+
+void NotationView::slotSelectSelected()
+{
+ NOTATION_DEBUG << "NotationView::slotSelectSelected()" << endl;
+ setTool(m_toolBox->getTool(NotationSelector::ToolName));
+ setMenuStates();
+}
+
+void NotationView::slotLinearMode()
+{
+ setPageMode(LinedStaff::LinearMode);
+}
+
+void NotationView::slotContinuousPageMode()
+{
+ setPageMode(LinedStaff::ContinuousPageMode);
+}
+
+void NotationView::slotMultiPageMode()
+{
+ setPageMode(LinedStaff::MultiPageMode);
+}
+
+void NotationView::slotToggleChordsRuler()
+{
+ if (m_hlayout->isPageMode())
+ return ;
+ toggleWidget(m_chordNameRuler, "show_chords_ruler");
+}
+
+void NotationView::slotToggleRawNoteRuler()
+{
+ if (m_hlayout->isPageMode())
+ return ;
+ toggleWidget(m_rawNoteRuler, "show_raw_note_ruler");
+}
+
+void NotationView::slotToggleTempoRuler()
+{
+ if (m_hlayout->isPageMode())
+ return ;
+ toggleWidget(m_tempoRuler, "show_tempo_ruler");
+}
+
+void NotationView::slotToggleAnnotations()
+{
+ m_annotationsVisible = !m_annotationsVisible;
+ slotUpdateAnnotationsStatus();
+ //!!! use refresh mechanism
+ refreshSegment(0, 0, 0);
+}
+
+void NotationView::slotToggleLilyPondDirectives()
+{
+ m_lilyPondDirectivesVisible = !m_lilyPondDirectivesVisible;
+ slotUpdateLilyPondDirectivesStatus();
+ //!!! use refresh mechanism
+ refreshSegment(0, 0, 0);
+}
+
+void NotationView::slotEditLyrics()
+{
+ Staff *staff = getCurrentStaff();
+ Segment &segment = staff->getSegment();
+
+ LyricEditDialog dialog(this, &segment);
+
+ if (dialog.exec() == QDialog::Accepted) {
+
+ KMacroCommand *macro = new KMacroCommand
+ (SetLyricsCommand::getGlobalName());
+
+ for (int i = 0; i < dialog.getVerseCount(); ++i) {
+ SetLyricsCommand *command = new SetLyricsCommand
+ (&segment, i, dialog.getLyricData(i));
+ macro->addCommand(command);
+ }
+
+ addCommandToHistory(macro);
+ }
+}
+
+void NotationView::slotItemPressed(int height, int staffNo,
+ QMouseEvent* e,
+ NotationElement* el)
+{
+ NOTATION_DEBUG << "NotationView::slotItemPressed(height = "
+ << height << ", staffNo = " << staffNo
+ << ")" << endl;
+
+ if (staffNo < 0 && el != 0) {
+ // We have an element but no staff -- that's because the
+ // element extended outside the staff region. But we need
+ // to handle it properly, so we rather laboriously need to
+ // find out which staff it was.
+ for (unsigned int i = 0; i < m_staffs.size(); ++i) {
+ if (m_staffs[i]->getViewElementList()->findSingle(el) !=
+ m_staffs[i]->getViewElementList()->end()) {
+ staffNo = m_staffs[i]->getId();
+ break;
+ }
+ }
+ }
+
+ ButtonState btnState = e->state();
+
+ if (btnState & ControlButton) { // on ctrl-click, set cursor position
+
+ slotSetInsertCursorPosition(e->x(), (int)e->y());
+
+ } else {
+
+ setActiveItem(0);
+
+ timeT unknownTime = 0;
+
+ if (e->type() == QEvent::MouseButtonDblClick) {
+ m_tool->handleMouseDoubleClick(unknownTime, height,
+ staffNo, e, el);
+ } else {
+ m_tool->handleMousePress(unknownTime, height,
+ staffNo, e, el);
+ }
+ }
+}
+
+void NotationView::slotNonNotationItemPressed(QMouseEvent *e, QCanvasItem *it)
+{
+ if (e->type() != QEvent::MouseButtonDblClick)
+ return ;
+
+ Staff *staff = getStaffForCanvasCoords(e->x(), e->y());
+ if (!staff)
+ return ;
+
+ NOTATION_DEBUG << "NotationView::slotNonNotationItemPressed(doubly)" << endl;
+
+ if (dynamic_cast<QCanvasStaffNameSprite *>(it)) {
+
+ std::string name =
+ staff->getSegment().getComposition()->
+ getTrackById(staff->getSegment().getTrack())->getLabel();
+
+ bool ok = false;
+ QRegExpValidator validator(QRegExp(".*"), this); // empty is OK
+
+ QString newText = KLineEditDlg::getText(QString("Change staff name"),
+ QString("Enter new staff name"),
+ strtoqstr(name),
+ &ok,
+ this,
+ &validator);
+
+ if (ok) {
+ addCommandToHistory(new RenameTrackCommand
+ (staff->getSegment().getComposition(),
+ staff->getSegment().getTrack(),
+ qstrtostr(newText)));
+
+ emit staffLabelChanged(staff->getSegment().getTrack(), newText);
+ }
+
+ } else if (dynamic_cast<QCanvasTimeSigSprite *>(it)) {
+
+ double layoutX = (dynamic_cast<QCanvasTimeSigSprite *>(it))->getLayoutX();
+ emit editTimeSignature(m_hlayout->getTimeForX(layoutX));
+ }
+}
+
+void NotationView::slotTextItemPressed(QMouseEvent *e, QCanvasItem *it)
+{
+ if (e->type() != QEvent::MouseButtonDblClick)
+ return ;
+
+ if (it == m_title) {
+ emit editMetadata(strtoqstr(CompositionMetadataKeys::Title.getName()));
+ } else if (it == m_subtitle) {
+ emit editMetadata(strtoqstr(CompositionMetadataKeys::Subtitle.getName()));
+ } else if (it == m_composer) {
+ emit editMetadata(strtoqstr(CompositionMetadataKeys::Composer.getName()));
+ } else if (it == m_copyright) {
+ emit editMetadata(strtoqstr(CompositionMetadataKeys::Copyright.getName()));
+ } else {
+ return ;
+ }
+
+ positionStaffs();
+}
+
+void NotationView::slotMouseMoved(QMouseEvent *e)
+{
+ if (activeItem()) {
+ activeItem()->handleMouseMove(e);
+ updateView();
+ } else {
+ int follow = m_tool->handleMouseMove(0, 0, // unknown time and height
+ e);
+
+ if (getCanvasView()->isTimeForSmoothScroll()) {
+
+ if (follow & RosegardenCanvasView::FollowHorizontal) {
+ getCanvasView()->slotScrollHorizSmallSteps(e->x());
+ }
+
+ if (follow & RosegardenCanvasView::FollowVertical) {
+ getCanvasView()->slotScrollVertSmallSteps(e->y());
+ }
+
+ }
+ }
+}
+
+void NotationView::slotMouseReleased(QMouseEvent *e)
+{
+ if (activeItem()) {
+ activeItem()->handleMouseRelease(e);
+ setActiveItem(0);
+ updateView();
+ } else
+ m_tool->handleMouseRelease(0, 0, // unknown time and height
+ e);
+}
+
+void
+NotationView::slotHoveredOverNoteChanged(const QString &noteName)
+{
+ m_hoveredOverNoteName->setText(QString(" ") + noteName);
+}
+
+void
+NotationView::slotHoveredOverAbsoluteTimeChanged(unsigned int time)
+{
+ timeT t = time;
+ RealTime rt =
+ getDocument()->getComposition().getElapsedRealTime(t);
+ long ms = rt.msec();
+
+ int bar, beat, fraction, remainder;
+ getDocument()->getComposition().getMusicalTimeForAbsoluteTime
+ (t, bar, beat, fraction, remainder);
+
+ // QString message;
+ // QString format("%ld (%ld.%03lds)");
+ // format = i18n("Time: %1").arg(format);
+ // message.sprintf(format, t, rt.sec, ms);
+
+ QString message = i18n("Time: %1 (%2.%3s)")
+ .arg(QString("%1-%2-%3-%4")
+ .arg(QString("%1").arg(bar + 1).rightJustify(3, '0'))
+ .arg(QString("%1").arg(beat).rightJustify(2, '0'))
+ .arg(QString("%1").arg(fraction).rightJustify(2, '0'))
+ .arg(QString("%1").arg(remainder).rightJustify(2, '0')))
+ .arg(rt.sec)
+ .arg(QString("%1").arg(ms).rightJustify(3, '0'));
+
+ m_hoveredOverAbsoluteTime->setText(message);
+}
+
+void
+NotationView::slotInsertableNoteEventReceived(int pitch, int velocity, bool noteOn)
+{
+ //!!! Problematic. Ideally we wouldn't insert events into windows
+ //that weren't actually visible, otherwise all hell could break
+ //loose (metaphorically speaking, I should probably add). I did
+ //think of checking isActiveWindow() and returning if the current
+ //window wasn't active, but that will prevent anyone from
+ //step-recording from e.g. vkeybd, which cannot be used without
+ //losing focus (and thus active-ness) from the Rosegarden window.
+
+ //!!! I know -- we'll keep track of which edit view (or main view,
+ //or mixer, etc) is active, and we'll only allow insertion into
+ //the most recently activated. How about that?
+
+ KToggleAction *action = dynamic_cast<KToggleAction *>
+ (actionCollection()->action("toggle_step_by_step"));
+ if (!action) {
+ NOTATION_DEBUG << "WARNING: No toggle_step_by_step action" << endl;
+ return ;
+ }
+ if (!action->isChecked())
+ return ;
+
+ Segment &segment = m_staffs[m_currentStaff]->getSegment();
+
+ NoteInserter *noteInserter = dynamic_cast<NoteInserter *>(m_tool);
+ if (!noteInserter) {
+ static bool showingError = false;
+ if (showingError)
+ return ;
+ showingError = true;
+ KMessageBox::sorry(this, i18n("Can't insert note: No note duration selected"));
+ showingError = false;
+ return ;
+ }
+
+ if (m_inPaintEvent) {
+ NOTATION_DEBUG << "NotationView::slotInsertableNoteEventReceived: in paint event already" << endl;
+ if (noteOn) {
+ m_pendingInsertableNotes.push_back(std::pair<int, int>(pitch, velocity));
+ }
+ return ;
+ }
+
+ // If the segment is transposed, we want to take that into
+ // account. But the note has already been played back to the user
+ // at its untransposed pitch, because that's done by the MIDI THRU
+ // code in the sequencer which has no way to know whether a note
+ // was intended for step recording. So rather than adjust the
+ // pitch for playback according to the transpose setting, we have
+ // to adjust the stored pitch in the opposite direction.
+
+ pitch -= segment.getTranspose();
+
+ // KTmpStatusMsg msg(i18n("Inserting note"), this);
+
+ // We need to ensure that multiple notes hit at once come out as
+ // chords, without imposing the interpretation that overlapping
+ // notes are always chords and without getting too involved with
+ // the actual absolute times of the notes (this is still step
+ // editing, not proper recording).
+
+ // First, if we're in chord mode, there's no problem.
+
+ static int numberOfNotesOn = 0;
+ static timeT insertionTime = getInsertionTime();
+ static time_t lastInsertionTime = 0;
+
+ if (isInChordMode()) {
+ if (!noteOn)
+ return ;
+ NOTATION_DEBUG << "Inserting note in chord at pitch " << pitch << endl;
+ noteInserter->insertNote(segment, getInsertionTime(), pitch,
+ Accidentals::NoAccidental,
+ true);
+
+ } else {
+
+ if (!noteOn) {
+ numberOfNotesOn--;
+ } else if (noteOn) {
+ // Rules:
+ //
+ // * If no other note event has turned up within half a
+ // second, insert this note and advance.
+ //
+ // * Relatedly, if this note is within half a second of
+ // the previous one, they're chords. Insert the previous
+ // one, don't advance, and use the same rules for this.
+ //
+ // * If a note event turns up before that time has elapsed,
+ // we need to wait for the note-off events: if the second
+ // note happened less than half way through the first,
+ // it's a chord.
+ //
+ // We haven't implemented these yet... For now:
+ //
+ // Rules (hjj):
+ //
+ // * The overlapping notes are always included in to a chord.
+ // This is the most convenient for step inserting of chords.
+ //
+ // * The timer resets the numberOfNotesOn, if noteOff signals were
+ // drop out for some reason (which has not been encountered yet).
+
+ time_t now;
+ time (&now);
+ double elapsed = difftime(now, lastInsertionTime);
+ time (&lastInsertionTime);
+
+ if (numberOfNotesOn <= 0 || elapsed > 10.0 ) {
+ numberOfNotesOn = 0;
+ insertionTime = getInsertionTime();
+ }
+ numberOfNotesOn++;
+
+ noteInserter->insertNote(segment, insertionTime, pitch,
+ Accidentals::NoAccidental,
+ true);
+ }
+ }
+}
+
+void
+NotationView::slotInsertableNoteOnReceived(int pitch, int velocity)
+{
+ NOTATION_DEBUG << "NotationView::slotInsertableNoteOnReceived: " << pitch << endl;
+ slotInsertableNoteEventReceived(pitch, velocity, true);
+}
+
+void
+NotationView::slotInsertableNoteOffReceived(int pitch, int velocity)
+{
+ NOTATION_DEBUG << "NotationView::slotInsertableNoteOffReceived: " << pitch << endl;
+ slotInsertableNoteEventReceived(pitch, velocity, false);
+}
+
+void
+NotationView::slotInsertableTimerElapsed()
+{}
+
+void
+NotationView::slotToggleStepByStep()
+{
+ KToggleAction *action = dynamic_cast<KToggleAction *>
+ (actionCollection()->action("toggle_step_by_step"));
+ if (!action) {
+ NOTATION_DEBUG << "WARNING: No toggle_step_by_step action" << endl;
+ return ;
+ }
+ if (action->isChecked()) { // after toggling, that is
+ emit stepByStepTargetRequested(this);
+ } else {
+ emit stepByStepTargetRequested(0);
+ }
+}
+
+void
+NotationView::slotStepByStepTargetRequested(QObject *obj)
+{
+ KToggleAction *action = dynamic_cast<KToggleAction *>
+ (actionCollection()->action("toggle_step_by_step"));
+ if (!action) {
+ NOTATION_DEBUG << "WARNING: No toggle_step_by_step action" << endl;
+ return ;
+ }
+ action->setChecked(obj == this);
+}
+
+void
+NotationView::slotCheckRendered(double cx0, double cx1)
+{
+ // NOTATION_DEBUG << "slotCheckRendered(" << cx0 << "," << cx1 << ")" << endl;
+
+ bool something = false;
+
+ for (size_t i = 0; i < m_staffs.size(); ++i) {
+
+ LinedStaff *staff = m_staffs[i];
+
+ LinedStaff::LinedStaffCoords cc0 = staff->getLayoutCoordsForCanvasCoords
+ (cx0, 0);
+
+ LinedStaff::LinedStaffCoords cc1 = staff->getLayoutCoordsForCanvasCoords
+ (cx1, staff->getTotalHeight() + staff->getY());
+
+ timeT t0 = m_hlayout->getTimeForX(cc0.first);
+ timeT t1 = m_hlayout->getTimeForX(cc1.first);
+
+ if (dynamic_cast<NotationStaff *>(staff)->checkRendered(t0, t1)) {
+ something = true; //!!!
+ }
+ }
+
+ if (something) {
+ emit renderComplete();
+ if (m_renderTimer)
+ delete m_renderTimer;
+ m_renderTimer = new QTimer(this);
+ connect(m_renderTimer, SIGNAL(timeout()), SLOT(slotRenderSomething()));
+ m_renderTimer->start(0, true);
+ }
+
+ if (m_deferredCursorMove != NoCursorMoveNeeded)
+ doDeferredCursorMove();
+}
+
+void
+NotationView::slotRenderSomething()
+{
+ delete m_renderTimer;
+ m_renderTimer = 0;
+ static clock_t lastWork = 0;
+
+ clock_t now = clock();
+ long elapsed = ((now - lastWork) * 1000 / CLOCKS_PER_SEC);
+ if (elapsed < 70) {
+ m_renderTimer = new QTimer(this);
+ connect(m_renderTimer, SIGNAL(timeout()), SLOT(slotRenderSomething()));
+ m_renderTimer->start(0, true);
+ return ;
+ }
+ lastWork = now;
+
+ for (size_t i = 0; i < m_staffs.size(); ++i) {
+
+ if (m_staffs[i]->doRenderWork(m_staffs[i]->getSegment().getStartTime(),
+ m_staffs[i]->getSegment().getEndTime())) {
+ m_renderTimer = new QTimer(this);
+ connect(m_renderTimer, SIGNAL(timeout()), SLOT(slotRenderSomething()));
+ m_renderTimer->start(0, true);
+ return ;
+ }
+ }
+
+ PixmapArrayGC::deleteAll();
+ NOTATION_DEBUG << "NotationView::slotRenderSomething: updating thumbnails" << endl;
+ updateThumbnails(true);
+
+ // Update track headers when rendering is done
+ // (better late than never)
+ m_headersGroup->slotUpdateAllHeaders(getCanvasLeftX(), 0, true);
+ m_headersGroupView->setContentsPos(getCanvasView()->contentsX(),
+ getCanvasView()->contentsY());
+}
+
+NotationCanvasView* NotationView::getCanvasView()
+{
+ return dynamic_cast<NotationCanvasView *>(m_canvasView);
+}
+
+void
+NotationView::slotVerticalScrollHeadersGroup(int y)
+{
+ m_headersGroupView->setContentsPos(0, y);
+}
+
+void
+NotationView::slotShowHeadersGroup()
+{
+ m_showHeadersGroup = HeadersGroup::ShowAlways;
+ showHeadersGroup();
+
+ // Disable menu entry when headers are shown
+ m_showHeadersMenuEntry->setEnabled(false);
+}
+
+void
+NotationView::slotHideHeadersGroup()
+{
+ m_showHeadersGroup = HeadersGroup::ShowNever;
+ hideHeadersGroup();
+
+ // Enable menu entry when headers are hidden
+ m_showHeadersMenuEntry->setEnabled(true);
+}
+
+void
+NotationView::showHeadersGroup()
+{
+ if (m_headersGroupView && (m_pageMode == LinedStaff::LinearMode)) {
+ m_headersGroupView->show();
+ m_headersTopFrame->show();
+ m_rulerBoxFiller->show();
+ }
+}
+
+void
+NotationView::hideHeadersGroup()
+{
+ if (m_headersGroupView) {
+ m_headersGroupView->hide();
+ m_headersTopFrame->hide();
+ m_rulerBoxFiller->hide();
+ }
+}
+
+void
+NotationView::slotUpdateHeaders(int x, int y)
+{
+ m_headersGroup->slotUpdateAllHeaders(x, y);
+ m_headersGroupView->setContentsPos(x, y);
+}
+
+void
+NotationView::slotHeadersWidthChanged(int w)
+{
+ m_headersTopFrame->setFixedWidth(w);
+ m_rulerBoxFiller->setFixedWidth(w);
+ m_canvasView->updateLeftWidgetGeometry();
+}
+
+
+int
+NotationView::getCanvasVisibleWidth()
+{
+ if (getCanvasView()) {
+ return getCanvasView()->visibleWidth();
+ } else {
+ return -1;
+ }
+}
+
+int
+NotationView::getHeadersTopFrameMinWidth()
+{
+ /// TODO : use a real button width got from a real button
+
+ // 2 buttons (2 x 24) + 2 margins (2 x 4) + buttons spacing (4)
+ return 4 + 24 + 4 + 24 + 4;
+}
+
+}
+#include "NotationView.moc"
diff --git a/src/gui/editors/notation/NotationView.h b/src/gui/editors/notation/NotationView.h
new file mode 100644
index 0000000..7678f8a
--- /dev/null
+++ b/src/gui/editors/notation/NotationView.h
@@ -0,0 +1,1131 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
+
+/*
+ Rosegarden
+ A MIDI and audio sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_NOTATIONVIEW_H_
+#define _RG_NOTATIONVIEW_H_
+
+#include "base/NotationTypes.h"
+#include "base/Track.h"
+#include "gui/general/EditView.h"
+#include "gui/general/LinedStaff.h"
+#include "gui/general/LinedStaffManager.h"
+#include "NotationProperties.h"
+#include "NotationCanvasView.h"
+#include <string>
+#include <kprocess.h>
+#include <ktempfile.h>
+#include <qmap.h>
+#include <qsize.h>
+#include <qstring.h>
+#include <vector>
+#include "base/Event.h"
+#include "gui/general/ClefIndex.h"
+
+
+class QWidget;
+class QTimer;
+class QPaintEvent;
+class QObject;
+class QMouseEvent;
+class QLabel;
+class QCursor;
+class QCanvasItem;
+class QCanvas;
+class KProgress;
+class KComboBox;
+class KActionMenu;
+class KAction;
+
+
+namespace Rosegarden
+{
+
+class Staff;
+class Segment;
+class ScrollBoxDialog;
+class RulerScale;
+class RosegardenGUIDoc;
+class RawNoteRuler;
+class ProgressDialog;
+class ProgressBar;
+class NotePixmapFactory;
+class NotationVLayout;
+class NotationStaff;
+class NotationHLayout;
+class NotationElement;
+class NoteActionData;
+class NoteActionDataMap;
+class MarkActionData;
+class MarkActionDataMap;
+class NoteChangeActionData;
+class NoteChangeActionDataMap;
+class Key;
+class EventSelection;
+class Event;
+class Clef;
+class ChordNameRuler;
+class QDeferScrollView;
+class HeadersGroup;
+
+
+/**
+ * NotationView is a view for one or more Staff objects, each of
+ * which contains the notation data associated with a Segment.
+ * NotationView owns the Staff objects it displays.
+ *
+ * This class manages the relationship between NotationHLayout/
+ * NotationVLayout and Staff data, as well as using rendering the
+ * actual notes (using NotePixmapFactory to generate the pixmaps).
+ */
+
+class NotationView : public EditView,
+ public LinedStaffManager
+{
+ friend class NoteInserter;
+ friend class ClefInserter;
+ friend class NotationEraser;
+ friend class NotationSelectionPaster;
+ friend class LilyPondExporter;
+
+ Q_OBJECT
+
+public:
+ explicit NotationView(RosegardenGUIDoc *doc,
+ std::vector<Segment *> segments,
+ QWidget *parent,
+ bool showProgressive); // update during initial render?
+
+ /**
+ * Constructor for printing only. If parent is provided, a
+ * progress dialog will be shown -- otherwise not. If another
+ * NotationView is provided, the fonts and other settings used
+ * for printing will be taken from that view.
+ */
+ explicit NotationView(RosegardenGUIDoc *doc,
+ std::vector<Segment *> segments,
+ QWidget *parent,
+ NotationView *referenceView);
+
+ ~NotationView();
+
+// void initialLayout();
+
+ /// constructed successfully? (main reason it might not is user hit Cancel)
+ bool isOK() const { return m_ok; }
+
+ /**
+ * Return the view-local PropertyName definitions for this view
+ */
+ const NotationProperties &getProperties() const;
+
+ /// Return the number of staffs
+ int getStaffCount() { return m_staffs.size(); }
+
+ /// Return a pointer to the staff at the specified index
+ Staff *getStaff(int i) {
+ return getLinedStaff(i);
+ }
+
+ /// Return a pointer to the staff corresponding to the given segment
+ Staff *getStaff(const Segment &segment) {
+ return getLinedStaff(segment);
+ }
+
+ /// Return a pointer to the staff at the specified index
+ LinedStaff *getLinedStaff(int i);
+
+ /// Return a pointer to the staff corresponding to the given segment
+ LinedStaff *getLinedStaff(const Segment &segment);
+
+ /// Return a pointer to the staff at the specified index
+ NotationStaff *getNotationStaff(int i) {
+ if (i >= 0 && unsigned(i) < m_staffs.size()) return m_staffs[i];
+ else return 0;
+ }
+
+ /// Return a pointer to the staff corresponding to the given segment
+ NotationStaff *getNotationStaff(const Segment &segment);
+
+ /// Return true if the staff at the specified index is the current one
+ bool isCurrentStaff(int i);
+
+ QCanvas* canvas() { return getCanvasView()->canvas(); }
+
+ void setCanvasCursor(const QCursor &cursor) {
+ getCanvasView()->viewport()->setCursor(cursor);
+ }
+
+ void setHeightTracking(bool t) {
+ getCanvasView()->setHeightTracking(t);
+ }
+
+ /**
+ * Returns true if the view is actually for printing
+ */
+ bool isInPrintMode() { return m_printMode; }
+
+ /**
+ * Set the note or rest selected by the user from the toolbars
+ */
+ void setCurrentSelectedNote(const char *pixmapName,
+ bool isRest, Note::Type,
+ int dots = 0);
+
+ /**
+ * Set the note or rest selected by the user from the toolbars
+ */
+ void setCurrentSelectedNote(const NoteActionData &);
+
+ /**
+ * Discover whether chord-mode insertions are enabled (as opposed
+ * to the default melody-mode)
+ */
+ bool isInChordMode();
+
+ /**
+ * Discover whether triplet-mode insertions are enabled
+ */
+ bool isInTripletMode();
+
+ /**
+ * Discover whether grace-mode insertions are enabled
+ */
+ bool isInGraceMode();
+
+ /**
+ * Discover whether annotations are being displayed or not
+ */
+ bool areAnnotationsVisible() { return m_annotationsVisible; }
+
+ /**
+ * Discover whether LilyPond directives are being displayed or not
+ */
+ bool areLilyPondDirectivesVisible() { return m_lilyPondDirectivesVisible; }
+
+ /**
+ * Set the current event selection.
+ *
+ * If preview is true, sound the selection as well.
+ *
+ * If redrawNow is true, recolour the elements on the canvas;
+ * otherwise just line up a refresh for the next paint event.
+ *
+ * (If the selection has changed as part of a modification to a
+ * segment, redrawNow should be unnecessary and undesirable, as a
+ * paint event will occur in the next event loop following the
+ * command invocation anyway.)
+ */
+ virtual void setCurrentSelection(EventSelection*,
+ bool preview = false,
+ bool redrawNow = false);
+
+ /**
+ * Set the current event selection to a single event
+ */
+ void setSingleSelectedEvent(int staffNo,
+ Event *event,
+ bool preview = false,
+ bool redrawNow = false);
+
+ /**
+ * Set the current event selection to a single event
+ */
+ void setSingleSelectedEvent(Segment &segment,
+ Event *event,
+ bool preview = false,
+ bool redrawNow = false);
+
+ /**
+ * Show and sound the given note. The height is used for display,
+ * the pitch for performance, so the two need not correspond (e.g.
+ * under ottava there may be octave differences).
+ */
+ void showPreviewNote(int staffNo, double layoutX,
+ int pitch, int height,
+ const Note &note,
+ bool grace,
+ int velocity = -1);
+
+ /// Remove any visible preview note
+ void clearPreviewNote();
+
+ /// Sound the given note
+ void playNote(Segment &segment, int pitch, int velocity = -1);
+
+ /// Switches between page- and linear- layout modes
+ void setPageMode(LinedStaff::PageMode mode);
+
+ /// Returns the page width according to the layout mode (page/linear)
+ int getPageWidth();
+
+ /// Returns the page height according to the layout mode (page/linear)
+ int getPageHeight();
+
+ /// Returns the margins within the page (zero if not in MultiPageMode)
+ void getPageMargins(int &left, int &top);
+
+ /// Scrolls the view such that the given time is centered
+ void scrollToTime(timeT t);
+
+ NotePixmapFactory *getNotePixmapFactory() const {
+ return m_notePixmapFactory;
+ }
+
+ virtual void refreshSegment(Segment *segment,
+ timeT startTime = 0,
+ timeT endTime = 0);
+
+ /**
+ * From LinedStaffManager
+ */
+ virtual LinedStaff* getStaffForCanvasCoords(int x, int y) const;
+
+
+ /**
+ * Overridden from EditView
+ */
+ virtual void updateView();
+
+ /**
+ * Render segments on printing painter. This uses the current
+ * font size and layout, rather than the optimal ones for the
+ * printer configuration (notation editing is not quite WYSIWYG,
+ * and we may be in a non-page mode).
+ *
+ * To print optimally use slotFilePrint, which will create
+ * another NotationView with the optimal settings and call print
+ * on that.
+ */
+ virtual void print(bool previewOnly = false);
+
+ /**
+ * Return X of the left of the canvas visible part.
+ */
+ double getCanvasLeftX() { return getCanvasView()->contentsX(); }
+
+ virtual RulerScale* getHLayout();
+
+ /**
+ * Return the notation window width
+ */
+ int getCanvasVisibleWidth();
+
+ /**
+ * Return the minimal width which shall be allocated to
+ * the track headers top frame.
+ * (The width of the close button + the width of an info
+ * button still to come).
+ */
+ int getHeadersTopFrameMinWidth();
+
+public slots:
+
+ /**
+ * Print the current set of segments, by creating another
+ * NotationView with the printing configuration but the same
+ * segments, font etc as this view and asking it to print.
+ */
+ void slotFilePrint();
+
+ /**
+ * Preview the current set of segments, by creating another
+ * NotationView with the printing configuration but the same
+ * segments, font etc as this view and asking it to preview.
+ */
+ void slotFilePrintPreview();
+
+ /**
+ * export a LilyPond file
+ */
+ bool exportLilyPondFile(QString url, bool forPreview = false);
+
+ /**
+ * Export to a temporary file and process
+ */
+ void slotPrintLilyPond();
+ void slotPreviewLilyPond();
+ void slotLilyPondViewProcessExited(KProcess *);
+
+ /**
+ * put the marked text/object into the clipboard and remove it
+ * from the document
+ */
+ void slotEditCut();
+
+ /**
+ * put the marked text/object into the clipboard
+ */
+ void slotEditCopy();
+
+ /**
+ * paste the clipboard into the document
+ */
+ void slotEditPaste();
+
+ /**
+ * cut the selection and close the gap, moving subsequent events
+ * towards the start of the segment
+ */
+ void slotEditCutAndClose();
+
+ /**
+ * paste the clipboard into the document, offering a choice for how
+ */
+ void slotEditGeneralPaste();
+
+ /**
+ * delete the selection (cut without the copy)
+ */
+ void slotEditDelete();
+
+ /**
+ * move the selection to the staff above
+ */
+ void slotMoveEventsUpStaff();
+
+ /**
+ * move the selection to the staff below
+ */
+ void slotMoveEventsDownStaff();
+
+ /**
+ * toggles the tools toolbar
+ */
+ void slotToggleToolsToolBar();
+
+ /**
+ * toggles the notes toolbar
+ */
+ void slotToggleNotesToolBar();
+
+ /**
+ * toggles the rests toolbar
+ */
+ void slotToggleRestsToolBar();
+
+ /**
+ * toggles the accidentals toolbar
+ */
+ void slotToggleAccidentalsToolBar();
+
+ /**
+ * toggles the clefs toolbar
+ */
+ void slotToggleClefsToolBar();
+
+ /**
+ * toggles the marks toolbar
+ */
+ void slotToggleMarksToolBar();
+
+ /**
+ * toggles the group toolbar
+ */
+ void slotToggleGroupToolBar();
+
+ /**
+ * toggles the layout toolbar
+ */
+ void slotToggleLayoutToolBar();
+
+ /**
+ * toggles the transport toolbar
+ */
+ void slotToggleTransportToolBar();
+
+ /**
+ * toggles the meta toolbar
+ */
+ void slotToggleMetaToolBar();
+
+ /// note switch slot
+ void slotNoteAction();
+
+ /// switch to last selected note
+ void slotLastNoteAction();
+
+ /// accidental switch slots
+ void slotNoAccidental();
+ void slotFollowAccidental();
+ void slotSharp();
+ void slotFlat();
+ void slotNatural();
+ void slotDoubleSharp();
+ void slotDoubleFlat();
+
+ /// clef switch slots
+ void slotTrebleClef();
+ void slotAltoClef();
+ void slotTenorClef();
+ void slotBassClef();
+
+ /// text tool
+ void slotText();
+
+ /// guitar chord tool
+ void slotGuitarChord();
+
+ /// editing tools
+ void slotEraseSelected();
+ void slotSelectSelected();
+
+ void slotToggleStepByStep();
+
+ /// status stuff
+ void slotUpdateInsertModeStatus();
+ void slotUpdateAnnotationsStatus();
+ void slotUpdateLilyPondDirectivesStatus();
+
+ /// edit menu
+ void slotPreviewSelection();
+ void slotClearLoop();
+ void slotClearSelection();
+ void slotEditSelectFromStart();
+ void slotEditSelectToEnd();
+ void slotEditSelectWholeStaff();
+ void slotFilterSelection();
+
+ /// view menu
+ void slotLinearMode();
+ void slotContinuousPageMode();
+ void slotMultiPageMode();
+ void slotToggleChordsRuler();
+ void slotToggleRawNoteRuler();
+ void slotToggleTempoRuler();
+ void slotToggleAnnotations();
+ void slotToggleLilyPondDirectives();
+ void slotEditLyrics();
+
+ /// Notation header slots
+ void slotShowHeadersGroup();
+ void slotHideHeadersGroup();
+ void slotVerticalScrollHeadersGroup(int);
+ void slotUpdateHeaders(int x, int y);
+ void slotHeadersWidthChanged(int w);
+
+ /// Adjust notation header view when bottom ruler added or removed
+ void slotCanvasBottomWidgetHeightChanged(int);
+
+ /// group slots
+ void slotGroupBeam();
+ void slotGroupAutoBeam();
+ void slotGroupBreak();
+ void slotGroupSimpleTuplet();
+ void slotGroupGeneralTuplet();
+ void slotGroupTuplet(bool simple);
+ void slotGroupUnTuplet();
+ void slotGroupSlur();
+ void slotGroupPhrasingSlur();
+ void slotGroupGlissando();
+ void slotGroupCrescendo();
+ void slotGroupDecrescendo();
+ void slotGroupMakeChord();
+ void slotGroupOctave2Up();
+ void slotGroupOctaveUp();
+ void slotGroupOctaveDown();
+ void slotGroupOctave2Down();
+ void slotAddIndication(std::string type, QString cat);
+
+ /// transforms slots
+ void slotTransformsNormalizeRests();
+ void slotTransformsCollapseRests();
+ void slotTransformsCollapseNotes();
+ void slotTransformsTieNotes();
+ void slotTransformsUntieNotes();
+ void slotTransformsMakeNotesViable();
+ void slotTransformsDeCounterpoint();
+ void slotTransformsStemsUp();
+ void slotTransformsStemsDown();
+ void slotTransformsRestoreStems();
+ void slotTransformsSlursAbove();
+ void slotTransformsSlursBelow();
+ void slotTransformsRestoreSlurs();
+ void slotTransformsTiesAbove();
+ void slotTransformsTiesBelow();
+ void slotTransformsRestoreTies();
+ void slotTransformsQuantize();
+ void slotTransformsFixQuantization();
+ void slotTransformsRemoveQuantization();
+ void slotTransformsInterpret();
+
+ void slotRespellDoubleFlat();
+ void slotRespellFlat();
+ void slotRespellNatural();
+ void slotRespellSharp();
+ void slotRespellDoubleSharp();
+ void slotRespellUp();
+ void slotRespellDown();
+ void slotRespellRestore();
+ void slotShowCautionary();
+ void slotCancelCautionary();
+
+ void slotSetStyleFromAction();
+ void slotInsertNoteFromAction();
+ void slotInsertRest();
+ void slotSwitchFromRestToNote();
+ void slotSwitchFromNoteToRest();
+ void slotToggleDot();
+
+ void slotAddMark();
+ void slotMarksAddTextMark();
+ void slotMarksAddFingeringMark();
+ void slotMarksAddFingeringMarkFromAction();
+ void slotMarksRemoveMarks();
+ void slotMarksRemoveFingeringMarks();
+ void slotMakeOrnament();
+ void slotUseOrnament();
+ void slotRemoveOrnament();
+
+ void slotNoteChangeAction();
+ void slotSetNoteDurations(Note::Type, bool notationOnly);
+ void slotAddDot();
+ void slotAddDotNotationOnly();
+
+ void slotAddSlashes();
+
+ void slotEditAddClef();
+ void slotEditAddKeySignature();
+ void slotEditAddSustainDown();
+ void slotEditAddSustainUp();
+ void slotEditAddSustain(bool down);
+ void slotEditTranspose();
+ void slotEditSwitchPreset();
+ void slotEditElement(NotationStaff *, NotationElement *, bool advanced);
+
+ void slotFinePositionLeft();
+ void slotFinePositionRight();
+ void slotFinePositionUp();
+ void slotFinePositionDown();
+ void slotFinePositionRestore();
+
+ void slotMakeVisible();
+ void slotMakeInvisible();
+
+ void slotDebugDump();
+
+ /// Canvas actions slots
+
+ /**
+ * Called when a mouse press occurred on a notation element
+ * or somewhere on a staff
+ */
+ void slotItemPressed(int height, int staffNo, QMouseEvent*, NotationElement*);
+
+ /**
+ * Called when a mouse press occurred on a non-notation element
+ */
+ void slotNonNotationItemPressed(QMouseEvent *e, QCanvasItem *i);
+
+ /**
+ * Called when a mouse press occurred on a QCanvasText
+ */
+ void slotTextItemPressed(QMouseEvent *e, QCanvasItem *i);
+
+ void slotMouseMoved(QMouseEvent*);
+ void slotMouseReleased(QMouseEvent*);
+
+ /**
+ * Called when the mouse cursor moves over a different height on
+ * the staff
+ *
+ * @see NotationCanvasView#hoveredOverNoteChange()
+ */
+ void slotHoveredOverNoteChanged(const QString&);
+
+ /**
+ * Called when the mouse cursor moves over a note which is at a
+ * different time on the staff
+ *
+ * @see NotationCanvasView#hoveredOverAbsoluteTimeChange()
+ */
+ void slotHoveredOverAbsoluteTimeChanged(unsigned int);
+
+ /**
+ * Set the time pointer position during playback (purely visual,
+ * doesn't affect playback). This is also at liberty to highlight
+ * some notes, if it so desires...
+ */
+ void slotSetPointerPosition(timeT position);
+
+ /**
+ * As above, but with the ability to specify whether to scroll or
+ * not to follow the pointer (above method uses the play tracking
+ * setting to determine that)
+ */
+ void slotSetPointerPosition(timeT position, bool scroll);
+
+ /**
+ * Update the recording segment if it's one of the ones in the
+ * view
+ */
+ void slotUpdateRecordingSegment(Segment *recordingSegment,
+ timeT updatedFrom);
+
+ /// Set the current staff to the one containing the given canvas Y coord
+ void slotSetCurrentStaff(double canvasX, int canvasY);
+
+ /// Set the current staff to that with the given id
+ void slotSetCurrentStaff(int staffNo);
+
+ /**
+ * Set the insert cursor position (from the top LoopRuler).
+ * If the segment has recently been changed and no refresh has
+ * occurred since, pass updateNow false; then the move will
+ * happen on the next update.
+ */
+ void slotSetInsertCursorPosition(timeT position,
+ bool scroll, bool updateNow);
+
+ virtual void slotSetInsertCursorPosition(timeT position) {
+ slotSetInsertCursorPosition(position, true, true);
+ }
+
+ /// Set the insert cursor position from a mouse event location
+ void slotSetInsertCursorPosition(double canvasX, int canvasY,
+ bool scroll, bool updateNow);
+
+ void slotSetInsertCursorPosition(double canvasX, int canvasY) {
+ slotSetInsertCursorPosition(canvasX, canvasY, true, true);
+ }
+
+ /**
+ * Set the insert cursor position and scroll so it's at given point.
+ * If the segment has recently been changed and no refresh has
+ * occurred since, pass updateNow false; then the move will
+ * happen on the next update.
+ */
+ void slotSetInsertCursorAndRecentre(timeT position,
+ double cx, int cy,
+ bool updateNow = true);
+
+ void slotSetInsertCursorAndRecentre(timeT position,
+ double cx, double cy) {
+ slotSetInsertCursorAndRecentre(position, cx, static_cast<int>(cy), true);
+ }
+
+ /// Set insert cursor to playback pointer position
+ void slotJumpCursorToPlayback();
+
+ /// Set playback pointer to insert cursor position (affects playback)
+ void slotJumpPlaybackToCursor();
+
+ /// Toggle tracking with the position pointer during playback
+ void slotToggleTracking();
+
+ /// Change the current staff to the one preceding the current one
+ void slotCurrentStaffUp();
+
+ /// Change the current staff to the one following the current one
+ void slotCurrentStaffDown();
+
+ /// Change the current segment to the one following the current one
+ void slotCurrentSegmentPrior();
+
+ /// Change the current segment to the one preceding the current one
+ void slotCurrentSegmentNext();
+
+ /// Changes the font of the staffs on the view, gets font name from sender
+ void slotChangeFontFromAction();
+
+ /// Changes the font of the staffs on the view
+ void slotChangeFont(std::string newFont);
+
+ /// Changes the font and font size of the staffs on the view
+ void slotChangeFont(std::string newFont, int newSize);
+
+ /// Changes the font of the staffs on the view
+ void slotChangeFont(const QString &newFont);
+
+ /// Changes the font size of the staffs on the view
+ void slotChangeFontSize(int newSize);
+
+ /// Changes the font size of the staffs on the view, gets size from sender
+ void slotChangeFontSizeFromAction();
+
+ /// Changes the font size of the staffs on the view to the nth size in the available size list
+ void slotChangeFontSizeFromStringValue(const QString&);
+
+ /// Changes to the next font size up
+ void slotZoomIn();
+
+ /// Changes to the next font size down
+ void slotZoomOut();
+
+ /// Changes the hlayout spacing of the staffs on the view
+ void slotChangeSpacing(int newSpacing);
+
+ /// Changes the hlayout spacing of the staffs on the view
+ void slotChangeSpacingFromStringValue(const QString&);
+
+ /// Changes the hlayout spacing of the staffs on the view
+ void slotChangeSpacingFromAction();
+
+ /// Changes the hlayout proportion of the staffs on the view
+ void slotChangeProportion(int newProportion);
+
+ /// Changes the hlayout proportion of the staffs on the view
+ void slotChangeProportionFromIndex(int newProportionIndex);
+
+ /// Changes the hlayout proportion of the staffs on the view
+ void slotChangeProportionFromAction();
+
+ /// Note-on received asynchronously -- consider step-by-step editing
+ void slotInsertableNoteOnReceived(int pitch, int velocity);
+
+ /// Note-off received asynchronously -- consider step-by-step editing
+ void slotInsertableNoteOffReceived(int pitch, int velocity);
+
+ /// Note-on or note-off received asynchronously -- as above
+ void slotInsertableNoteEventReceived(int pitch, int velocity, bool noteOn);
+
+ /// A timer set when a note-on event was received has elapsed
+ void slotInsertableTimerElapsed();
+
+ /// The given QObject has originated a step-by-step-editing request
+ void slotStepByStepTargetRequested(QObject *);
+
+ /// Do on-demand rendering for a region.
+ void slotCheckRendered(double cx0, double cx1);
+
+ /// Do some background rendering work.
+ void slotRenderSomething();
+
+ void slotSetOperationNameAndStatus(QString);
+
+ // Update notation view based on track/staff name change
+ void slotUpdateStaffName();
+
+ // LilyPond Directive slots
+ void slotBeginLilyPondRepeat();
+
+signals:
+ /**
+ * Emitted when the note selected in the palette changes
+ */
+ void changeCurrentNote(bool isRest, Note::Type);
+
+ /**
+ * Emitted when a new accidental has been choosen by the user
+ */
+ void changeAccidental(Accidental, bool follow);
+
+ /**
+ * Emitted when the selection has been cut or copied
+ *
+ * @see NotationSelector#hideSelection
+ */
+ void usedSelection();
+
+ void play();
+ void stop();
+ void fastForwardPlayback();
+ void rewindPlayback();
+ void fastForwardPlaybackToEnd();
+ void rewindPlaybackToBeginning();
+ void jumpPlaybackTo(timeT);
+ void panic();
+
+ /// progress Report
+ void setProgress(int);
+ void incrementProgress(int);
+ void setOperationName(QString);
+
+ void stepByStepTargetRequested(QObject *);
+
+ void renderComplete();
+
+ void editTimeSignature(timeT);
+
+ void editMetadata(QString);
+
+ void editTriggerSegment(int);
+
+ void staffLabelChanged(TrackId id, QString label);
+
+protected:
+
+ virtual void paintEvent(QPaintEvent* e);
+
+ /**
+ * init the action maps for notes, marks etc
+ */
+ void initActionDataMaps();
+
+protected slots:
+ /**
+ * save general Options like all bar positions and status as well
+ * as the geometry and the recent file list to the configuration
+ * file
+ */
+ virtual void slotSaveOptions();
+
+protected:
+
+ /**
+ * read general Options again and initialize all variables like the recent file list
+ */
+ virtual void readOptions();
+
+ void setOneToolbar(const char *actionName,
+ const char *toolbarName);
+
+ /**
+ * create menus and toolbars
+ */
+ virtual void setupActions();
+
+ /**
+ * create or re-initialise (after font change) the font size menu
+ */
+ virtual void setupFontSizeMenu(std::string oldFontName = "");
+
+ /**
+ * Set KDE3+ menu states based on the current selection
+ */
+ virtual void setMenuStates();
+
+ /**
+ * setup status bar
+ */
+ virtual void initStatusBar();
+
+ /**
+ * Place the staffs at the correct x & y coordinates (before layout)
+ */
+ void positionStaffs();
+
+ /**
+ * Place the page pixmaps (if any) at the correct x & y
+ * coordinates (after layout)
+ */
+ void positionPages();
+
+ /**
+ * Update the panner thumbnail images. If complete is true,
+ * copy the entire mini-canvas.
+ */
+ void updateThumbnails(bool complete);
+
+ /**
+ * setup the layout/font toolbar
+ */
+ void initLayoutToolbar();
+
+ /**
+ * Helper function to toggle a toolbar given its name
+ * If \a force point to a bool, then the bool's value
+ * is used to show/hide the toolbar.
+ */
+ void toggleNamedToolBar(const QString& toolBarName, bool* force = 0);
+
+ /// Calls all the relevant preparse and layout methods
+ virtual bool applyLayout(int staffNo = -1,
+ timeT startTime = 0,
+ timeT endTime = 0);
+
+ /**
+ * Readjust the size of the canvas after a layout
+ *
+ * Checks the total width computed by the horizontal layout
+ *
+ * @see NotationHLayout#getTotalWidth()
+ */
+ void readjustCanvasSize();
+
+ /**
+ * Override from EditView
+ * @see EditView#getViewSize
+ */
+ virtual QSize getViewSize();
+
+ /**
+ * Override from EditView
+ * @see EditView#setViewSize
+ */
+ virtual void setViewSize(QSize);
+
+ /**
+ * Set the note pixmap factory
+ *
+ * The previous pixmap factory is deleted
+ */
+ void setNotePixmapFactory(NotePixmapFactory*);
+
+ virtual NotationCanvasView* getCanvasView();
+
+ virtual Segment *getCurrentSegment();
+ virtual Staff *getCurrentStaff() { return getCurrentLinedStaff(); }
+ virtual LinedStaff *getCurrentLinedStaff();
+
+ virtual LinedStaff *getStaffAbove();
+ virtual LinedStaff *getStaffBelow();
+
+ virtual bool hasSegment(Segment *segment);
+
+ /**
+ * Return the time at which the insert cursor may be found.
+ */
+ virtual timeT getInsertionTime();
+
+ /**
+ * Return the time at which the insert cursor may be found,
+ * and the time signature, clef and key at that time.
+ */
+ virtual timeT getInsertionTime(Clef &clef,
+ Rosegarden::Key &key);
+
+ void doDeferredCursorMove();
+
+ void removeViewLocalProperties(Event*);
+
+ void setupProgress(KProgress*);
+ void setupProgress(ProgressDialog*);
+ void setupDefaultProgress();
+ void disconnectProgress();
+
+ /**
+ * Test whether we've had too many preview notes recently
+ */
+ bool canPreviewAnotherNote();
+
+ virtual void updateViewCaption();
+
+ void showHeadersGroup();
+ void hideHeadersGroup();
+
+
+ //--------------- Data members ---------------------------------
+
+ NotationProperties m_properties;
+
+ /// Displayed in the status bar, shows number of events selected
+ QLabel *m_selectionCounter;
+
+ /// Displayed in the status bar, shows insertion mode
+ QLabel *m_insertModeLabel;
+
+ /// Displayed in the status bar, shows when annotations are hidden
+ QLabel *m_annotationsLabel;
+
+ /// Displayed in the status bar, shows when LilyPond directives are hidden
+ QLabel *m_lilyPondDirectivesLabel;
+
+ /// Displayed in the status bar, shows progress of current operation
+ ProgressBar *m_progressBar;
+
+ /// Displayed in the status bar, holds the pixmap of the current note
+ QLabel* m_currentNotePixmap;
+
+ /// Displayed in the status bar, shows the pitch the cursor is at
+ QLabel* m_hoveredOverNoteName;
+
+ /// Displayed in the status bar, shows the absolute time the cursor is at
+ QLabel* m_hoveredOverAbsoluteTime;
+
+ std::vector<NotationStaff*> m_staffs;
+ int m_currentStaff;
+ int m_lastFinishingStaff;
+
+ QCanvasItem *m_title;
+ QCanvasItem *m_subtitle;
+ QCanvasItem *m_composer;
+ QCanvasItem *m_copyright;
+ std::vector<QCanvasItem *> m_pages;
+ std::vector<QCanvasItem *> m_pageNumbers;
+
+ timeT m_insertionTime;
+ enum DeferredCursorMoveType {
+ NoCursorMoveNeeded,
+ CursorMoveOnly,
+ CursorMoveAndMakeVisible,
+ CursorMoveAndScrollToPosition
+ };
+ DeferredCursorMoveType m_deferredCursorMove;
+ double m_deferredCursorScrollToX;
+
+ QString m_lastNoteAction;
+
+ std::string m_fontName;
+ int m_fontSize;
+ LinedStaff::PageMode m_pageMode;
+ int m_leftGutter;
+
+ NotePixmapFactory *m_notePixmapFactory;
+
+ NotationHLayout* m_hlayout;
+ NotationVLayout* m_vlayout;
+
+ ChordNameRuler *m_chordNameRuler;
+ QWidget *m_tempoRuler;
+ RawNoteRuler *m_rawNoteRuler;
+ bool m_annotationsVisible;
+ bool m_lilyPondDirectivesVisible;
+
+ KAction* m_selectDefaultNote;
+
+ typedef QMap<QString, NoteActionData *> NoteActionDataMap;
+ static NoteActionDataMap* m_noteActionDataMap;
+
+ typedef QMap<QString, NoteChangeActionData *> NoteChangeActionDataMap;
+ static NoteChangeActionDataMap* m_noteChangeActionDataMap;
+
+ typedef QMap<QString, MarkActionData *> MarkActionDataMap;
+ static MarkActionDataMap *m_markActionDataMap;
+
+ KComboBox *m_fontCombo;
+ KComboBox *m_fontSizeCombo;
+ KComboBox *m_spacingCombo;
+ KActionMenu *m_fontSizeActionMenu;
+ ScrollBoxDialog *m_pannerDialog;
+ QTimer *m_renderTimer;
+
+ bool m_playTracking;
+
+ std::vector<std::pair<int, int> > m_pendingInsertableNotes;
+
+ enum { PROGRESS_NONE,
+ PROGRESS_BAR,
+ PROGRESS_DIALOG } m_progressDisplayer;
+
+ bool m_inhibitRefresh;
+ bool m_ok;
+
+ bool m_printMode;
+ int m_printSize;
+
+ static std::map<KProcess *, KTempFile *> m_lilyTempFileMap;
+
+ int m_showHeadersGroup;
+ QDeferScrollView * m_headersGroupView;
+ HeadersGroup * m_headersGroup;
+ QFrame * m_headersTopFrame;
+
+ KAction * m_showHeadersMenuEntry;
+
+};
+
+
+}
+
+#endif
diff --git a/src/gui/editors/notation/NoteCharacter.cpp b/src/gui/editors/notation/NoteCharacter.cpp
new file mode 100644
index 0000000..fdcb578
--- /dev/null
+++ b/src/gui/editors/notation/NoteCharacter.cpp
@@ -0,0 +1,133 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
+
+/*
+ Rosegarden
+ A MIDI and audio sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "NoteCharacter.h"
+
+#include <qpainter.h>
+#include <qpixmap.h>
+#include <qpoint.h>
+#include <qcanvas.h>
+#include <qbitmap.h>
+
+
+namespace Rosegarden
+{
+
+NoteCharacter::NoteCharacter() :
+ m_hotspot(0, 0),
+ m_pixmap(new QPixmap()),
+ m_rep(0)
+{}
+
+NoteCharacter::NoteCharacter(QPixmap pixmap,
+ QPoint hotspot, NoteCharacterDrawRep *rep) :
+ m_hotspot(hotspot),
+ m_pixmap(new QPixmap(pixmap)),
+ m_rep(rep)
+{}
+
+NoteCharacter::NoteCharacter(const NoteCharacter &c) :
+ m_hotspot(c.m_hotspot),
+ m_pixmap(new QPixmap(*c.m_pixmap)),
+ m_rep(c.m_rep)
+{
+ // nothing else
+}
+
+NoteCharacter &
+NoteCharacter::operator=(const NoteCharacter &c)
+{
+ if (&c == this)
+ return * this;
+ m_hotspot = c.m_hotspot;
+ m_pixmap = new QPixmap(*c.m_pixmap);
+ m_rep = c.m_rep;
+ return *this;
+}
+
+NoteCharacter::~NoteCharacter()
+{
+ delete m_pixmap;
+}
+
+int
+NoteCharacter::getWidth() const
+{
+ return m_pixmap->width();
+}
+
+int
+NoteCharacter::getHeight() const
+{
+ return m_pixmap->height();
+}
+
+QPoint
+NoteCharacter::getHotspot() const
+{
+ return m_hotspot;
+}
+
+QPixmap *
+NoteCharacter::getPixmap() const
+{
+ return m_pixmap;
+}
+
+QCanvasPixmap *
+NoteCharacter::getCanvasPixmap() const
+{
+ return new QCanvasPixmap(*m_pixmap, m_hotspot);
+}
+
+void
+NoteCharacter::draw(QPainter *painter, int x, int y) const
+{
+ if (!m_rep) {
+
+ painter->drawPixmap(x, y, *m_pixmap);
+
+ } else {
+
+ NoteCharacterDrawRep a(m_rep->size());
+
+ for (unsigned int i = 0; i < m_rep->size(); ++i) {
+ QPoint p(m_rep->point(i));
+ a.setPoint(i, p.x() + x, p.y() + y);
+ }
+
+ painter->drawLineSegments(a);
+ }
+}
+
+void
+NoteCharacter::drawMask(QPainter *painter, int x, int y) const
+{
+ if (!m_rep && m_pixmap->mask()) {
+ painter->drawPixmap(x, y, *(m_pixmap->mask()));
+ }
+}
+
+}
diff --git a/src/gui/editors/notation/NoteCharacter.h b/src/gui/editors/notation/NoteCharacter.h
new file mode 100644
index 0000000..bc9359e
--- /dev/null
+++ b/src/gui/editors/notation/NoteCharacter.h
@@ -0,0 +1,93 @@
+
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
+
+/*
+ Rosegarden
+ A MIDI and audio sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_NOTECHARACTER_H_
+#define _RG_NOTECHARACTER_H_
+
+#include <qpixmap.h>
+#include <qpoint.h>
+#include <qpointarray.h>
+
+
+class QPainter;
+class QCanvasPixmap;
+
+namespace Rosegarden
+{
+
+class NoteCharacterDrawRep : public QPointArray
+{
+public:
+ NoteCharacterDrawRep(int size = 0) : QPointArray(size) { }
+};
+
+
+/**
+ * NoteCharacter knows how to draw a character from a font. It may be
+ * optimised for screen (using QPixmap underneath to produce
+ * low-resolution colour or greyscale glyphs) or printer (using some
+ * internal representation to draw in high-resolution monochrome on a
+ * print device). You can use screen characters on a printer and vice
+ * versa, but the performance and quality might not be as good.
+ *
+ * NoteCharacter objects are always constructed by the NoteFont, never
+ * directly.
+ */
+
+class NoteCharacter
+{
+public:
+ NoteCharacter();
+ NoteCharacter(const NoteCharacter &);
+ NoteCharacter &operator=(const NoteCharacter &);
+ ~NoteCharacter();
+
+ int getWidth() const;
+ int getHeight() const;
+
+ QPoint getHotspot() const;
+
+ QPixmap *getPixmap() const;
+ QCanvasPixmap *getCanvasPixmap() const;
+
+ void draw(QPainter *painter, int x, int y) const;
+ void drawMask(QPainter *painter, int x, int y) const;
+
+private:
+ friend class NoteFont;
+ NoteCharacter(QPixmap pixmap, QPoint hotspot, NoteCharacterDrawRep *rep);
+
+ QPoint m_hotspot;
+ QPixmap *m_pixmap; // I own this
+ NoteCharacterDrawRep *m_rep; // I don't own this, it's a reference to a static in the NoteFont
+};
+
+
+// Encapsulates NoteFontMap, and loads pixmaps etc on demand
+
+
+}
+
+#endif
diff --git a/src/gui/editors/notation/NoteCharacterNames.cpp b/src/gui/editors/notation/NoteCharacterNames.cpp
new file mode 100644
index 0000000..bcd450c
--- /dev/null
+++ b/src/gui/editors/notation/NoteCharacterNames.cpp
@@ -0,0 +1,123 @@
+// -*- c-basic-offset: 4 -*-
+
+/*
+ Rosegarden
+ A sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <bownie@bownie.com>
+
+ 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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <bownie@bownie.com>
+
+ 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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "NoteFont.h"
+#include "misc/Debug.h"
+
+#include "misc/Strings.h"
+#include "base/Exception.h"
+#include "gui/general/PixmapFunctions.h"
+#include "NoteCharacter.h"
+#include "NoteFontMap.h"
+#include "SystemFont.h"
+#include <qbitmap.h>
+#include <qgarray.h>
+#include <qimage.h>
+#include <qpainter.h>
+#include <qpixmap.h>
+#include <qpoint.h>
+#include <qstring.h>
+#include <qstringlist.h>
+
+
+namespace Rosegarden
+{
+
+NoteFont::FontPixmapMap *NoteFont::m_fontPixmapMap = 0;
+
+NoteFont::DrawRepMap *NoteFont::m_drawRepMap = 0;
+QPixmap *NoteFont::m_blankPixmap = 0;
+
+
+NoteFont::NoteFont(std::string fontName, int size) :
+ m_fontMap(fontName)
+{
+ // Do the size checks first, to avoid doing the extra work if they fail
+
+ std::set<int> sizes = m_fontMap.getSizes();
+
+ if (sizes.size() > 0) {
+ m_size = *sizes.begin();
+ } else {
+ throw BadNoteFont(std::string("No sizes listed for font ") + fontName);
+ }
+
+ if (size > 0) {
+ if (sizes.find(size) == sizes.end()) {
+ throw BadNoteFont(qstrtostr(QString("Font \"%1\" not available in size %2").arg(strtoqstr(fontName)).arg(size)));
+ } else {
+ m_size = size;
+ }
+ }
+
+ // Create the global font map and blank pixmap if necessary
+
+ if (m_fontPixmapMap == 0) {
+ m_fontPixmapMap = new FontPixmapMap();
+ }
+
+ if (m_blankPixmap == 0) {
+ m_blankPixmap = new QPixmap(10, 10);
+ m_blankPixmap->setMask(QBitmap(10, 10, TRUE));
+ }
+
+ // Locate our font's pixmap map in the font map, create if necessary
+
+ std::string fontKey = qstrtostr(QString("__%1__%2__")
+ .arg(strtoqstr(m_fontMap.getName()))
+ .arg(m_size));
+
+ FontPixmapMap::iterator i = m_fontPixmapMap->find(fontKey);
+ if (i == m_fontPixmapMap->end()) {
+ (*m_fontPixmapMap)[fontKey] = new PixmapMap();
+ }
+
+ m_map = (*m_fontPixmapMap)[fontKey];
+}
+
+NoteFont::~NoteFont()
+{
+ // empty
+}
+
+bool
+NoteFont::getStemThickness(unsigned int &thickness) const
+{
+ thickness = m_size / 9 + 1;
+ return m_fontMap.getStemThickness(m_size, thickness);
+}
+
+bool
+NoteFont::getBeamThickness(unsigned int &thickness) const
+{
+ thickness = m_size / 2;
+ return m_fontMap.getBeamThickness(m_size, thickness);
+}
+
+bool
+NoteFont::getStemLength(unsigned int &length) const
+{
+ getStaffLineThickness(length);
+ length = (m_size + length) * 7 / 2;
+ return m_fontMap.getStemLength(m_size, length);
+}
+
+bool
+NoteFont::getFlagSpacing(unsigned int &spacing) const
+{
+ spacing = m_size;
+ return m_fontMap.getFlagSpacing(m_size, spacing);
+}
+
+bool
+NoteFont::getStaffLineThickness(unsigned int &thickness) const
+{
+ thickness = (m_size < 7 ? 1 : m_size / 7);
+ return m_fontMap.getStaffLineThickness(m_size, thickness);
+}
+
+bool
+NoteFont::getLegerLineThickness(unsigned int &thickness) const
+{
+ thickness = (m_size < 6 ? 1 : m_size / 6);
+ return m_fontMap.getLegerLineThickness(m_size, thickness);
+}
+
+bool
+NoteFont::lookup(CharName charName, bool inverted, QPixmap *&pixmap) const
+{
+ PixmapMap::iterator i = m_map->find(charName);
+ if (i != m_map->end()) {
+ if (inverted) {
+ pixmap = i->second.second;
+ if (!pixmap && i->second.first)
+ return false;
+ } else {
+ pixmap = i->second.first;
+ if (!pixmap && i->second.second)
+ return false;
+ }
+ return true;
+ }
+ pixmap = 0;
+ return false;
+}
+
+void
+NoteFont::add
+(CharName charName, bool inverted, QPixmap *pixmap) const
+{
+ PixmapMap::iterator i = m_map->find(charName);
+ if (i != m_map->end()) {
+ if (inverted) {
+ delete i->second.second;
+ i->second.second = pixmap;
+ } else {
+ delete i->second.first;
+ i->second.first = pixmap;
+ }
+ } else {
+ if (inverted) {
+ (*m_map)[charName] = PixmapPair(0, pixmap);
+ } else {
+ (*m_map)[charName] = PixmapPair(pixmap, 0);
+ }
+ }
+}
+
+NoteCharacterDrawRep *
+NoteFont::lookupDrawRep(QPixmap *pixmap) const
+{
+ if (!m_drawRepMap)
+ m_drawRepMap = new DrawRepMap();
+
+ if (m_drawRepMap->find(pixmap) != m_drawRepMap->end()) {
+
+ return (*m_drawRepMap)[pixmap];
+
+ } else {
+
+ QImage image = pixmap->convertToImage();
+ if (image.isNull())
+ return 0;
+
+ if (image.depth() > 1) {
+ image = image.convertDepth(1, Qt::MonoOnly | Qt::ThresholdDither);
+ }
+
+ NoteCharacterDrawRep *a = new NoteCharacterDrawRep();
+
+ for (int yi = 0; yi < image.height(); ++yi) {
+
+ unsigned char *line = image.scanLine(yi);
+
+ int startx = 0;
+
+ for (int xi = 0; xi <= image.width(); ++xi) {
+
+ bool pixel = false;
+
+ if (xi < image.width()) {
+ if (image.bitOrder() == QImage::LittleEndian) {
+ if (*(line + (xi >> 3)) & 1 << (xi & 7))
+ pixel = true;
+ } else {
+ if (*(line + (xi >> 3)) & 1 << (7 - (xi & 7)))
+ pixel = true;
+ }
+ }
+
+ if (!pixel) {
+ if (startx < xi) {
+ a->resize(a->size() + 2, QGArray::SpeedOptim);
+ a->setPoint(a->size() - 2, startx, yi);
+ a->setPoint(a->size() - 1, xi - 1, yi);
+ }
+ startx = xi + 1;
+ }
+ }
+ }
+
+ (*m_drawRepMap)[pixmap] = a;
+ return a;
+ }
+}
+
+bool
+NoteFont::getPixmap(CharName charName, QPixmap &pixmap, bool inverted) const
+{
+ QPixmap *found = 0;
+ bool ok = lookup(charName, inverted, found);
+ if (ok) {
+ if (found) {
+ pixmap = *found;
+ return true;
+ } else {
+ pixmap = *m_blankPixmap;
+ return false;
+ }
+ }
+
+ if (inverted && !m_fontMap.hasInversion(m_size, charName)) {
+ if (!getPixmap(charName, pixmap, !inverted))
+ return false;
+ found = new QPixmap(PixmapFunctions::flipVertical(pixmap));
+ add(charName, inverted, found);
+ pixmap = *found;
+ return true;
+ }
+
+ std::string src;
+ ok = false;
+
+ if (!inverted)
+ ok = m_fontMap.getSrc(m_size, charName, src);
+ else
+ ok = m_fontMap.getInversionSrc(m_size, charName, src);
+
+ if (ok) {
+ NOTATION_DEBUG
+ << "NoteFont::getPixmap: Loading \"" << src << "\"" << endl;
+
+ found = new QPixmap(strtoqstr(src));
+
+ if (!found->isNull()) {
+
+ if (found->mask() == 0) {
+ std::cerr << "NoteFont::getPixmap: Warning: No automatic mask "
+ << "for character \"" << charName << "\""
+ << (inverted ? " (inverted)" : "") << " in font \""
+ << m_fontMap.getName() << "-" << m_size
+ << "\"; consider making xpm background transparent"
+ << std::endl;
+ found->setMask(PixmapFunctions::generateMask(*found));
+ }
+
+ add(charName, inverted, found);
+ pixmap = *found;
+ return true;
+ }
+
+ std::cerr << "NoteFont::getPixmap: Warning: Unable to read pixmap file " << src << std::endl;
+ } else {
+
+ int code = -1;
+ if (!inverted)
+ ok = m_fontMap.getCode(m_size, charName, code);
+ else
+ ok = m_fontMap.getInversionCode(m_size, charName, code);
+
+ int glyph = -1;
+ if (!inverted)
+ ok = m_fontMap.getGlyph(m_size, charName, glyph);
+ else
+ ok = m_fontMap.getInversionGlyph(m_size, charName, glyph);
+
+ if (code < 0 && glyph < 0) {
+ std::cerr << "NoteFont::getPixmap: Warning: No pixmap, code, or glyph for character \""
+ << charName << "\"" << (inverted ? " (inverted)" : "")
+ << " in font \"" << m_fontMap.getName() << "\"" << std::endl;
+ add(charName, inverted, 0);
+ pixmap = *m_blankPixmap;
+ return false;
+ }
+
+ int charBase = 0;
+ SystemFont *systemFont =
+ m_fontMap.getSystemFont(m_size, charName, charBase);
+
+ if (!systemFont) {
+ if (!inverted && m_fontMap.hasInversion(m_size, charName)) {
+ if (!getPixmap(charName, pixmap, !inverted))
+ return false;
+ found = new QPixmap(PixmapFunctions::flipVertical(pixmap));
+ add(charName, inverted, found);
+ pixmap = *found;
+ return true;
+ }
+
+ std::cerr << "NoteFont::getPixmap: Warning: No system font for character \""
+ << charName << "\"" << (inverted ? " (inverted)" : "")
+ << " in font \"" << m_fontMap.getName() << "\"" << std::endl;
+
+ add(charName, inverted, 0);
+ pixmap = *m_blankPixmap;
+ return false;
+ }
+
+ SystemFont::Strategy strategy =
+ m_fontMap.getStrategy(m_size, charName);
+
+ bool success;
+ found = new QPixmap(systemFont->renderChar(charName,
+ glyph,
+ code + charBase,
+ strategy,
+ success));
+
+ if (success) {
+ add(charName, inverted, found);
+ pixmap = *found;
+ return true;
+ } else {
+ add(charName, inverted, 0);
+ pixmap = *m_blankPixmap;
+ return false;
+ }
+ }
+
+ add(charName, inverted, 0);
+ pixmap = *m_blankPixmap;
+ return false;
+}
+
+bool
+NoteFont::getColouredPixmap(CharName baseCharName, QPixmap &pixmap,
+ int hue, int minValue, bool inverted) const
+{
+ CharName charName(getNameWithColour(baseCharName, hue));
+
+ QPixmap *found = 0;
+ bool ok = lookup(charName, inverted, found);
+ if (ok) {
+ if (found) {
+ pixmap = *found;
+ return true;
+ } else {
+ pixmap = *m_blankPixmap;
+ return false;
+ }
+ }
+
+ QPixmap basePixmap;
+ ok = getPixmap(baseCharName, basePixmap, inverted);
+
+ if (!ok) {
+ add(charName, inverted, 0);
+ pixmap = *m_blankPixmap;
+ return false;
+ }
+
+ found = new QPixmap
+ (PixmapFunctions::colourPixmap(basePixmap, hue, minValue));
+ add(charName, inverted, found);
+ pixmap = *found;
+ return ok;
+}
+
+bool
+NoteFont::getShadedPixmap(CharName baseCharName, QPixmap &pixmap,
+ bool inverted) const
+{
+ CharName charName(getNameShaded(baseCharName));
+
+ QPixmap *found = 0;
+ bool ok = lookup(charName, inverted, found);
+ if (ok) {
+ if (found) {
+ pixmap = *found;
+ return true;
+ } else {
+ pixmap = *m_blankPixmap;
+ return false;
+ }
+ }
+
+ QPixmap basePixmap;
+ ok = getPixmap(baseCharName, basePixmap, inverted);
+
+ if (!ok) {
+ add(charName, inverted, 0);
+ pixmap = *m_blankPixmap;
+ return false;
+ }
+
+ found = new QPixmap(PixmapFunctions::shadePixmap(basePixmap));
+ add(charName, inverted, found);
+ pixmap = *found;
+ return ok;
+}
+
+CharName
+NoteFont::getNameWithColour(CharName base, int hue) const
+{
+ return qstrtostr(QString("%1__%2").arg(hue).arg(strtoqstr(base)));
+}
+
+CharName
+NoteFont::getNameShaded(CharName base) const
+{
+ return qstrtostr(QString("shaded__%1").arg(strtoqstr(base)));
+}
+
+bool
+NoteFont::getDimensions(CharName charName, int &x, int &y, bool inverted) const
+{
+ QPixmap pixmap;
+ bool ok = getPixmap(charName, pixmap, inverted);
+ x = pixmap.width();
+ y = pixmap.height();
+ return ok;
+}
+
+int
+NoteFont::getWidth(CharName charName) const
+{
+ int x, y;
+ getDimensions(charName, x, y);
+ return x;
+}
+
+int
+NoteFont::getHeight(CharName charName) const
+{
+ int x, y;
+ getDimensions(charName, x, y);
+ return y;
+}
+
+bool
+NoteFont::getHotspot(CharName charName, int &x, int &y, bool inverted) const
+{
+ int w, h;
+ getDimensions(charName, w, h, inverted);
+ bool ok = m_fontMap.getHotspot(m_size, charName, w, h, x, y);
+
+ if (!ok) {
+ x = 0;
+ y = h / 2;
+ }
+
+ if (inverted) {
+ y = h - y;
+ }
+
+ return ok;
+}
+
+QPoint
+NoteFont::getHotspot(CharName charName, bool inverted) const
+{
+ int x, y;
+ (void)getHotspot(charName, x, y, inverted);
+ return QPoint(x, y);
+}
+
+bool
+NoteFont::getCharacter(CharName charName,
+ NoteCharacter &character,
+ CharacterType type,
+ bool inverted)
+{
+ QPixmap pixmap;
+ if (!getPixmap(charName, pixmap, inverted))
+ return false;
+
+ if (type == Screen) {
+ character = NoteCharacter(pixmap,
+ getHotspot(charName, inverted),
+ 0);
+ } else {
+
+ // Get the pointer direct from cache (depends on earlier call
+ // to getPixmap to put it in the cache if available)
+
+ QPixmap *pmapptr = 0;
+ bool found = lookup(charName, inverted, pmapptr);
+
+ NoteCharacterDrawRep *rep = 0;
+ if (found && pmapptr)
+ rep = lookupDrawRep(pmapptr);
+
+ character = NoteCharacter(pixmap,
+ getHotspot(charName, inverted),
+ rep);
+ }
+
+ return true;
+}
+
+NoteCharacter
+NoteFont::getCharacter(CharName charName,
+ CharacterType type,
+ bool inverted)
+{
+ NoteCharacter character;
+ getCharacter(charName, character, type, inverted);
+ return character;
+}
+
+bool
+NoteFont::getCharacterColoured(CharName charName,
+ int hue, int minValue,
+ NoteCharacter &character,
+ CharacterType type,
+ bool inverted)
+{
+ QPixmap pixmap;
+ if (!getColouredPixmap(charName, pixmap, hue, minValue, inverted)) {
+ return false;
+ }
+
+ if (type == Screen) {
+
+ character = NoteCharacter(pixmap,
+ getHotspot(charName, inverted),
+ 0);
+
+ } else {
+
+ // Get the pointer direct from cache (depends on earlier call
+ // to getPixmap to put it in the cache if available)
+
+ QPixmap *pmapptr = 0;
+ CharName cCharName(getNameWithColour(charName, hue));
+ bool found = lookup(cCharName, inverted, pmapptr);
+
+ NoteCharacterDrawRep *rep = 0;
+ if (found && pmapptr)
+ rep = lookupDrawRep(pmapptr);
+
+ character = NoteCharacter(pixmap,
+ getHotspot(charName, inverted),
+ rep);
+ }
+
+ return true;
+}
+
+NoteCharacter
+NoteFont::getCharacterColoured(CharName charName,
+ int hue, int minValue,
+ CharacterType type,
+ bool inverted)
+{
+ NoteCharacter character;
+ getCharacterColoured(charName, hue, minValue, character, type, inverted);
+ return character;
+}
+
+bool
+NoteFont::getCharacterShaded(CharName charName,
+ NoteCharacter &character,
+ CharacterType type,
+ bool inverted)
+{
+ QPixmap pixmap;
+ if (!getShadedPixmap(charName, pixmap, inverted)) {
+ return false;
+ }
+
+ if (type == Screen) {
+
+ character = NoteCharacter(pixmap,
+ getHotspot(charName, inverted),
+ 0);
+
+ } else {
+
+ // Get the pointer direct from cache (depends on earlier call
+ // to getPixmap to put it in the cache if available)
+
+ QPixmap *pmapptr = 0;
+ CharName cCharName(getNameShaded(charName));
+ bool found = lookup(cCharName, inverted, pmapptr);
+
+ NoteCharacterDrawRep *rep = 0;
+ if (found && pmapptr)
+ rep = lookupDrawRep(pmapptr);
+
+ character = NoteCharacter(pixmap,
+ getHotspot(charName, inverted),
+ rep);
+ }
+
+ return true;
+}
+
+NoteCharacter
+NoteFont::getCharacterShaded(CharName charName,
+ CharacterType type,
+ bool inverted)
+{
+ NoteCharacter character;
+ getCharacterShaded(charName, character, type, inverted);
+ return character;
+}
+
+}
diff --git a/src/gui/editors/notation/NoteFont.h b/src/gui/editors/notation/NoteFont.h
new file mode 100644
index 0000000..81a3b19
--- /dev/null
+++ b/src/gui/editors/notation/NoteFont.h
@@ -0,0 +1,184 @@
+
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
+
+/*
+ Rosegarden
+ A MIDI and audio sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_NOTEFONT_H_
+#define _RG_NOTEFONT_H_
+
+#include "base/Exception.h"
+#include <map>
+#include "NoteCharacter.h"
+#include "NoteFontMap.h"
+#include <set>
+#include <string>
+#include <qpoint.h>
+#include <utility>
+#include "gui/editors/notation/NoteCharacterNames.h"
+
+
+class QPixmap;
+class PixmapMap;
+class NoteCharacterDrawRep;
+class FontPixmapMap;
+class DrawRepMap;
+
+
+namespace Rosegarden
+{
+
+
+
+class NoteFont
+{
+public:
+ enum CharacterType { Screen, Printer };
+
+ typedef Exception BadNoteFont;
+ ~NoteFont();
+
+ std::string getName() const { return m_fontMap.getName(); }
+ int getSize() const { return m_size; }
+ bool isSmooth() const { return m_fontMap.isSmooth(); }
+ const NoteFontMap &getNoteFontMap() const { return m_fontMap; }
+
+ /// Returns false + thickness=1 if not specified
+ bool getStemThickness(unsigned int &thickness) const;
+
+ /// Returns false + a guess at suitable thickness if not specified
+ bool getBeamThickness(unsigned int &thickness) const;
+
+ /// Returns false + a guess at suitable length if not specified
+ bool getStemLength(unsigned int &length) const;
+
+ /// Returns false + a guess at suitable spacing if not specified
+ bool getFlagSpacing(unsigned int &spacing) const;
+
+ /// Returns false + thickness=1 if not specified
+ bool getStaffLineThickness(unsigned int &thickness) const;
+
+ /// Returns false + thickness=1 if not specified
+ bool getLegerLineThickness(unsigned int &thickness) const;
+
+ /// Returns false if not available
+ bool getCharacter(CharName charName,
+ NoteCharacter &character,
+ CharacterType type = Screen,
+ bool inverted = false);
+
+ /// Returns an empty character if not available
+ NoteCharacter getCharacter(CharName charName,
+ CharacterType type = Screen,
+ bool inverted = false);
+
+ /// Returns false if not available
+ bool getCharacterColoured(CharName charName,
+ int hue, int minValue,
+ NoteCharacter &character,
+ CharacterType type = Screen,
+ bool inverted = false);
+
+ /// Returns an empty character if not available
+ NoteCharacter getCharacterColoured(CharName charName,
+ int hue, int minValue,
+ CharacterType type = Screen,
+ bool inverted = false);
+
+ /// Returns false if not available
+ bool getCharacterShaded(CharName charName,
+ NoteCharacter &character,
+ CharacterType type = Screen,
+ bool inverted = false);
+
+ /// Returns an empty character if not available
+ NoteCharacter getCharacterShaded(CharName charName,
+ CharacterType type = Screen,
+ bool inverted = false);
+
+ /// Returns false + dimensions of blank pixmap if none found
+ bool getDimensions(CharName charName, int &x, int &y,
+ bool inverted = false) const;
+
+ /// Ignores problems, returning dimension of blank pixmap if necessary
+ int getWidth(CharName charName) const;
+
+ /// Ignores problems, returning dimension of blank pixmap if necessary
+ int getHeight(CharName charName) const;
+
+ /// Returns false + centre-left of pixmap if no hotspot specified
+ bool getHotspot(CharName charName, int &x, int &y,
+ bool inverted = false) const;
+
+ /// Ignores problems, returns centre-left of pixmap if necessary
+ QPoint getHotspot(CharName charName, bool inverted = false) const;
+
+private:
+ /// Returns false + blank pixmap if it can't find the right one
+ bool getPixmap(CharName charName, QPixmap &pixmap,
+ bool inverted = false) const;
+
+ /// Returns false + blank pixmap if it can't find the right one
+ bool getColouredPixmap(CharName charName, QPixmap &pixmap,
+ int hue, int minValue,
+ bool inverted = false) const;
+
+ /// Returns false + blank pixmap if it can't find the right one
+ bool getShadedPixmap(CharName charName, QPixmap &pixmap,
+ bool inverted = false) const;
+
+ friend class NoteFontFactory;
+ NoteFont(std::string fontName, int size = 0);
+ std::set<int> getSizes() const { return m_fontMap.getSizes(); }
+
+ bool lookup(CharName charName, bool inverted, QPixmap *&pixmap) const;
+ void add(CharName charName, bool inverted, QPixmap *pixmap) const;
+
+ NoteCharacterDrawRep *lookupDrawRep(QPixmap *pixmap) const;
+
+ CharName getNameWithColour(CharName origName, int hue) const;
+ CharName getNameShaded(CharName origName) const;
+
+ typedef std::pair<QPixmap *, QPixmap *> PixmapPair;
+ typedef std::map<CharName, PixmapPair> PixmapMap;
+ typedef std::map<std::string, PixmapMap *> FontPixmapMap;
+
+ typedef std::map<QPixmap *, NoteCharacterDrawRep *> DrawRepMap;
+
+ //--------------- Data members ---------------------------------
+
+ int m_size;
+ NoteFontMap m_fontMap;
+
+ mutable PixmapMap *m_map; // pointer at a member of m_fontPixmapMap
+
+ static FontPixmapMap *m_fontPixmapMap;
+ static DrawRepMap *m_drawRepMap;
+
+ static QPixmap *m_blankPixmap;
+};
+
+
+
+}
+
+#endif
diff --git a/src/gui/editors/notation/NoteFontFactory.cpp b/src/gui/editors/notation/NoteFontFactory.cpp
new file mode 100644
index 0000000..2decce4
--- /dev/null
+++ b/src/gui/editors/notation/NoteFontFactory.cpp
@@ -0,0 +1,236 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
+
+/*
+ Rosegarden
+ A MIDI and audio sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "NoteFontFactory.h"
+#include "misc/Debug.h"
+#include <kapplication.h>
+
+#include <klocale.h>
+#include <kstddirs.h>
+#include "misc/Strings.h"
+#include "document/ConfigGroups.h"
+#include "base/Exception.h"
+#include "gui/kdeext/KStartupLogo.h"
+#include "NoteFont.h"
+#include "NoteFontMap.h"
+#include <kconfig.h>
+#include <kglobal.h>
+#include <kmessagebox.h>
+#include <qdir.h>
+#include <qstring.h>
+#include <qstringlist.h>
+#include <algorithm>
+
+
+namespace Rosegarden
+{
+
+std::set<std::string>
+NoteFontFactory::getFontNames(bool forceRescan)
+{
+ NOTATION_DEBUG << "NoteFontFactory::getFontNames: forceRescan = " << forceRescan << endl;
+
+ if (forceRescan)
+ m_fontNames.clear();
+ if (!m_fontNames.empty())
+ return m_fontNames;
+
+ KConfig *config = kapp->config();
+ config->setGroup(NotationViewConfigGroup);
+
+ QString fontNameList = "";
+ if (!forceRescan) {
+ fontNameList = config->readEntry("notefontlist");
+ }
+
+ NOTATION_DEBUG << "NoteFontFactory::getFontNames: read from cache: " << fontNameList << endl;
+
+ QStringList names = QStringList::split(",", fontNameList);
+
+ if (names.empty()) {
+
+ NOTATION_DEBUG << "NoteFontFactory::getFontNames: No names available, rescanning..." << endl;
+
+ QString mappingDir =
+ KGlobal::dirs()->findResource("appdata", "fonts/mappings/");
+ QDir dir(mappingDir);
+ if (!dir.exists()) {
+ std::cerr << "NoteFontFactory::getFontNames: mapping directory \""
+ << mappingDir << "\" not found" << std::endl;
+ return m_fontNames;
+ }
+
+ dir.setFilter(QDir::Files | QDir::Readable);
+ QStringList files = dir.entryList();
+ for (QStringList::Iterator i = files.begin(); i != files.end(); ++i) {
+
+ if ((*i).length() > 4 && (*i).right(4).lower() == ".xml") {
+
+ std::string name(qstrtostr((*i).left((*i).length() - 4)));
+
+ try {
+ NoteFontMap map(name);
+ if (map.ok())
+ names.append(strtoqstr(map.getName()));
+ } catch (Exception e) {
+ KStartupLogo::hideIfStillThere();
+ KMessageBox::error(0, strtoqstr(e.getMessage()));
+ throw;
+ }
+ }
+ }
+ }
+
+ QString savedNames = "";
+
+ for (QStringList::Iterator i = names.begin(); i != names.end(); ++i) {
+ m_fontNames.insert(qstrtostr(*i));
+ if (i != names.begin())
+ savedNames += ",";
+ savedNames += *i;
+ }
+
+ config->writeEntry("notefontlist", savedNames);
+
+ return m_fontNames;
+}
+
+std::vector<int>
+NoteFontFactory::getAllSizes(std::string fontName)
+{
+ NoteFont *font = getFont(fontName, 0);
+ if (!font)
+ return std::vector<int>();
+
+ std::set
+ <int> s(font->getSizes());
+ std::vector<int> v;
+ for (std::set
+ <int>::iterator i = s.begin(); i != s.end(); ++i) {
+ v.push_back(*i);
+ }
+
+ std::sort(v.begin(), v.end());
+ return v;
+}
+
+std::vector<int>
+NoteFontFactory::getScreenSizes(std::string fontName)
+{
+ NoteFont *font = getFont(fontName, 0);
+ if (!font)
+ return std::vector<int>();
+
+ std::set
+ <int> s(font->getSizes());
+ std::vector<int> v;
+ for (std::set
+ <int>::iterator i = s.begin(); i != s.end(); ++i) {
+ if (*i <= 16)
+ v.push_back(*i);
+ }
+ std::sort(v.begin(), v.end());
+ return v;
+}
+
+NoteFont *
+NoteFontFactory::getFont(std::string fontName, int size)
+{
+ std::map<std::pair<std::string, int>, NoteFont *>::iterator i =
+ m_fonts.find(std::pair<std::string, int>(fontName, size));
+
+ if (i == m_fonts.end()) {
+ try {
+ NoteFont *font = new NoteFont(fontName, size);
+ m_fonts[std::pair<std::string, int>(fontName, size)] = font;
+ return font;
+ } catch (Exception e) {
+ KStartupLogo::hideIfStillThere();
+ KMessageBox::error(0, strtoqstr(e.getMessage()));
+ throw;
+ }
+ } else {
+ return i->second;
+ }
+}
+
+std::string
+NoteFontFactory::getDefaultFontName()
+{
+ static std::string defaultFont = "";
+ if (defaultFont != "")
+ return defaultFont;
+
+ std::set
+ <std::string> fontNames = getFontNames();
+
+ if (fontNames.find("Feta") != fontNames.end())
+ defaultFont = "Feta";
+ else {
+ fontNames = getFontNames(true);
+ if (fontNames.find("Feta") != fontNames.end())
+ defaultFont = "Feta";
+ else if (fontNames.find("Feta Pixmaps") != fontNames.end())
+ defaultFont = "Feta Pixmaps";
+ else if (fontNames.size() > 0)
+ defaultFont = *fontNames.begin();
+ else {
+ QString message = i18n("Can't obtain a default font -- no fonts found");
+ KStartupLogo::hideIfStillThere();
+ KMessageBox::error(0, message);
+ throw NoFontsAvailable(qstrtostr(message));
+ }
+ }
+
+ return defaultFont;
+}
+
+int
+NoteFontFactory::getDefaultSize(std::string fontName)
+{
+ // always return 8 if it's supported!
+ std::vector<int> sizes(getScreenSizes(fontName));
+ for (unsigned int i = 0; i < sizes.size(); ++i) {
+ if (sizes[i] == 8)
+ return sizes[i];
+ }
+ return sizes[sizes.size() / 2];
+}
+
+bool
+NoteFontFactory::isAvailableInSize(std::string fontName, int size)
+{
+ std::vector<int> sizes(getAllSizes(fontName));
+ for (unsigned int i = 0; i < sizes.size(); ++i) {
+ if (sizes[i] == size)
+ return true;
+ }
+ return false;
+}
+
+std::set<std::string> NoteFontFactory::m_fontNames;
+std::map<std::pair<std::string, int>, NoteFont *> NoteFontFactory::m_fonts;
+
+}
diff --git a/src/gui/editors/notation/NoteFontFactory.h b/src/gui/editors/notation/NoteFontFactory.h
new file mode 100644
index 0000000..33e6e80
--- /dev/null
+++ b/src/gui/editors/notation/NoteFontFactory.h
@@ -0,0 +1,71 @@
+
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
+
+/*
+ Rosegarden
+ A MIDI and audio sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_NOTEFONTFACTORY_H_
+#define _RG_NOTEFONTFACTORY_H_
+
+#include "base/Exception.h"
+#include <map>
+#include <set>
+#include <string>
+#include <vector>
+
+
+
+
+namespace Rosegarden
+{
+
+class NoteFont;
+
+
+class NoteFontFactory
+{
+public:
+ typedef Exception NoFontsAvailable;
+
+ // Any method passed a fontName argument may throw BadFont or
+ // MappingFileReadFailed; any other method may throw NoFontsAvailable
+
+ static NoteFont *getFont(std::string fontName, int size);
+
+ static std::set<std::string> getFontNames(bool forceRescan = false);
+ static std::vector<int> getAllSizes(std::string fontName); // sorted
+ static std::vector<int> getScreenSizes(std::string fontName); // sorted
+
+ static std::string getDefaultFontName();
+ static int getDefaultSize(std::string fontName);
+ static bool isAvailableInSize(std::string fontName, int size);
+
+private:
+ static std::set<std::string> m_fontNames;
+ static std::map<std::pair<std::string, int>, NoteFont *> m_fonts;
+};
+
+
+
+}
+
+#endif
diff --git a/src/gui/editors/notation/NoteFontMap.cpp b/src/gui/editors/notation/NoteFontMap.cpp
new file mode 100644
index 0000000..e11c126
--- /dev/null
+++ b/src/gui/editors/notation/NoteFontMap.cpp
@@ -0,0 +1,1088 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
+
+/*
+ Rosegarden
+ A MIDI and audio sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "NoteFontMap.h"
+#include "misc/Debug.h"
+
+#include <klocale.h>
+#include <kstddirs.h>
+#include "misc/Strings.h"
+#include "base/Exception.h"
+#include "SystemFont.h"
+#include <kglobal.h>
+#include <qfile.h>
+#include <qfileinfo.h>
+#include <qpixmap.h>
+#include <qregexp.h>
+#include <qstring.h>
+#include <qstringlist.h>
+
+
+namespace Rosegarden
+{
+
+NoteFontMap::NoteFontMap(std::string name) :
+ m_name(name),
+ m_smooth(false),
+ m_srcDirectory(name),
+ m_characterDestination(0),
+ m_hotspotCharName(""),
+ m_errorString(i18n("unknown error")),
+ m_ok(true)
+{
+ m_fontDirectory = KGlobal::dirs()->findResource("appdata", "fonts/");
+
+ QString mapFileName;
+
+ QString mapFileMixedName = QString("%1/mappings/%2.xml")
+ .arg(m_fontDirectory)
+ .arg(strtoqstr(name));
+
+ QFileInfo mapFileMixedInfo(mapFileMixedName);
+
+ if (!mapFileMixedInfo.isReadable()) {
+
+ QString lowerName = strtoqstr(name).lower();
+ lowerName.replace(QRegExp(" "), "_");
+ QString mapFileLowerName = QString("%1/mappings/%2.xml")
+ .arg(m_fontDirectory)
+ .arg(lowerName);
+
+ QFileInfo mapFileLowerInfo(mapFileLowerName);
+
+ if (!mapFileLowerInfo.isReadable()) {
+ if (mapFileLowerName != mapFileMixedName) {
+ throw MappingFileReadFailed
+ (qstrtostr(i18n("Can't open font mapping file %1 or %2").
+ arg(mapFileMixedName).arg(mapFileLowerName)));
+ } else {
+ throw MappingFileReadFailed
+ (qstrtostr(i18n("Can't open font mapping file %1").
+ arg(mapFileMixedName)));
+ }
+ } else {
+ mapFileName = mapFileLowerName;
+ }
+ } else {
+ mapFileName = mapFileMixedName;
+ }
+
+ QFile mapFile(mapFileName);
+
+ QXmlInputSource source(mapFile);
+ QXmlSimpleReader reader;
+ reader.setContentHandler(this);
+ reader.setErrorHandler(this);
+ bool ok = reader.parse(source);
+ mapFile.close();
+
+ if (!ok) {
+ throw MappingFileReadFailed(qstrtostr(m_errorString));
+ }
+}
+
+NoteFontMap::~NoteFontMap()
+{
+ for (SystemFontMap::iterator i = m_systemFontCache.begin();
+ i != m_systemFontCache.end(); ++i) {
+ delete i->second;
+ }
+}
+
+bool
+NoteFontMap::characters(QString &chars)
+{
+ if (!m_characterDestination)
+ return true;
+ *m_characterDestination += qstrtostr(chars);
+ return true;
+}
+
+int
+NoteFontMap::toSize(int baseSize, double factor, bool limitAtOne)
+{
+ double dsize = factor * baseSize;
+ dsize += 0.5;
+ if (limitAtOne && dsize < 1.0)
+ dsize = 1.0;
+ return int(dsize);
+}
+
+bool
+NoteFontMap::startElement(const QString &, const QString &,
+ const QString &qName,
+ const QXmlAttributes &attributes)
+{
+ QString lcName = qName.lower();
+ m_characterDestination = 0;
+
+ // The element names are actually unique within the whole file;
+ // we don't bother checking we're in the right context. Leave that
+ // to the DTD, when we have one.
+
+ if (lcName == "rosegarden-font-encoding") {
+
+ QString s;
+
+ s = attributes.value("name");
+ if (s) {
+ m_name = qstrtostr(s);
+ m_srcDirectory = m_name;
+ }
+
+ } else if (lcName == "font-information") {
+
+ QString s;
+
+ s = attributes.value("origin");
+ if (s)
+ m_origin = qstrtostr(s);
+
+ s = attributes.value("copyright");
+ if (s)
+ m_copyright = qstrtostr(s);
+
+ s = attributes.value("mapped-by");
+ if (s)
+ m_mappedBy = qstrtostr(s);
+
+ s = attributes.value("type");
+ if (s)
+ m_type = qstrtostr(s);
+
+ s = attributes.value("autocrop");
+ if (s) {
+ std::cerr << "Warning: autocrop attribute in note font mapping file is no longer supported\n(all fonts are now always autocropped)" << std::endl;
+ }
+
+ s = attributes.value("smooth");
+ if (s)
+ m_smooth = (s.lower() == "true");
+
+ } else if (lcName == "font-sizes") {
+ }
+ else if (lcName == "font-size") {
+
+ QString s = attributes.value("note-height");
+ if (!s) {
+ m_errorString = "note-height is a required attribute of font-size";
+ return false;
+ }
+ int noteHeight = s.toInt();
+
+ SizeData &sizeData = m_sizes[noteHeight];
+
+ s = attributes.value("staff-line-thickness");
+ if (s)
+ sizeData.setStaffLineThickness(s.toInt());
+
+ s = attributes.value("leger-line-thickness");
+ if (s)
+ sizeData.setLegerLineThickness(s.toInt());
+
+ s = attributes.value("stem-thickness");
+ if (s)
+ sizeData.setStemThickness(s.toInt());
+
+ s = attributes.value("beam-thickness");
+ if (s)
+ sizeData.setBeamThickness(s.toInt());
+
+ s = attributes.value("stem-length");
+ if (s)
+ sizeData.setStemLength(s.toInt());
+
+ s = attributes.value("flag-spacing");
+ if (s)
+ sizeData.setFlagSpacing(s.toInt());
+
+ s = attributes.value("border-x");
+ if (s) {
+ std::cerr << "Warning: border-x attribute in note font mapping file is no longer supported\n(use hotspot-x for note head or flag)" << std::endl;
+ }
+
+ s = attributes.value("border-y");
+ if (s) {
+ std::cerr << "Warning: border-y attribute in note font mapping file is no longer supported" << std::endl;
+ }
+
+ int fontId = 0;
+ s = attributes.value("font-id");
+ if (s)
+ fontId = s.toInt();
+
+ s = attributes.value("font-height");
+ if (s)
+ sizeData.setFontHeight(fontId, s.toInt());
+
+ } else if (lcName == "font-scale") {
+
+ double fontHeight = -1.0;
+ double beamThickness = -1.0;
+ double stemLength = -1.0;
+ double flagSpacing = -1.0;
+ double staffLineThickness = -1.0;
+ double legerLineThickness = -1.0;
+ double stemThickness = -1.0;
+
+ QString s;
+
+ s = attributes.value("font-height");
+ if (s)
+ fontHeight = qstrtodouble(s);
+ else {
+ m_errorString = "font-height is a required attribute of font-scale";
+ return false;
+ }
+
+ s = attributes.value("staff-line-thickness");
+ if (s)
+ staffLineThickness = qstrtodouble(s);
+
+ s = attributes.value("leger-line-thickness");
+ if (s)
+ legerLineThickness = qstrtodouble(s);
+
+ s = attributes.value("stem-thickness");
+ if (s)
+ stemThickness = qstrtodouble(s);
+
+ s = attributes.value("beam-thickness");
+ if (s)
+ beamThickness = qstrtodouble(s);
+
+ s = attributes.value("stem-length");
+ if (s)
+ stemLength = qstrtodouble(s);
+
+ s = attributes.value("flag-spacing");
+ if (s)
+ flagSpacing = qstrtodouble(s);
+
+ int fontId = 0;
+ s = attributes.value("font-id");
+ if (s)
+ fontId = s.toInt();
+
+ //!!! need to be able to calculate max size -- checkFont needs
+ //to take a size argument; unfortunately Qt doesn't seem to be
+ //able to report to us when a scalable font was loaded in the
+ //wrong size, so large sizes might be significantly inaccurate
+ //as it just stops scaling up any further at somewhere around
+ //120px. We could test whether the metric for the black
+ //notehead is noticeably smaller than the notehead should be,
+ //and reject if so? [update -- no, that doesn't work either,
+ //Qt just returns the correct metric even if drawing the
+ //incorrect size]
+
+ for (int sz = 1; sz <= 30; sz += (sz == 1 ? 1 : 2)) {
+
+ SizeData & sizeData = m_sizes[sz];
+ unsigned int temp;
+
+ if (sizeData.getStaffLineThickness(temp) == false &&
+ staffLineThickness >= 0.0)
+ sizeData.setStaffLineThickness(toSize(sz, staffLineThickness, true));
+
+ if (sizeData.getLegerLineThickness(temp) == false &&
+ legerLineThickness >= 0.0)
+ sizeData.setLegerLineThickness(toSize(sz, legerLineThickness, true));
+
+ if (sizeData.getStemThickness(temp) == false &&
+ stemThickness >= 0.0)
+ sizeData.setStemThickness(toSize(sz, stemThickness, true));
+
+ if (sizeData.getBeamThickness(temp) == false &&
+ beamThickness >= 0.0)
+ sizeData.setBeamThickness(toSize(sz, beamThickness, true));
+
+ if (sizeData.getStemLength(temp) == false &&
+ stemLength >= 0.0)
+ sizeData.setStemLength(toSize(sz, stemLength, true));
+
+ if (sizeData.getFlagSpacing(temp) == false &&
+ flagSpacing >= 0.0)
+ sizeData.setFlagSpacing(toSize(sz, flagSpacing, true));
+
+ if (sizeData.getFontHeight(fontId, temp) == false)
+ sizeData.setFontHeight(fontId, toSize(sz, fontHeight, true));
+ }
+
+ } else if (lcName == "font-symbol-map") {
+ }
+ else if (lcName == "src-directory") {
+
+ QString d = attributes.value("name");
+ if (!d) {
+ m_errorString = "name is a required attribute of src-directory";
+ return false;
+ }
+
+ m_srcDirectory = qstrtostr(d);
+
+ } else if (lcName == "codebase") {
+
+ int bn = 0, fn = 0;
+ bool ok;
+ QString base = attributes.value("base");
+ if (!base) {
+ m_errorString = "base is a required attribute of codebase";
+ return false;
+ }
+ bn = base.toInt(&ok);
+ if (!ok || bn < 0) {
+ m_errorString =
+ QString("invalid base attribute \"%1\" (must be integer >= 0)").
+ arg(base);
+ return false;
+ }
+
+ QString fontId = attributes.value("font-id");
+ if (!fontId) {
+ m_errorString = "font-id is a required attribute of codebase";
+ return false;
+ }
+ fn = fontId.stripWhiteSpace().toInt(&ok);
+ if (!ok || fn < 0) {
+ m_errorString =
+ QString("invalid font-id attribute \"%1\" (must be integer >= 0)").
+ arg(fontId);
+ return false;
+ }
+
+ m_bases[fn] = bn;
+
+ } else if (lcName == "symbol") {
+
+ QString symbolName = attributes.value("name");
+ if (!symbolName) {
+ m_errorString = "name is a required attribute of symbol";
+ return false;
+ }
+ SymbolData symbolData;
+
+ QString src = attributes.value("src");
+ QString code = attributes.value("code");
+ QString glyph = attributes.value("glyph");
+
+ int icode = -1;
+ bool ok = false;
+ if (code) {
+ icode = code.stripWhiteSpace().toInt(&ok);
+ if (!ok || icode < 0) {
+ m_errorString =
+ QString("invalid code attribute \"%1\" (must be integer >= 0)").
+ arg(code);
+ return false;
+ }
+ symbolData.setCode(icode);
+ }
+
+ int iglyph = -1;
+ ok = false;
+ if (glyph) {
+ iglyph = glyph.stripWhiteSpace().toInt(&ok);
+ if (!ok || iglyph < 0) {
+ m_errorString =
+ QString("invalid glyph attribute \"%1\" (must be integer >= 0)").
+ arg(glyph);
+ return false;
+ }
+ symbolData.setGlyph(iglyph);
+ }
+
+ if (!src && icode < 0 && iglyph < 0) {
+ m_errorString = "symbol must have either src, code, or glyph attribute";
+ return false;
+ }
+ if (src)
+ symbolData.setSrc(qstrtostr(src));
+
+ QString inversionSrc = attributes.value("inversion-src");
+ if (inversionSrc)
+ symbolData.setInversionSrc(qstrtostr(inversionSrc));
+
+ QString inversionCode = attributes.value("inversion-code");
+ if (inversionCode) {
+ icode = inversionCode.stripWhiteSpace().toInt(&ok);
+ if (!ok || icode < 0) {
+ m_errorString =
+ QString("invalid inversion code attribute \"%1\" (must be integer >= 0)").
+ arg(inversionCode);
+ return false;
+ }
+ symbolData.setInversionCode(icode);
+ }
+
+ QString inversionGlyph = attributes.value("inversion-glyph");
+ if (inversionGlyph) {
+ iglyph = inversionGlyph.stripWhiteSpace().toInt(&ok);
+ if (!ok || iglyph < 0) {
+ m_errorString =
+ QString("invalid inversion glyph attribute \"%1\" (must be integer >= 0)").
+ arg(inversionGlyph);
+ return false;
+ }
+ symbolData.setInversionGlyph(iglyph);
+ }
+
+ QString fontId = attributes.value("font-id");
+ if (fontId) {
+ int n = fontId.stripWhiteSpace().toInt(&ok);
+ if (!ok || n < 0) {
+ m_errorString =
+ QString("invalid font-id attribute \"%1\" (must be integer >= 0)").
+ arg(fontId);
+ return false;
+ }
+ symbolData.setFontId(n);
+ }
+
+ m_data[qstrtostr(symbolName.upper())] = symbolData;
+
+ } else if (lcName == "font-hotspots") {
+ }
+ else if (lcName == "hotspot") {
+
+ QString s = attributes.value("name");
+ if (!s) {
+ m_errorString = "name is a required attribute of hotspot";
+ return false;
+ }
+ m_hotspotCharName = qstrtostr(s.upper());
+
+ } else if (lcName == "scaled") {
+
+ if (m_hotspotCharName == "") {
+ m_errorString = "scaled-element must be in hotspot-element";
+ return false;
+ }
+
+ QString s = attributes.value("x");
+ double x = -1.0;
+ if (s)
+ x = qstrtodouble(s);
+
+ s = attributes.value("y");
+ if (!s) {
+ m_errorString = "y is a required attribute of scaled";
+ return false;
+ }
+ double y = qstrtodouble(s);
+
+ HotspotDataMap::iterator i = m_hotspots.find(m_hotspotCharName);
+ if (i == m_hotspots.end()) {
+ m_hotspots[m_hotspotCharName] = HotspotData();
+ i = m_hotspots.find(m_hotspotCharName);
+ }
+
+ i->second.setScaledHotspot(x, y);
+
+ } else if (lcName == "fixed") {
+
+ if (m_hotspotCharName == "") {
+ m_errorString = "fixed-element must be in hotspot-element";
+ return false;
+ }
+
+ QString s = attributes.value("x");
+ int x = 0;
+ if (s)
+ x = s.toInt();
+
+ s = attributes.value("y");
+ int y = 0;
+ if (s)
+ y = s.toInt();
+
+ HotspotDataMap::iterator i = m_hotspots.find(m_hotspotCharName);
+ if (i == m_hotspots.end()) {
+ m_hotspots[m_hotspotCharName] = HotspotData();
+ i = m_hotspots.find(m_hotspotCharName);
+ }
+
+ i->second.addHotspot(0, x, y);
+
+ } else if (lcName == "when") {
+
+ if (m_hotspotCharName == "") {
+ m_errorString = "when-element must be in hotspot-element";
+ return false;
+ }
+
+ QString s = attributes.value("note-height");
+ if (!s) {
+ m_errorString = "note-height is a required attribute of when";
+ return false;
+ }
+ int noteHeight = s.toInt();
+
+ s = attributes.value("x");
+ int x = 0;
+ if (s)
+ x = s.toInt();
+
+ s = attributes.value("y");
+ if (!s) {
+ m_errorString = "y is a required attribute of when";
+ return false;
+ }
+ int y = s.toInt();
+
+ HotspotDataMap::iterator i = m_hotspots.find(m_hotspotCharName);
+ if (i == m_hotspots.end()) {
+ m_hotspots[m_hotspotCharName] = HotspotData();
+ i = m_hotspots.find(m_hotspotCharName);
+ }
+
+ i->second.addHotspot(noteHeight, x, y);
+
+ } else if (lcName == "font-requirements") {
+ }
+ else if (lcName == "font-requirement") {
+
+ QString id = attributes.value("font-id");
+ int n = -1;
+ bool ok = false;
+ if (id) {
+ n = id.stripWhiteSpace().toInt(&ok);
+ if (!ok) {
+ m_errorString =
+ QString("invalid font-id attribute \"%1\" (must be integer >= 0)").
+ arg(id);
+ return false;
+ }
+ } else {
+ m_errorString = "font-id is a required attribute of font-requirement";
+ return false;
+ }
+
+ QString name = attributes.value("name");
+ QString names = attributes.value("names");
+
+ if (name) {
+ if (names) {
+ m_errorString = "font-requirement may have name or names attribute, but not both";
+ return false;
+ }
+
+ SystemFont *font = SystemFont::loadSystemFont
+ (SystemFontSpec(name, 12));
+
+ if (font) {
+ m_systemFontNames[n] = name;
+ delete font;
+ } else {
+ std::cerr << QString("Warning: Unable to load font \"%1\"").arg(name) << std::endl;
+ m_ok = false;
+ }
+
+ } else if (names) {
+
+ bool have = false;
+ QStringList list = QStringList::split(",", names, false);
+ for (QStringList::Iterator i = list.begin(); i != list.end(); ++i) {
+ SystemFont *font = SystemFont::loadSystemFont
+ (SystemFontSpec(*i, 12));
+ if (font) {
+ m_systemFontNames[n] = *i;
+ have = true;
+ delete font;
+ break;
+ }
+ }
+ if (!have) {
+ std::cerr << QString("Warning: Unable to load any of the fonts in \"%1\"").
+ arg(names) << std::endl;
+ m_ok = false;
+ }
+
+ } else {
+ m_errorString = "font-requirement must have either name or names attribute";
+ return false;
+ }
+
+ QString s = attributes.value("strategy").lower();
+ SystemFont::Strategy strategy = SystemFont::PreferGlyphs;
+
+ if (s) {
+ if (s == "prefer-glyphs")
+ strategy = SystemFont::PreferGlyphs;
+ else if (s == "prefer-codes")
+ strategy = SystemFont::PreferCodes;
+ else if (s == "only-glyphs")
+ strategy = SystemFont::OnlyGlyphs;
+ else if (s == "only-codes")
+ strategy = SystemFont::OnlyCodes;
+ else {
+ std::cerr << "Warning: Unknown strategy value " << s
+ << " (known values are prefer-glyphs, prefer-codes,"
+ << " only-glyphs, only-codes)" << std::endl;
+ }
+ }
+
+ m_systemFontStrategies[n] = strategy;
+
+ } else {
+ }
+
+ if (m_characterDestination)
+ *m_characterDestination = "";
+ return true;
+}
+
+bool
+NoteFontMap::error(const QXmlParseException& exception)
+{
+ m_errorString = QString("%1 at line %2, column %3: %4")
+ .arg(exception.message())
+ .arg(exception.lineNumber())
+ .arg(exception.columnNumber())
+ .arg(m_errorString);
+ return QXmlDefaultHandler::error(exception);
+}
+
+bool
+NoteFontMap::fatalError(const QXmlParseException& exception)
+{
+ m_errorString = QString("%1 at line %2, column %3: %4")
+ .arg(exception.message())
+ .arg(exception.lineNumber())
+ .arg(exception.columnNumber())
+ .arg(m_errorString);
+ return QXmlDefaultHandler::fatalError(exception);
+}
+
+std::set<int>
+NoteFontMap::getSizes() const
+{
+ std::set<int> sizes;
+
+ for (SizeDataMap::const_iterator i = m_sizes.begin();
+ i != m_sizes.end(); ++i) {
+ sizes.insert(i->first);
+ }
+
+ return sizes;
+}
+
+std::set<CharName>
+NoteFontMap::getCharNames() const
+{
+ std::set<CharName> names;
+
+ for (SymbolDataMap::const_iterator i = m_data.begin();
+ i != m_data.end(); ++i) {
+ names.insert(i->first);
+ }
+
+ return names;
+}
+
+bool
+NoteFontMap::checkFile(int size, std::string &src) const
+{
+ QString pixmapFileMixedName = QString("%1/%2/%3/%4.xpm")
+ .arg(m_fontDirectory)
+ .arg(strtoqstr(m_srcDirectory))
+ .arg(size)
+ .arg(strtoqstr(src));
+
+ QFileInfo pixmapFileMixedInfo(pixmapFileMixedName);
+
+ if (!pixmapFileMixedInfo.isReadable()) {
+
+ QString pixmapFileLowerName = QString("%1/%2/%3/%4.xpm")
+ .arg(m_fontDirectory)
+ .arg(strtoqstr(m_srcDirectory).lower())
+ .arg(size)
+ .arg(strtoqstr(src));
+
+ QFileInfo pixmapFileLowerInfo(pixmapFileLowerName);
+
+ if (!pixmapFileLowerInfo.isReadable()) {
+ if (pixmapFileMixedName != pixmapFileLowerName) {
+ std::cerr << "Warning: Unable to open pixmap file "
+ << pixmapFileMixedName << " or " << pixmapFileLowerName
+ << std::endl;
+ } else {
+ std::cerr << "Warning: Unable to open pixmap file "
+ << pixmapFileMixedName << std::endl;
+ }
+ return false;
+ } else {
+ src = qstrtostr(pixmapFileLowerName);
+ }
+ } else {
+ src = qstrtostr(pixmapFileMixedName);
+ }
+
+ return true;
+}
+
+bool
+NoteFontMap::hasInversion(int, CharName charName) const
+{
+ SymbolDataMap::const_iterator i = m_data.find(charName);
+ if (i == m_data.end())
+ return false;
+ return i->second.hasInversion();
+}
+
+bool
+NoteFontMap::getSrc(int size, CharName charName, std::string &src) const
+{
+ SymbolDataMap::const_iterator i = m_data.find(charName);
+ if (i == m_data.end())
+ return false;
+
+ src = i->second.getSrc();
+ if (src == "")
+ return false;
+ return checkFile(size, src);
+}
+
+bool
+NoteFontMap::getInversionSrc(int size, CharName charName, std::string &src) const
+{
+ SymbolDataMap::const_iterator i = m_data.find(charName);
+ if (i == m_data.end())
+ return false;
+
+ if (!i->second.hasInversion())
+ return false;
+ src = i->second.getInversionSrc();
+ if (src == "")
+ return false;
+ return checkFile(size, src);
+}
+
+SystemFont *
+NoteFontMap::getSystemFont(int size, CharName charName, int &charBase)
+const
+{
+ SymbolDataMap::const_iterator i = m_data.find(charName);
+ if (i == m_data.end())
+ return false;
+
+ SizeDataMap::const_iterator si = m_sizes.find(size);
+ if (si == m_sizes.end())
+ return false;
+
+ int fontId = i->second.getFontId();
+
+ unsigned int fontHeight = 0;
+ if (!si->second.getFontHeight(fontId, fontHeight)) {
+ if (fontId == 0 || !si->second.getFontHeight(0, fontHeight)) {
+ fontHeight = size;
+ }
+ }
+
+ SystemFontNameMap::const_iterator fni = m_systemFontNames.find(fontId);
+ if (fontId < 0 || fni == m_systemFontNames.end())
+ return false;
+ QString fontName = fni->second;
+
+ CharBaseMap::const_iterator bi = m_bases.find(fontId);
+ if (bi == m_bases.end())
+ charBase = 0;
+ else
+ charBase = bi->second;
+
+ SystemFontSpec spec(fontName, fontHeight);
+ SystemFontMap::const_iterator fi = m_systemFontCache.find(spec);
+ if (fi != m_systemFontCache.end()) {
+ return fi->second;
+ }
+
+ SystemFont *font = SystemFont::loadSystemFont(spec);
+ if (!font)
+ return 0;
+ m_systemFontCache[spec] = font;
+
+ NOTATION_DEBUG << "NoteFontMap::getFont: loaded font " << fontName
+ << " at pixel size " << fontHeight << endl;
+
+ return font;
+}
+
+SystemFont::Strategy
+NoteFontMap::getStrategy(int, CharName charName) const
+{
+ SymbolDataMap::const_iterator i = m_data.find(charName);
+ if (i == m_data.end())
+ return SystemFont::PreferGlyphs;
+
+ int fontId = i->second.getFontId();
+ SystemFontStrategyMap::const_iterator si =
+ m_systemFontStrategies.find(fontId);
+
+ if (si != m_systemFontStrategies.end()) {
+ return si->second;
+ }
+
+ return SystemFont::PreferGlyphs;
+}
+
+bool
+NoteFontMap::getCode(int, CharName charName, int &code) const
+{
+ SymbolDataMap::const_iterator i = m_data.find(charName);
+ if (i == m_data.end())
+ return false;
+
+ code = i->second.getCode();
+ return (code >= 0);
+}
+
+bool
+NoteFontMap::getInversionCode(int, CharName charName, int &code) const
+{
+ SymbolDataMap::const_iterator i = m_data.find(charName);
+ if (i == m_data.end())
+ return false;
+
+ code = i->second.getInversionCode();
+ return (code >= 0);
+}
+
+bool
+NoteFontMap::getGlyph(int, CharName charName, int &glyph) const
+{
+ SymbolDataMap::const_iterator i = m_data.find(charName);
+ if (i == m_data.end())
+ return false;
+
+ glyph = i->second.getGlyph();
+ return (glyph >= 0);
+}
+
+bool
+NoteFontMap::getInversionGlyph(int, CharName charName, int &glyph) const
+{
+ SymbolDataMap::const_iterator i = m_data.find(charName);
+ if (i == m_data.end())
+ return false;
+
+ glyph = i->second.getInversionGlyph();
+ return (glyph >= 0);
+}
+
+bool
+NoteFontMap::getStaffLineThickness(int size, unsigned int &thickness) const
+{
+ SizeDataMap::const_iterator i = m_sizes.find(size);
+ if (i == m_sizes.end())
+ return false;
+
+ return i->second.getStaffLineThickness(thickness);
+}
+
+bool
+NoteFontMap::getLegerLineThickness(int size, unsigned int &thickness) const
+{
+ SizeDataMap::const_iterator i = m_sizes.find(size);
+ if (i == m_sizes.end())
+ return false;
+
+ return i->second.getLegerLineThickness(thickness);
+}
+
+bool
+NoteFontMap::getStemThickness(int size, unsigned int &thickness) const
+{
+ SizeDataMap::const_iterator i = m_sizes.find(size);
+ if (i == m_sizes.end())
+ return false;
+
+ return i->second.getStemThickness(thickness);
+}
+
+bool
+NoteFontMap::getBeamThickness(int size, unsigned int &thickness) const
+{
+ SizeDataMap::const_iterator i = m_sizes.find(size);
+ if (i == m_sizes.end())
+ return false;
+
+ return i->second.getBeamThickness(thickness);
+}
+
+bool
+NoteFontMap::getStemLength(int size, unsigned int &length) const
+{
+ SizeDataMap::const_iterator i = m_sizes.find(size);
+ if (i == m_sizes.end())
+ return false;
+
+ return i->second.getStemLength(length);
+}
+
+bool
+NoteFontMap::getFlagSpacing(int size, unsigned int &spacing) const
+{
+ SizeDataMap::const_iterator i = m_sizes.find(size);
+ if (i == m_sizes.end())
+ return false;
+
+ return i->second.getFlagSpacing(spacing);
+}
+
+bool
+NoteFontMap::getHotspot(int size, CharName charName, int width, int height,
+ int &x, int &y) const
+{
+ HotspotDataMap::const_iterator i = m_hotspots.find(charName);
+ if (i == m_hotspots.end())
+ return false;
+ return i->second.getHotspot(size, width, height, x, y);
+}
+
+bool
+NoteFontMap::HotspotData::getHotspot(int size, int width, int height,
+ int &x, int &y) const
+{
+ DataMap::const_iterator i = m_data.find(size);
+ if (i == m_data.end()) {
+ i = m_data.find(0); // fixed-pixel hotspot
+ x = 0;
+ if (m_scaled.first >= 0) {
+ x = toSize(width, m_scaled.first, false);
+ } else {
+ if (i != m_data.end()) {
+ x = i->second.first;
+ }
+ }
+ if (m_scaled.second >= 0) {
+ y = toSize(height, m_scaled.second, false);
+ return true;
+ } else {
+ if (i != m_data.end()) {
+ y = i->second.second;
+ return true;
+ }
+ return false;
+ }
+ }
+ x = i->second.first;
+ y = i->second.second;
+ return true;
+}
+
+QStringList
+NoteFontMap::getSystemFontNames() const
+{
+ QStringList names;
+ for (SystemFontNameMap::const_iterator i = m_systemFontNames.begin();
+ i != m_systemFontNames.end(); ++i) {
+ names.append(i->second);
+ }
+ return names;
+}
+
+void
+NoteFontMap::dump() const
+{
+ // debug code
+
+ std::cout << "Font data:\nName: " << getName() << "\nOrigin: " << getOrigin()
+ << "\nCopyright: " << getCopyright() << "\nMapped by: "
+ << getMappedBy() << "\nType: " << getType()
+ << "\nSmooth: " << isSmooth() << std::endl;
+
+ std::set<int> sizes = getSizes();
+ std::set<CharName> names = getCharNames();
+
+ for (std::set<int>::iterator sizei = sizes.begin(); sizei != sizes.end();
+ ++sizei) {
+
+ std::cout << "\nSize: " << *sizei << "\n" << std::endl;
+
+ unsigned int t = 0;
+
+ if (getStaffLineThickness(*sizei, t)) {
+ std::cout << "Staff line thickness: " << t << std::endl;
+ }
+
+ if (getLegerLineThickness(*sizei, t)) {
+ std::cout << "Leger line thickness: " << t << std::endl;
+ }
+
+ if (getStemThickness(*sizei, t)) {
+ std::cout << "Stem thickness: " << t << std::endl;
+ }
+
+ if (getBeamThickness(*sizei, t)) {
+ std::cout << "Beam thickness: " << t << std::endl;
+ }
+
+ if (getStemLength(*sizei, t)) {
+ std::cout << "Stem length: " << t << std::endl;
+ }
+
+ if (getFlagSpacing(*sizei, t)) {
+ std::cout << "Flag spacing: " << t << std::endl;
+ }
+
+ for (std::set<CharName>::iterator namei = names.begin();
+ namei != names.end(); ++namei) {
+
+ std::cout << "\nCharacter: " << namei->c_str() << std::endl;
+
+ std::string s;
+ int x, y, c;
+
+ if (getSrc(*sizei, *namei, s)) {
+ std::cout << "Src: " << s << std::endl;
+ }
+
+ if (getInversionSrc(*sizei, *namei, s)) {
+ std::cout << "Inversion src: " << s << std::endl;
+ }
+
+ if (getCode(*sizei, *namei, c)) {
+ std::cout << "Code: " << c << std::endl;
+ }
+
+ if (getInversionCode(*sizei, *namei, c)) {
+ std::cout << "Inversion code: " << c << std::endl;
+ }
+
+ if (getGlyph(*sizei, *namei, c)) {
+ std::cout << "Glyph: " << c << std::endl;
+ }
+
+ if (getInversionGlyph(*sizei, *namei, c)) {
+ std::cout << "Inversion glyph: " << c << std::endl;
+ }
+
+ if (getHotspot(*sizei, *namei, 1, 1, x, y)) {
+ std::cout << "Hot spot: (" << x << "," << y << ")" << std::endl;
+ }
+ }
+ }
+}
+
+}
diff --git a/src/gui/editors/notation/NoteFontMap.h b/src/gui/editors/notation/NoteFontMap.h
new file mode 100644
index 0000000..119db76
--- /dev/null
+++ b/src/gui/editors/notation/NoteFontMap.h
@@ -0,0 +1,333 @@
+
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
+
+/*
+ Rosegarden
+ A MIDI and audio sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_NOTEFONTMAP_H_
+#define _RG_NOTEFONTMAP_H_
+
+#include "base/Exception.h"
+#include <map>
+#include <set>
+#include <string>
+#include "SystemFont.h"
+#include <qstring.h>
+#include <qstringlist.h>
+#include <utility>
+#include <qxml.h>
+#include "gui/editors/notation/NoteCharacterNames.h"
+
+
+class QXmlParseException;
+class QXmlAttributes;
+
+
+namespace Rosegarden
+{
+
+
+
+class NoteFontMap : public QXmlDefaultHandler
+{
+public:
+ typedef Exception MappingFileReadFailed;
+
+ NoteFontMap(std::string name); // load and parse the XML mapping file
+ ~NoteFontMap();
+
+ /**
+ * ok() returns false if the file read succeeded but the font
+ * relies on system fonts that are not available. (If the file
+ * read fails, the constructor throws MappingFileReadFailed.)
+ */
+ bool ok() const { return m_ok; }
+
+ std::string getName() const { return m_name; }
+ std::string getOrigin() const { return m_origin; }
+ std::string getCopyright() const { return m_copyright; }
+ std::string getMappedBy() const { return m_mappedBy; }
+ std::string getType() const { return m_type; }
+ bool isSmooth() const { return m_smooth; }
+
+ std::set<int> getSizes() const;
+ std::set<CharName> getCharNames() const;
+
+ bool getStaffLineThickness(int size, unsigned int &thickness) const;
+ bool getLegerLineThickness(int size, unsigned int &thickness) const;
+ bool getStemThickness(int size, unsigned int &thickness) const;
+ bool getBeamThickness(int size, unsigned int &thickness) const;
+ bool getStemLength(int size, unsigned int &length) const;
+ bool getFlagSpacing(int size, unsigned int &spacing) const;
+
+ bool hasInversion(int size, CharName charName) const;
+
+ bool getSrc(int size, CharName charName, std::string &src) const;
+ bool getInversionSrc(int size, CharName charName, std::string &src) const;
+
+ SystemFont *getSystemFont(int size, CharName charName, int &charBase) const;
+ SystemFont::Strategy getStrategy(int size, CharName charName) const;
+ bool getCode(int size, CharName charName, int &code) const;
+ bool getInversionCode(int size, CharName charName, int &code) const;
+ bool getGlyph(int size, CharName charName, int &glyph) const;
+ bool getInversionGlyph(int size, CharName charName, int &glyph) const;
+
+ bool getHotspot(int size, CharName charName, int width, int height,
+ int &x, int &y) const;
+
+ // Xml handler methods:
+
+ virtual bool startElement
+ (const QString& namespaceURI, const QString& localName,
+ const QString& qName, const QXmlAttributes& atts);
+
+ virtual bool characters(QString &);
+
+ bool error(const QXmlParseException& exception);
+ bool fatalError(const QXmlParseException& exception);
+
+ void dump() const;
+
+ // Not for general use, but very handy for diagnostic display
+ QStringList getSystemFontNames() const;
+
+ // want this to be private, but need access from HotspotData
+ static int toSize(int baseSize, double factor, bool limitAtOne);
+
+private:
+ class SymbolData
+ {
+ public:
+ SymbolData() : m_fontId(0),
+ m_src(""), m_inversionSrc(""),
+ m_code(-1), m_inversionCode(-1),
+ m_glyph(-1), m_inversionGlyph(-1) { }
+ ~SymbolData() { }
+
+ void setFontId(int id) { m_fontId = id; }
+ int getFontId() const { return m_fontId; }
+
+ void setSrc(std::string src) { m_src = src; }
+ std::string getSrc() const { return m_src; }
+
+ void setCode(int code) { m_code = code; }
+ int getCode() const { return m_code; }
+
+ void setGlyph(int glyph) { m_glyph = glyph; }
+ int getGlyph() const { return m_glyph; }
+
+ void setInversionSrc(std::string inversion) { m_inversionSrc = inversion; }
+ std::string getInversionSrc() const { return m_inversionSrc; }
+
+ void setInversionCode(int code) { m_inversionCode = code; }
+ int getInversionCode() const { return m_inversionCode; }
+
+ void setInversionGlyph(int glyph) { m_inversionGlyph = glyph; }
+ int getInversionGlyph() const { return m_inversionGlyph; }
+
+ bool hasInversion() const {
+ return m_inversionGlyph >= 0 ||
+ m_inversionCode >= 0 ||
+ m_inversionSrc != "";
+ }
+
+ private:
+ int m_fontId;
+ std::string m_src;
+ std::string m_inversionSrc;
+ int m_code;
+ int m_inversionCode;
+ int m_glyph;
+ int m_inversionGlyph;
+ };
+
+ class HotspotData
+ {
+ private:
+ typedef std::pair<int, int> Point;
+ typedef std::pair<double, double> ScaledPoint;
+ typedef std::map<int, Point> DataMap;
+
+ public:
+ HotspotData() : m_scaled(-1.0, -1.0) { }
+ ~HotspotData() { }
+
+ void addHotspot(int size, int x, int y) {
+ m_data[size] = Point(x, y);
+ }
+
+ void setScaledHotspot(double x, double y) {
+ m_scaled = ScaledPoint(x, y);
+ }
+
+ bool getHotspot(int size, int width, int height, int &x, int &y) const;
+
+ private:
+ DataMap m_data;
+ ScaledPoint m_scaled;
+ };
+
+ class SizeData
+ {
+ public:
+ SizeData() : m_stemThickness(-1),
+ m_beamThickness(-1),
+ m_stemLength(-1),
+ m_flagSpacing(-1),
+ m_staffLineThickness(-1),
+ m_legerLineThickness(-1) { }
+ ~SizeData() { }
+
+ void setStemThickness(unsigned int i) {
+ m_stemThickness = (int)i;
+ }
+ void setBeamThickness(unsigned int i) {
+ m_beamThickness = (int)i;
+ }
+ void setStemLength(unsigned int i) {
+ m_stemLength = (int)i;
+ }
+ void setFlagSpacing(unsigned int i) {
+ m_flagSpacing = (int)i;
+ }
+ void setStaffLineThickness(unsigned int i) {
+ m_staffLineThickness = (int)i;
+ }
+ void setLegerLineThickness(unsigned int i) {
+ m_legerLineThickness = (int)i;
+ }
+ void setFontHeight(int fontId, unsigned int h) {
+ m_fontHeights[fontId] = (int)h;
+ }
+
+ bool getStemThickness(unsigned int &i) const {
+ if (m_stemThickness >= 0) {
+ i = (unsigned int)m_stemThickness;
+ return true;
+ } else return false;
+ }
+
+ bool getBeamThickness(unsigned int &i) const {
+ if (m_beamThickness >= 0) {
+ i = (unsigned int)m_beamThickness;
+ return true;
+ } else return false;
+ }
+
+ bool getStemLength(unsigned int &i) const {
+ if (m_stemLength >= 0) {
+ i = (unsigned int)m_stemLength;
+ return true;
+ } else return false;
+ }
+
+ bool getFlagSpacing(unsigned int &i) const {
+ if (m_flagSpacing >= 0) {
+ i = (unsigned int)m_flagSpacing;
+ return true;
+ } else return false;
+ }
+
+ bool getStaffLineThickness(unsigned int &i) const {
+ if (m_staffLineThickness >= 0) {
+ i = (unsigned int)m_staffLineThickness;
+ return true;
+ } else return false;
+ }
+
+ bool getLegerLineThickness(unsigned int &i) const {
+ if (m_legerLineThickness >= 0) {
+ i = (unsigned int)m_legerLineThickness;
+ return true;
+ } else return false;
+ }
+
+ bool getFontHeight(int fontId, unsigned int &h) const {
+ std::map<int, int>::const_iterator fhi = m_fontHeights.find(fontId);
+ if (fhi != m_fontHeights.end()) {
+ h = (unsigned int)fhi->second;
+ return true;
+ }
+ return false;
+ }
+
+ private:
+ int m_stemThickness;
+ int m_beamThickness;
+ int m_stemLength;
+ int m_flagSpacing;
+ int m_staffLineThickness;
+ int m_legerLineThickness;
+ std::map<int, int> m_fontHeights; // per-font-id
+ };
+
+ //--------------- Data members ---------------------------------
+
+ std::string m_name;
+ std::string m_origin;
+ std::string m_copyright;
+ std::string m_mappedBy;
+ std::string m_type;
+ bool m_smooth;
+
+ std::string m_srcDirectory;
+
+ typedef std::map<CharName, SymbolData> SymbolDataMap;
+ SymbolDataMap m_data;
+
+ typedef std::map<CharName, HotspotData> HotspotDataMap;
+ HotspotDataMap m_hotspots;
+
+ typedef std::map<int, SizeData> SizeDataMap;
+ SizeDataMap m_sizes;
+
+ typedef std::map<int, QString> SystemFontNameMap;
+ SystemFontNameMap m_systemFontNames;
+
+ typedef std::map<int, SystemFont::Strategy> SystemFontStrategyMap;
+ SystemFontStrategyMap m_systemFontStrategies;
+
+ typedef std::map<SystemFontSpec, SystemFont *> SystemFontMap;
+ mutable SystemFontMap m_systemFontCache;
+
+ typedef std::map<int, int> CharBaseMap;
+ CharBaseMap m_bases;
+
+ // For use when reading the XML file:
+ bool m_expectingCharacters;
+ std::string *m_characterDestination;
+ std::string m_hotspotCharName;
+ QString m_errorString;
+
+ bool checkFile(int size, std::string &src) const;
+ QString m_fontDirectory;
+
+ bool m_ok;
+};
+
+
+class NoteCharacterDrawRep;
+
+
+}
+
+#endif
diff --git a/src/gui/editors/notation/NoteFontViewer.cpp b/src/gui/editors/notation/NoteFontViewer.cpp
new file mode 100644
index 0000000..81f07e9
--- /dev/null
+++ b/src/gui/editors/notation/NoteFontViewer.cpp
@@ -0,0 +1,125 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
+
+/*
+ Rosegarden
+ A MIDI and audio sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "NoteFontViewer.h"
+
+#include <klocale.h>
+#include "FontViewFrame.h"
+#include <kcombobox.h>
+#include <kdialogbase.h>
+#include <ktoolbar.h>
+#include <qlabel.h>
+#include <qstring.h>
+#include <qstringlist.h>
+#include <qvbox.h>
+#include <qwidget.h>
+
+
+namespace Rosegarden
+{
+
+void
+NoteFontViewer::slotViewChanged(int i)
+{
+ m_frame->setGlyphs(i == 0);
+
+ m_rows->clear();
+ int firstRow = -1;
+
+ for (int r = 0; r < 256; ++r) {
+ if (m_frame->hasRow(r)) {
+ m_rows->insertItem(QString("%1").arg(r));
+ if (firstRow < 0)
+ firstRow = r;
+ }
+ }
+
+ if (firstRow < 0) {
+ m_rows->setEnabled(false);
+ m_frame->setRow(0);
+ } else {
+ m_rows->setEnabled(true);
+ m_frame->setRow(firstRow);
+ }
+}
+
+void
+NoteFontViewer::slotRowChanged(const QString &s)
+{
+ bool ok;
+ int i = s.toInt(&ok);
+ if (ok)
+ m_frame->setRow(i);
+}
+
+void
+NoteFontViewer::slotFontChanged(const QString &s)
+{
+ m_frame->setFont(s);
+ slotViewChanged(m_view->currentItem());
+}
+
+NoteFontViewer::NoteFontViewer(QWidget *parent, QString noteFontName,
+ QStringList fontNames, int pixelSize) :
+ KDialogBase(parent, 0, true,
+ i18n("Note Font Viewer: %1").arg(noteFontName), Close)
+{
+ QVBox *box = makeVBoxMainWidget();
+ KToolBar* controls = new KToolBar(box);
+ controls->setMargin(3);
+
+ (void) new QLabel(i18n(" Component: "), controls);
+ m_font = new KComboBox(controls);
+
+ for (QStringList::iterator i = fontNames.begin(); i != fontNames.end();
+ ++i) {
+ m_font->insertItem(*i);
+ }
+
+ (void) new QLabel(i18n(" View: "), controls);
+ m_view = new KComboBox(controls);
+
+ m_view->insertItem(i18n("Glyphs"));
+ m_view->insertItem(i18n("Codes"));
+
+ (void) new QLabel(i18n(" Page: "), controls);
+ m_rows = new KComboBox(controls);
+
+ m_frame = new FontViewFrame(pixelSize, box);
+
+ connect(m_font, SIGNAL(activated(const QString &)),
+ this, SLOT(slotFontChanged(const QString &)));
+
+ connect(m_view, SIGNAL(activated(int)),
+ this, SLOT(slotViewChanged(int)));
+
+ connect(m_rows, SIGNAL(activated(const QString &)),
+ this, SLOT(slotRowChanged(const QString &)));
+
+ slotFontChanged(m_font->currentText());
+}
+
+}
+#include "NoteFontViewer.moc"
diff --git a/src/gui/editors/notation/NoteFontViewer.h b/src/gui/editors/notation/NoteFontViewer.h
new file mode 100644
index 0000000..31c8613
--- /dev/null
+++ b/src/gui/editors/notation/NoteFontViewer.h
@@ -0,0 +1,68 @@
+
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
+
+/*
+ Rosegarden
+ A MIDI and audio sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_NOTEFONTVIEWER_H_
+#define _RG_NOTEFONTVIEWER_H_
+
+#include <kdialogbase.h>
+#include <qstring.h>
+#include <qstringlist.h>
+
+
+class QWidget;
+class KComboBox;
+
+
+namespace Rosegarden
+{
+
+class FontViewFrame;
+
+
+class NoteFontViewer : public KDialogBase
+{
+ Q_OBJECT
+
+public:
+ NoteFontViewer(QWidget *parent, QString noteFontName,
+ QStringList systemFontNames, int pixelSize);
+
+protected slots:
+ void slotFontChanged(const QString &);
+ void slotViewChanged(int);
+ void slotRowChanged(const QString &);
+
+private:
+ KComboBox *m_font;
+ KComboBox *m_view;
+ KComboBox *m_rows;
+ FontViewFrame *m_frame;
+};
+
+
+
+}
+
+#endif
diff --git a/src/gui/editors/notation/NoteInserter.cpp b/src/gui/editors/notation/NoteInserter.cpp
new file mode 100644
index 0000000..66adafe
--- /dev/null
+++ b/src/gui/editors/notation/NoteInserter.cpp
@@ -0,0 +1,722 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
+
+/*
+ Rosegarden
+ A MIDI and audio sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "NoteInserter.h"
+#include "misc/Debug.h"
+#include <kapplication.h>
+
+#include "base/BaseProperties.h"
+#include <klocale.h>
+#include "misc/Strings.h"
+#include "document/ConfigGroups.h"
+#include "base/Event.h"
+#include "base/NotationTypes.h"
+#include "base/Segment.h"
+#include "base/Staff.h"
+#include "base/ViewElement.h"
+#include "commands/notation/NoteInsertionCommand.h"
+#include "commands/notation/RestInsertionCommand.h"
+#include "commands/notation/TupletCommand.h"
+#include "gui/general/EditTool.h"
+#include "gui/general/LinedStaff.h"
+#include "gui/general/RosegardenCanvasView.h"
+#include "NotationProperties.h"
+#include "NotationStrings.h"
+#include "NotationTool.h"
+#include "NotationView.h"
+#include "NotationStaff.h"
+#include "NotePixmapFactory.h"
+#include "NoteStyleFactory.h"
+#include <kaction.h>
+#include <kcommand.h>
+#include <kconfig.h>
+#include <qiconset.h>
+#include <qregexp.h>
+#include <qstring.h>
+
+
+namespace Rosegarden
+{
+
+NoteInserter::NoteInserter(NotationView* view)
+ : NotationTool("NoteInserter", view),
+ m_noteType(Note::Quaver),
+ m_noteDots(0),
+ m_autoBeam(true),
+ m_accidental(Accidentals::NoAccidental),
+ m_lastAccidental(Accidentals::NoAccidental),
+ m_followAccidental(false)
+{
+ QIconSet icon;
+
+ KConfig *config = kapp->config();
+ config->setGroup(NotationViewConfigGroup);
+ m_autoBeam = config->readBoolEntry("autobeam", true);
+ m_matrixInsertType = (config->readNumEntry("inserttype", 0) > 0);
+ m_defaultStyle = qstrtostr(config->readEntry
+ ("style", strtoqstr(NoteStyleFactory::DefaultStyle)));
+
+ KToggleAction *autoBeamAction =
+ new KToggleAction(i18n("Auto-Beam when appropriate"), 0, this,
+ SLOT(slotToggleAutoBeam()), actionCollection(),
+ "toggle_auto_beam");
+ autoBeamAction->setChecked(m_autoBeam);
+
+ for (unsigned int i = 0; i < 6; ++i) {
+
+ icon = QIconSet
+ (NotePixmapFactory::toQPixmap(NotePixmapFactory::
+ makeToolbarPixmap(m_actionsAccidental[i][3])));
+ KRadioAction* noteAction = new KRadioAction(i18n(m_actionsAccidental[i][0]),
+ icon, 0, this,
+ m_actionsAccidental[i][1],
+ actionCollection(),
+ m_actionsAccidental[i][2]);
+ noteAction->setExclusiveGroup("accidentals");
+ }
+
+ icon = QIconSet
+ (NotePixmapFactory::toQPixmap(NotePixmapFactory::
+ makeToolbarPixmap("dotted-crotchet")));
+ new KToggleAction(i18n("Dotted note"), icon, 0, this,
+ SLOT(slotToggleDot()), actionCollection(),
+ "toggle_dot");
+
+ icon = QIconSet(NotePixmapFactory::toQPixmap(NotePixmapFactory::
+ makeToolbarPixmap("select")));
+ new KAction(i18n("Switch to Select Tool"), icon, 0, this,
+ SLOT(slotSelectSelected()), actionCollection(),
+ "select");
+
+ new KAction(i18n("Switch to Erase Tool"), "eraser", 0, this,
+ SLOT(slotEraseSelected()), actionCollection(),
+ "erase");
+
+ icon = QIconSet
+ (NotePixmapFactory::toQPixmap(NotePixmapFactory::
+ makeToolbarPixmap("rest-crotchet")));
+ new KAction(i18n("Switch to Inserting Rests"), icon, 0, this,
+ SLOT(slotRestsSelected()), actionCollection(),
+ "rests");
+
+ createMenu("noteinserter.rc");
+
+ connect(m_parentView, SIGNAL(changeAccidental(Accidental, bool)),
+ this, SLOT(slotSetAccidental(Accidental, bool)));
+}
+
+NoteInserter::NoteInserter(const QString& menuName, NotationView* view)
+ : NotationTool(menuName, view),
+ m_noteType(Note::Quaver),
+ m_noteDots(0),
+ m_autoBeam(false),
+ m_clickHappened(false),
+ m_accidental(Accidentals::NoAccidental),
+ m_lastAccidental(Accidentals::NoAccidental),
+ m_followAccidental(false)
+{
+ connect(m_parentView, SIGNAL(changeAccidental(Accidental, bool)),
+ this, SLOT(slotSetAccidental(Accidental, bool)));
+}
+
+NoteInserter::~NoteInserter()
+{}
+
+void NoteInserter::ready()
+{
+ m_clickHappened = false;
+ m_nParentView->setCanvasCursor(Qt::crossCursor);
+ m_nParentView->setHeightTracking(true);
+}
+
+void
+NoteInserter::handleLeftButtonPress(timeT,
+ int,
+ int staffNo,
+ QMouseEvent* e,
+ ViewElement*)
+{
+ if (staffNo < 0)
+ return ;
+ computeLocationAndPreview(e);
+}
+
+int
+NoteInserter::handleMouseMove(timeT,
+ int,
+ QMouseEvent *e)
+{
+ if (m_clickHappened) {
+ computeLocationAndPreview(e);
+ }
+
+ return RosegardenCanvasView::NoFollow;
+}
+
+void
+NoteInserter::handleMouseRelease(timeT,
+ int,
+ QMouseEvent *e)
+{
+ if (!m_clickHappened)
+ return ;
+ bool okay = computeLocationAndPreview(e);
+ m_clickHappened = false;
+ if (!okay)
+ return ;
+ clearPreview();
+
+ Note note(m_noteType, m_noteDots);
+ timeT endTime = m_clickTime + note.getDuration();
+ Segment &segment = m_nParentView->getStaff(m_clickStaffNo)->getSegment();
+
+ Segment::iterator realEnd = segment.findTime(endTime);
+ if (!segment.isBeforeEndMarker( realEnd) ||
+ !segment.isBeforeEndMarker(++realEnd)) {
+ endTime = segment.getEndMarkerTime();
+ } else {
+ endTime = std::max(endTime, (*realEnd)->getNotationAbsoluteTime());
+ }
+
+ Event *lastInsertedEvent = doAddCommand
+ (segment, m_clickTime, endTime, note, m_clickPitch,
+ (m_accidental == Accidentals::NoAccidental &&
+ m_followAccidental) ?
+ m_lastAccidental : m_accidental);
+
+ if (lastInsertedEvent) {
+
+ m_nParentView->setSingleSelectedEvent
+ (m_clickStaffNo, lastInsertedEvent);
+
+ if (m_nParentView->isInChordMode()) {
+ m_nParentView->slotSetInsertCursorAndRecentre
+ (lastInsertedEvent->getAbsoluteTime(), e->x(), (int)e->y(),
+ false);
+ } else {
+ m_nParentView->slotSetInsertCursorAndRecentre
+ (lastInsertedEvent->getAbsoluteTime() +
+ lastInsertedEvent->getDuration(), e->x(), (int)e->y(),
+ false);
+ }
+ }
+}
+
+void
+NoteInserter::insertNote(Segment &segment, timeT insertionTime,
+ int pitch, Accidental accidental,
+ bool suppressPreview)
+{
+ Note note(m_noteType, m_noteDots);
+ timeT endTime = insertionTime + note.getDuration();
+
+ Segment::iterator realEnd = segment.findTime(endTime);
+ if (!segment.isBeforeEndMarker( realEnd) ||
+ !segment.isBeforeEndMarker(++realEnd)) {
+ endTime = segment.getEndMarkerTime();
+ } else {
+ endTime = std::max(endTime, (*realEnd)->getNotationAbsoluteTime());
+ }
+
+ Event *lastInsertedEvent = doAddCommand
+ (segment, insertionTime, endTime, note, pitch, accidental);
+
+ if (lastInsertedEvent) {
+
+ m_nParentView->setSingleSelectedEvent(segment, lastInsertedEvent);
+
+ if (m_nParentView->isInChordMode()) {
+ m_nParentView->slotSetInsertCursorPosition
+ (lastInsertedEvent->getAbsoluteTime(), true, false);
+ } else {
+ m_nParentView->slotSetInsertCursorPosition
+ (lastInsertedEvent->getAbsoluteTime() +
+ lastInsertedEvent->getDuration(), true, false);
+ }
+ }
+
+ if (!suppressPreview)
+ m_nParentView->playNote(segment, pitch);
+}
+
+bool
+NoteInserter::computeLocationAndPreview(QMouseEvent *e)
+{
+ double x = e->x();
+ int y = (int)e->y();
+
+ LinedStaff *staff = m_nParentView->getStaffForCanvasCoords(e->x(), y);
+ if (!staff) {
+ clearPreview();
+ return false;
+ }
+
+ int staffNo = staff->getId();
+ if (m_clickHappened && staffNo != m_clickStaffNo) {
+ // abandon
+ clearPreview();
+ return false;
+ }
+
+ // If we're inserting grace notes, then we need to "dress to the
+ // right", as it were
+ bool grace = m_nParentView->isInGraceMode();
+
+ int height = staff->getHeightAtCanvasCoords(x, y);
+
+ Event *clefEvt = 0, *keyEvt = 0;
+ Clef clef;
+ Rosegarden::Key key;
+
+ NotationElementList::iterator itr =
+ staff->getElementUnderCanvasCoords(x, y, clefEvt, keyEvt);
+ if (itr == staff->getViewElementList()->end()) {
+ clearPreview();
+ return false;
+ }
+
+ NotationElement* el = static_cast<NotationElement*>(*itr);
+
+ timeT time = el->event()->getAbsoluteTime(); // not getViewAbsoluteTime()
+ m_clickInsertX = el->getLayoutX();
+ if (clefEvt)
+ clef = Clef(*clefEvt);
+ if (keyEvt)
+ key = Rosegarden::Key(*keyEvt);
+
+ int subordering = el->event()->getSubOrdering();
+ float targetSubordering = subordering;
+
+ if (grace && el->getCanvasItem()) {
+
+ NotationStaff *ns = dynamic_cast<NotationStaff *>(staff);
+ if (!ns) {
+ std::cerr << "WARNING: NoteInserter: Staff is not a NotationStaff"
+ << std::endl;
+ } else {
+ std::cerr << "x=" << x << ", el->getCanvasX()=" << el->getCanvasX() << std::endl;
+ if (el->isRest()) std::cerr << "elt is a rest" << std::endl;
+ if (x - el->getCanvasX() >
+ ns->getNotePixmapFactory(false).getNoteBodyWidth()) {
+ NotationElementList::iterator j(itr);
+ while (++j != staff->getViewElementList()->end()) {
+ NotationElement *candidate = static_cast<NotationElement *>(*j);
+ if ((candidate->isNote() || candidate->isRest()) &&
+ (candidate->getViewAbsoluteTime()
+ > el->getViewAbsoluteTime() ||
+ candidate->event()->getSubOrdering()
+ > el->event()->getSubOrdering())) {
+ itr = j;
+ el = candidate;
+ m_clickInsertX = el->getLayoutX();
+ time = el->event()->getAbsoluteTime();
+ subordering = el->event()->getSubOrdering();
+ targetSubordering = subordering;
+ break;
+ }
+ }
+ }
+ }
+
+ if (x - el->getCanvasX() < 1) {
+ targetSubordering -= 0.5;
+ }
+ }
+
+ if (el->isRest() && el->getCanvasItem()) {
+ time += getOffsetWithinRest(staffNo, itr, x);
+ m_clickInsertX += (x - el->getCanvasX());
+ }
+
+ Pitch p(height, clef, key, m_accidental);
+ int pitch = p.getPerformancePitch();
+
+ // [RFE 987960] When inserting via mouse, if no accidental is
+ // selected, we use the same accidental (and thus the same pitch)
+ // as of the previous note found at this height -- iff such a note
+ // is found more recently than the last key signature.
+
+ if (m_accidental == Accidentals::NoAccidental &&
+ m_followAccidental) {
+ Segment &segment = staff->getSegment();
+ m_lastAccidental = m_accidental;
+ Segment::iterator i = segment.findNearestTime(time);
+ while (i != segment.end()) {
+ if ((*i)->isa(Rosegarden::Key::EventType)) break;
+ if ((*i)->isa(Note::EventType)) {
+ if ((*i)->has(NotationProperties::HEIGHT_ON_STAFF) &&
+ (*i)->has(BaseProperties::PITCH)) {
+ int h = (*i)->get<Int>(NotationProperties::HEIGHT_ON_STAFF);
+ if (h == height) {
+ pitch = (*i)->get<Int>(BaseProperties::PITCH);
+ (*i)->get<String>(BaseProperties::ACCIDENTAL,
+ m_lastAccidental);
+ break;
+ }
+ }
+ }
+ if (i == segment.begin()) break;
+ --i;
+ }
+ }
+
+ bool changed = false;
+
+ if (m_clickHappened) {
+ if (time != m_clickTime ||
+ subordering != m_clickSubordering ||
+ pitch != m_clickPitch ||
+ height != m_clickHeight ||
+ staffNo != m_clickStaffNo) {
+ changed = true;
+ }
+ } else {
+ m_clickHappened = true;
+ changed = true;
+ }
+
+ if (changed) {
+ m_clickTime = time;
+ m_clickSubordering = subordering;
+ m_clickPitch = pitch;
+ m_clickHeight = height;
+ m_clickStaffNo = staffNo;
+ m_targetSubordering = targetSubordering;
+
+ showPreview();
+ }
+
+ return true;
+}
+
+void NoteInserter::showPreview()
+{
+ Segment &segment = m_nParentView->getStaff(m_clickStaffNo)->getSegment();
+
+ int pitch = m_clickPitch;
+ pitch += getOttavaShift(segment, m_clickTime) * 12;
+
+ m_nParentView->showPreviewNote(m_clickStaffNo, m_clickInsertX,
+ pitch, m_clickHeight,
+ Note(m_noteType, m_noteDots),
+ m_nParentView->isInGraceMode());
+}
+
+void NoteInserter::clearPreview()
+{
+ m_nParentView->clearPreviewNote();
+}
+
+timeT
+NoteInserter::getOffsetWithinRest(int staffNo,
+ const NotationElementList::iterator &i,
+ double &canvasX) // will be snapped
+{
+ //!!! To make this work correctly in tuplet mode, our divisor would
+ // have to be the tupletified duration of the tuplet unit -- we can
+ // do that, we just haven't yet
+ if (m_nParentView->isInTripletMode())
+ return 0;
+
+ Staff *staff = m_nParentView->getStaff(staffNo);
+ NotationElement* el = static_cast<NotationElement*>(*i);
+ if (!el->getCanvasItem())
+ return 0;
+ double offset = canvasX - el->getCanvasX();
+
+ if (offset < 0)
+ return 0;
+
+ double airX, airWidth;
+ el->getLayoutAirspace(airX, airWidth);
+ double origin = ((*i)->getLayoutX() - airX) / 2;
+ double width = airWidth - origin;
+
+ timeT duration = (*i)->getViewDuration();
+
+ TimeSignature timeSig =
+ staff->getSegment().getComposition()->getTimeSignatureAt
+ ((*i)->event()->getAbsoluteTime());
+ timeT unit = timeSig.getUnitDuration();
+
+ int unitCount = duration / unit;
+ if (unitCount > 1) {
+
+ timeT result = (int)((offset / width) * unitCount);
+ if (result > unitCount - 1)
+ result = unitCount - 1;
+
+ double visibleWidth(airWidth);
+ NotationElementList::iterator j(i);
+ if (++j != staff->getViewElementList()->end()) {
+ visibleWidth = (*j)->getLayoutX() - (*i)->getLayoutX();
+ }
+ offset = (visibleWidth * result) / unitCount;
+ canvasX = el->getCanvasX() + offset;
+
+ result *= unit;
+ return result;
+ }
+
+ return 0;
+}
+
+int
+NoteInserter::getOttavaShift(Segment &segment, timeT time)
+{
+ // Find out whether we're in an ottava section.
+
+ int ottavaShift = 0;
+
+ for (Segment::iterator i = segment.findTime(time); ; --i) {
+
+ if (!segment.isBeforeEndMarker(i)) {
+ break;
+ }
+
+ if ((*i)->isa(Indication::EventType)) {
+ try {
+ Indication ind(**i);
+ if (ind.isOttavaType()) {
+ timeT endTime =
+ (*i)->getNotationAbsoluteTime() +
+ (*i)->getNotationDuration();
+ if (time < endTime) {
+ ottavaShift = ind.getOttavaShift();
+ }
+ break;
+ }
+ } catch (...) { }
+ }
+
+ if (i == segment.begin()) {
+ break;
+ }
+ }
+
+ return ottavaShift;
+}
+
+Event *
+NoteInserter::doAddCommand(Segment &segment, timeT time, timeT endTime,
+ const Note &note, 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
+ //## <slowpic@web.de> 28/02/2005 22:32:56 UTC: Triplet input error
+ //# HJJ: Comment out this attempt. It breaks the splitting of
+ //# the first bars into rests.
+ //## if ((*i)->isa(Note::EventRestType) &&
+ //## (*i)->getNotationDuration() > (note.getDuration() * 3)) {
+ // split the rest
+ command->addCommand(new RestInsertionCommand
+ (segment, time,
+ time + note.getDuration() * 2,
+ Note::getNearestNote(note.getDuration() * 2)));
+ //## }
+ //# These comments should probably be deleted.
+
+ command->addCommand(new TupletCommand
+ (segment, time, note.getDuration(),
+ 3, 2, true)); // #1046934: "has timing already"
+ command->addCommand(insertionCommand);
+ activeCommand = command;
+ }
+ }
+
+ m_nParentView->addCommandToHistory(activeCommand);
+
+ NOTATION_DEBUG << "NoteInserter::doAddCommand: accidental is "
+ << accidental << endl;
+
+ return insertionCommand->getLastInsertedEvent();
+}
+
+void NoteInserter::slotSetNote(Note::Type nt)
+{
+ m_noteType = nt;
+}
+
+void NoteInserter::slotSetDots(unsigned int dots)
+{
+ m_noteDots = dots;
+
+ KToggleAction *dotsAction = dynamic_cast<KToggleAction *>
+ (actionCollection()->action("toggle_dot"));
+ if (dotsAction)
+ dotsAction->setChecked(dots > 0);
+}
+
+void NoteInserter::slotSetAccidental(Accidental accidental,
+ bool follow)
+{
+ NOTATION_DEBUG << "NoteInserter::setAccidental: accidental is "
+ << accidental << endl;
+ m_accidental = accidental;
+ m_followAccidental = follow;
+}
+
+void NoteInserter::slotNoAccidental()
+{
+ m_parentView->actionCollection()->action("no_accidental")->activate();
+}
+
+void NoteInserter::slotFollowAccidental()
+{
+ m_parentView->actionCollection()->action("follow_accidental")->activate();
+}
+
+void NoteInserter::slotSharp()
+{
+ m_parentView->actionCollection()->action("sharp_accidental")->activate();
+}
+
+void NoteInserter::slotFlat()
+{
+ m_parentView->actionCollection()->action("flat_accidental")->activate();
+}
+
+void NoteInserter::slotNatural()
+{
+ m_parentView->actionCollection()->action("natural_accidental")->activate();
+}
+
+void NoteInserter::slotDoubleSharp()
+{
+ m_parentView->actionCollection()->action("double_sharp_accidental")->activate();
+}
+
+void NoteInserter::slotDoubleFlat()
+{
+ m_parentView->actionCollection()->action("double_flat_accidental")->activate();
+}
+
+void NoteInserter::slotToggleDot()
+{
+ m_noteDots = (m_noteDots) ? 0 : 1;
+ Note note(m_noteType, m_noteDots);
+ QString actionName(NotationStrings::getReferenceName(note));
+ actionName.replace(QRegExp("-"), "_");
+ KAction *action = m_parentView->actionCollection()->action(actionName);
+ if (!action) {
+ std::cerr << "WARNING: No such action as " << actionName << std::endl;
+ } else {
+ action->activate();
+ }
+}
+
+void NoteInserter::slotToggleAutoBeam()
+{
+ m_autoBeam = !m_autoBeam;
+}
+
+void NoteInserter::slotEraseSelected()
+{
+ m_parentView->actionCollection()->action("erase")->activate();
+}
+
+void NoteInserter::slotSelectSelected()
+{
+ m_parentView->actionCollection()->action("select")->activate();
+}
+
+void NoteInserter::slotRestsSelected()
+{
+ Note note(m_noteType, m_noteDots);
+ QString actionName(NotationStrings::getReferenceName(note, true));
+ actionName.replace(QRegExp("-"), "_");
+ KAction *action = m_parentView->actionCollection()->action(actionName);
+ if (!action) {
+ std::cerr << "WARNING: No such action as " << actionName << std::endl;
+ } else {
+ action->activate();
+ }
+}
+
+const char* NoteInserter::m_actionsAccidental[][4] =
+{
+ { "No accidental", "1slotNoAccidental()", "no_accidental",
+ "accidental-none" },
+ { "Follow accidental", "1slotFollowAccidental()", "follow_accidental",
+ "accidental-follow" },
+ { "Sharp", "1slotSharp()", "sharp_accidental",
+ "accidental-sharp" },
+ { "Flat", "1slotFlat()", "flat_accidental",
+ "accidental-flat" },
+ { "Natural", "1slotNatural()", "natural_accidental",
+ "accidental-natural" },
+ { "Double sharp", "1slotDoubleSharp()", "double_sharp_accidental",
+ "accidental-doublesharp" },
+ { "Double flat", "1slotDoubleFlat()", "double_flat_accidental",
+ "accidental-doubleflat" }
+};
+
+const QString NoteInserter::ToolName = "noteinserter";
+
+}
+#include "NoteInserter.moc"
diff --git a/src/gui/editors/notation/NoteInserter.h b/src/gui/editors/notation/NoteInserter.h
new file mode 100644
index 0000000..cb46b38
--- /dev/null
+++ b/src/gui/editors/notation/NoteInserter.h
@@ -0,0 +1,166 @@
+
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
+
+/*
+ Rosegarden
+ A MIDI and audio sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_NOTEINSERTER_H_
+#define _RG_NOTEINSERTER_H_
+
+#include "base/NotationTypes.h"
+#include "NotationTool.h"
+#include "NotationElement.h"
+#include "NoteStyle.h"
+#include <qstring.h>
+#include "base/Event.h"
+
+
+class QMouseEvent;
+
+
+namespace Rosegarden
+{
+
+class ViewElement;
+class Segment;
+class NotationView;
+class Event;
+
+
+/**
+ * This tool will insert notes on mouse click events
+ */
+class NoteInserter : public NotationTool
+{
+ Q_OBJECT
+
+ friend class NotationToolBox;
+
+public:
+ ~NoteInserter();
+
+ virtual void handleLeftButtonPress(timeT,
+ int height,
+ int staffNo,
+ QMouseEvent*,
+ ViewElement* el);
+
+ virtual int handleMouseMove(timeT time,
+ int height,
+ QMouseEvent*);
+
+ virtual void handleMouseRelease(timeT time,
+ int height,
+ QMouseEvent*);
+
+ virtual void ready();
+
+ Note getCurrentNote() {
+ return Note(m_noteType, m_noteDots);
+ }
+
+ /// Insert a note as if the user has clicked at the given time & pitch
+ void insertNote(Segment &segment,
+ timeT insertionTime,
+ int pitch,
+ Accidental accidental,
+ bool suppressPreview = false);
+
+ static const QString ToolName;
+
+public slots:
+ /// Set the type of note (quaver, breve...) which will be inserted
+ void slotSetNote(Note::Type);
+
+ /// Set the nb of dots the inserted note will have
+ void slotSetDots(unsigned int dots);
+
+ /// Set the accidental for the notes which will be inserted
+ void slotSetAccidental(Accidental, bool follow);
+
+protected:
+ NoteInserter(NotationView*);
+
+ /// this ctor is used by RestInserter
+ NoteInserter(const QString& menuName, NotationView*);
+
+ timeT getOffsetWithinRest(int staffNo,
+ const NotationElementList::iterator&,
+ double &canvasX);
+
+ int getOttavaShift(Segment &segment, timeT time);
+
+ virtual Event *doAddCommand(Segment &,
+ timeT time,
+ timeT endTime,
+ const Note &,
+ int pitch, Accidental);
+
+ virtual bool computeLocationAndPreview(QMouseEvent *e);
+ virtual void showPreview();
+ virtual void clearPreview();
+
+protected slots:
+ // RMB menu slots
+ void slotNoAccidental();
+ void slotFollowAccidental();
+ void slotSharp();
+ void slotFlat();
+ void slotNatural();
+ void slotDoubleSharp();
+ void slotDoubleFlat();
+ void slotToggleDot();
+ void slotToggleAutoBeam();
+
+ void slotEraseSelected();
+ void slotSelectSelected();
+ void slotRestsSelected();
+
+protected:
+ //--------------- Data members ---------------------------------
+
+ Note::Type m_noteType;
+ unsigned int m_noteDots;
+ bool m_autoBeam;
+ bool m_matrixInsertType;
+ NoteStyleName m_defaultStyle;
+
+ bool m_clickHappened;
+ timeT m_clickTime;
+ int m_clickSubordering;
+ int m_clickPitch;
+ int m_clickHeight;
+ int m_clickStaffNo;
+ double m_clickInsertX;
+ float m_targetSubordering;
+
+ Accidental m_accidental;
+ Accidental m_lastAccidental;
+ bool m_followAccidental;
+
+ static const char* m_actionsAccidental[][4];
+};
+
+
+}
+
+#endif
diff --git a/src/gui/editors/notation/NotePixmapFactory.cpp b/src/gui/editors/notation/NotePixmapFactory.cpp
new file mode 100644
index 0000000..c2a99ee
--- /dev/null
+++ b/src/gui/editors/notation/NotePixmapFactory.cpp
@@ -0,0 +1,3689 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
+
+/*
+ Rosegarden
+ A MIDI and audio sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#include <cmath>
+#include "NotePixmapFactory.h"
+#include "misc/Debug.h"
+#include "base/NotationRules.h"
+#include <kapplication.h>
+
+#include <klocale.h>
+#include <kstddirs.h>
+#include <kconfig.h>
+#include "misc/Strings.h"
+#include "document/ConfigGroups.h"
+#include "base/Exception.h"
+#include "base/NotationTypes.h"
+#include "base/Profiler.h"
+#include "gui/editors/guitar/Fingering.h"
+#include "gui/editors/guitar/FingeringBox.h"
+#include "gui/editors/guitar/NoteSymbols.h"
+#include "gui/editors/notation/TrackHeader.h"
+#include "gui/general/GUIPalette.h"
+#include "gui/general/PixmapFunctions.h"
+#include "gui/general/Spline.h"
+#include "gui/kdeext/KStartupLogo.h"
+#include "NotationStrings.h"
+#include "NotationView.h"
+#include "NoteCharacter.h"
+#include "NoteCharacterNames.h"
+#include "NoteFontFactory.h"
+#include "NoteFont.h"
+#include "NotePixmapParameters.h"
+#include "NotePixmapPainter.h"
+#include "NoteStyleFactory.h"
+#include "NoteStyle.h"
+#include <kglobal.h>
+#include <kmessagebox.h>
+#include <qbitmap.h>
+#include <qcolor.h>
+#include <qfile.h>
+#include <qfont.h>
+#include <qfontmetrics.h>
+#include <qimage.h>
+#include <qpainter.h>
+#include <qpen.h>
+#include <qpixmap.h>
+#include <qpointarray.h>
+#include <qpoint.h>
+#include <qrect.h>
+#include <qstring.h>
+#include <qwmatrix.h>
+
+
+namespace Rosegarden
+{
+
+using namespace Accidentals;
+
+static clock_t drawBeamsTime = 0;
+static clock_t makeNotesTime = 0;
+static int drawBeamsCount = 0;
+static int drawBeamsBeamCount = 0;
+
+class NotePixmapCache : public std::map<CharName, QCanvasPixmap*>
+{
+ // nothing to add -- just so we can predeclare it in the header
+};
+
+const char* const NotePixmapFactory::defaultSerifFontFamily = "Bitstream Vera Serif";
+const char* const NotePixmapFactory::defaultSansSerifFontFamily = "Bitstream Vera Sans";
+const char* const NotePixmapFactory::defaultTimeSigFontFamily = "Bitstream Vera Serif";
+
+NotePixmapFactory::NotePixmapFactory(std::string fontName, int size) :
+ m_selected(false),
+ m_shaded(false),
+ m_tupletCountFont(defaultSerifFontFamily, 8, QFont::Bold),
+ m_tupletCountFontMetrics(m_tupletCountFont),
+ m_textMarkFont(defaultSerifFontFamily, 8, QFont::Bold, true),
+ m_textMarkFontMetrics(m_textMarkFont),
+ m_fingeringFont(defaultSerifFontFamily, 8, QFont::Bold),
+ m_fingeringFontMetrics(m_fingeringFont),
+ m_timeSigFont(defaultTimeSigFontFamily, 8, QFont::Bold),
+ m_timeSigFontMetrics(m_timeSigFont),
+ m_bigTimeSigFont(defaultTimeSigFontFamily, 12, QFont::Normal),
+ m_bigTimeSigFontMetrics(m_bigTimeSigFont),
+ m_ottavaFont(defaultSerifFontFamily, 8, QFont::Normal, true),
+ m_ottavaFontMetrics(m_ottavaFont),
+ m_clefOttavaFont(defaultSerifFontFamily, 8, QFont::Normal),
+ m_clefOttavaFontMetrics(m_ottavaFont),
+ m_trackHeaderFont(defaultSansSerifFontFamily, 10, QFont::Normal),
+ m_trackHeaderFontMetrics(m_trackHeaderFont),
+ m_trackHeaderBoldFont(defaultSansSerifFontFamily, 10, QFont::Bold),
+ m_trackHeaderBoldFontMetrics(m_trackHeaderBoldFont),
+ m_generatedPixmap(0),
+ m_generatedMask(0),
+ m_generatedWidth( -1),
+ m_generatedHeight( -1),
+ m_inPrinterMethod(false),
+ m_p(new NotePixmapPainter()),
+ m_dottedRestCache(new NotePixmapCache)
+{
+ init(fontName, size);
+}
+
+NotePixmapFactory::NotePixmapFactory(const NotePixmapFactory &npf) :
+ m_selected(false),
+ m_shaded(false),
+ m_tupletCountFont(npf.m_tupletCountFont),
+ m_tupletCountFontMetrics(m_tupletCountFont),
+ m_textMarkFont(npf.m_textMarkFont),
+ m_textMarkFontMetrics(m_textMarkFont),
+ m_fingeringFont(npf.m_fingeringFont),
+ m_fingeringFontMetrics(m_fingeringFont),
+ m_timeSigFont(npf.m_timeSigFont),
+ m_timeSigFontMetrics(m_timeSigFont),
+ m_bigTimeSigFont(npf.m_bigTimeSigFont),
+ m_bigTimeSigFontMetrics(m_bigTimeSigFont),
+ m_ottavaFont(defaultSerifFontFamily, 8, QFont::Normal, true),
+ m_ottavaFontMetrics(m_ottavaFont),
+ m_clefOttavaFont(defaultSerifFontFamily, 8, QFont::Normal),
+ m_clefOttavaFontMetrics(m_ottavaFont),
+ m_trackHeaderFont(defaultSansSerifFontFamily, 10, QFont::Normal),
+ m_trackHeaderFontMetrics(m_trackHeaderFont),
+ m_trackHeaderBoldFont(defaultSansSerifFontFamily, 10, QFont::Bold),
+ m_trackHeaderBoldFontMetrics(m_trackHeaderBoldFont),
+ m_generatedPixmap(0),
+ m_generatedMask(0),
+ m_generatedWidth( -1),
+ m_generatedHeight( -1),
+ m_inPrinterMethod(false),
+ m_p(new NotePixmapPainter()),
+ m_dottedRestCache(new NotePixmapCache)
+{
+ init(npf.m_font->getName(), npf.m_font->getSize());
+}
+
+NotePixmapFactory &
+NotePixmapFactory::operator=(const NotePixmapFactory &npf)
+{
+ if (&npf != this) {
+ m_selected = npf.m_selected;
+ m_shaded = npf.m_shaded;
+ m_timeSigFont = npf.m_timeSigFont;
+ m_timeSigFontMetrics = QFontMetrics(m_timeSigFont);
+ m_bigTimeSigFont = npf.m_bigTimeSigFont;
+ m_bigTimeSigFontMetrics = QFontMetrics(m_bigTimeSigFont);
+ m_tupletCountFont = npf.m_tupletCountFont;
+ m_tupletCountFontMetrics = QFontMetrics(m_tupletCountFont);
+ m_textMarkFont = npf.m_textMarkFont;
+ m_textMarkFontMetrics = QFontMetrics(m_textMarkFont);
+ m_fingeringFont = npf.m_fingeringFont;
+ m_fingeringFontMetrics = QFontMetrics(m_fingeringFont);
+ m_ottavaFont = npf.m_ottavaFont;
+ m_ottavaFontMetrics = QFontMetrics(m_ottavaFont);
+ m_clefOttavaFont = npf.m_clefOttavaFont;
+ m_clefOttavaFontMetrics = QFontMetrics(m_clefOttavaFont);
+ m_trackHeaderFont = npf.m_trackHeaderFont;
+ m_trackHeaderFontMetrics = QFontMetrics(m_trackHeaderFont);
+ m_trackHeaderBoldFont = npf.m_trackHeaderBoldFont;
+ m_trackHeaderBoldFontMetrics = QFontMetrics(m_trackHeaderBoldFont);
+ init(npf.m_font->getName(), npf.m_font->getSize());
+ m_dottedRestCache->clear();
+ m_textFontCache.clear();
+ }
+ return *this;
+}
+
+void
+NotePixmapFactory::init(std::string fontName, int size)
+{
+ try {
+ m_style = NoteStyleFactory::getStyle(NoteStyleFactory::DefaultStyle);
+ } catch (NoteStyleFactory::StyleUnavailable u) {
+ KStartupLogo::hideIfStillThere();
+ KMessageBox::error(0, i18n(strtoqstr(u.getMessage())));
+ throw;
+ }
+
+ int origSize = size;
+
+ if (fontName != "") {
+ try {
+ if (size < 0)
+ size = NoteFontFactory::getDefaultSize(fontName);
+ m_font = NoteFontFactory::getFont(fontName, size);
+ } catch (Exception f) {
+ fontName = "";
+ // fall through
+ }
+ }
+
+ if (fontName == "") { // either because it was passed in or because read failed
+ try {
+ fontName = NoteFontFactory::getDefaultFontName();
+ size = origSize;
+ if (size < 0)
+ size = NoteFontFactory::getDefaultSize(fontName);
+ m_font = NoteFontFactory::getFont(fontName, size);
+ } catch (Exception f) { // already reported
+ throw;
+ }
+ }
+
+ // Resize the fonts, because the original constructor used point
+ // sizes only and we want pixels
+ QFont timeSigFont(defaultTimeSigFontFamily),
+ textFont(defaultSerifFontFamily);
+ KConfig* config = kapp->config();
+ config->setGroup(NotationViewConfigGroup);
+
+ m_timeSigFont = config->readFontEntry("timesigfont", &timeSigFont);
+ m_timeSigFont.setBold(true);
+ m_timeSigFont.setPixelSize(size * 5 / 2);
+ m_timeSigFontMetrics = QFontMetrics(m_timeSigFont);
+
+ m_bigTimeSigFont = config->readFontEntry("timesigfont", &timeSigFont);
+ m_bigTimeSigFont.setPixelSize(size * 4 + 2);
+ m_bigTimeSigFontMetrics = QFontMetrics(m_bigTimeSigFont);
+
+ m_tupletCountFont = config->readFontEntry("textfont", &textFont);
+ m_tupletCountFont.setBold(true);
+ m_tupletCountFont.setPixelSize(size * 2);
+ m_tupletCountFontMetrics = QFontMetrics(m_tupletCountFont);
+
+ m_textMarkFont = config->readFontEntry("textfont", &textFont);
+ m_textMarkFont.setBold(true);
+ m_textMarkFont.setItalic(true);
+ m_textMarkFont.setPixelSize(size * 2);
+ m_textMarkFontMetrics = QFontMetrics(m_textMarkFont);
+
+ m_fingeringFont = config->readFontEntry("textfont", &textFont);
+ m_fingeringFont.setBold(true);
+ m_fingeringFont.setPixelSize(size * 5 / 3);
+ m_fingeringFontMetrics = QFontMetrics(m_fingeringFont);
+
+ m_ottavaFont = config->readFontEntry("textfont", &textFont);
+ m_ottavaFont.setPixelSize(size * 2);
+ m_ottavaFontMetrics = QFontMetrics(m_ottavaFont);
+
+ m_clefOttavaFont = config->readFontEntry("textfont", &textFont);
+ m_clefOttavaFont.setPixelSize(getLineSpacing() * 3 / 2);
+ m_clefOttavaFontMetrics = QFontMetrics(m_clefOttavaFont);
+
+ m_trackHeaderFont = config->readFontEntry("sansfont", &m_trackHeaderFont);
+ m_trackHeaderFont.setPixelSize(12);
+ m_trackHeaderFontMetrics = QFontMetrics(m_trackHeaderFont);
+
+ m_trackHeaderBoldFont = m_trackHeaderFont;
+ m_trackHeaderBoldFont.setBold(true);
+ m_trackHeaderBoldFontMetrics = QFontMetrics(m_trackHeaderBoldFont);
+}
+
+NotePixmapFactory::~NotePixmapFactory()
+{
+ delete m_p;
+ delete m_dottedRestCache;
+}
+
+std::string
+NotePixmapFactory::getFontName() const
+{
+ return m_font->getName();
+}
+
+int
+NotePixmapFactory::getSize() const
+{
+ return m_font->getSize();
+}
+
+QPixmap
+NotePixmapFactory::toQPixmap(QCanvasPixmap* cp)
+{
+ QPixmap p = *cp;
+ delete cp;
+ return p;
+}
+
+void
+NotePixmapFactory::dumpStats(std::ostream &s)
+{
+#ifdef DUMP_STATS
+ s << "NotePixmapFactory: total times since last stats dump:\n"
+ << "makeNotePixmap: "
+ << (makeNotesTime * 1000 / CLOCKS_PER_SEC) << "ms\n"
+ << "drawBeams: "
+ << (drawBeamsTime * 1000 / CLOCKS_PER_SEC) << "ms"
+ << " (drew " << drawBeamsCount << " individual points in " << drawBeamsBeamCount << " beams)"
+ << endl;
+ makeNotesTime = 0;
+ drawBeamsTime = 0;
+ drawBeamsCount = 0;
+ drawBeamsBeamCount = 0;
+#endif
+
+ (void)s; // avoid warnings
+}
+
+QCanvasPixmap*
+NotePixmapFactory::makeNotePixmap(const NotePixmapParameters &params)
+{
+ 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 &params,
+ 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 &params,
+ 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 &params)
+{
+ 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 &params) 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 &params,
+ int stemLength)
+{
+ int height = 0, width = 0;
+ int gap = m_noteBodyHeight / 5 + 1;
+
+ std::vector<Mark> normalMarks = params.getNormalMarks();
+ std::vector<Mark> aboveMarks = params.getAboveMarks();
+
+ for (std::vector<Mark>::iterator i = normalMarks.begin();
+ i != normalMarks.end(); ++i) {
+
+ if (!Marks::isTextMark(*i)) {
+
+ NoteCharacter character(m_font->getCharacter(m_style->getMarkCharName(*i)));
+ height += character.getHeight() + gap;
+ if (character.getWidth() > width)
+ width = character.getWidth();
+
+ } else {
+ // Inefficient to do this here _and_ in drawMarks, but
+ // text marks are not all that common
+ QString text = strtoqstr(Marks::getTextFromMark(*i));
+ QRect bounds = m_textMarkFontMetrics.boundingRect(text);
+ height += bounds.height() + gap;
+ if (bounds.width() > width)
+ width = bounds.width();
+ }
+ }
+
+ if (height > 0) {
+ if (isStemmed && params.m_stemGoesUp) {
+ m_below += height + 1;
+ } else {
+ m_above += height + 1;
+ }
+ }
+
+ height = 0;
+
+ if (params.m_safeVertDistance > 0 && !aboveMarks.empty()) {
+ m_above = std::max(m_above, params.m_safeVertDistance);
+ }
+
+ for (std::vector<Mark>::iterator i = aboveMarks.begin();
+ i != aboveMarks.end(); ++i) {
+
+ if (!Marks::isFingeringMark(*i)) {
+
+ Mark m(*i);
+
+ if (m == Marks::TrillLine)
+ m = Marks::LongTrill;
+
+ if (m == Marks::LongTrill) {
+ m_right = std::max(m_right, params.m_width);
+ }
+
+ NoteCharacter character(m_font->getCharacter(m_style->getMarkCharName(m)));
+ height += character.getHeight() + gap;
+ if (character.getWidth() > width)
+ width = character.getWidth();
+
+ } else {
+
+ // Inefficient to do this here _and_ in drawMarks
+ QString text = strtoqstr(Marks::getFingeringFromMark(*i));
+ QRect bounds = m_fingeringFontMetrics.boundingRect(text);
+ height += bounds.height() + gap + 3;
+ if (bounds.width() > width)
+ width = bounds.width();
+ }
+ }
+
+ if (height > 0) {
+ if (isStemmed && params.m_stemGoesUp && params.m_safeVertDistance == 0) {
+ m_above += stemLength + height + 1;
+ } else {
+ m_above += height + 1;
+ }
+ }
+
+ m_left = std::max(m_left, width / 2 - m_noteBodyWidth / 2);
+ m_right = std::max(m_right, width / 2 - m_noteBodyWidth / 2);
+}
+
+void
+NotePixmapFactory::drawMarks(bool isStemmed,
+ const NotePixmapParameters &params,
+ int stemLength)
+{
+ int gap = m_noteBodyHeight / 5 + 1;
+ int dy = gap;
+
+ std::vector<Mark> normalMarks = params.getNormalMarks();
+ std::vector<Mark> aboveMarks = params.getAboveMarks();
+
+ bool normalMarksAreAbove = !(isStemmed && params.m_stemGoesUp);
+
+ for (std::vector<Mark>::iterator i = normalMarks.begin();
+ i != normalMarks.end(); ++i) {
+
+ if (!Marks::isTextMark(*i)) {
+
+ NoteCharacter character = getCharacter
+ (m_style->getMarkCharName(*i), PlainColour,
+ !normalMarksAreAbove);
+
+ int x = m_left + m_noteBodyWidth / 2 - character.getWidth() / 2;
+ int y = (normalMarksAreAbove ?
+ (m_above - dy - character.getHeight() - 1) :
+ (m_above + m_noteBodyHeight + m_borderY * 2 + dy));
+
+ m_p->drawNoteCharacter(x, y, character);
+ dy += character.getHeight() + gap;
+
+ } else {
+
+ QString text = strtoqstr(Marks::getTextFromMark(*i));
+ QRect bounds = m_textMarkFontMetrics.boundingRect(text);
+
+ m_p->painter().setFont(m_textMarkFont);
+ if (!m_inPrinterMethod)
+ m_p->maskPainter().setFont(m_textMarkFont);
+
+ int x = m_left + m_noteBodyWidth / 2 - bounds.width() / 2;
+ int y = (normalMarksAreAbove ?
+ (m_above - dy - 3) :
+ (m_above + m_noteBodyHeight + m_borderY * 2 + dy + bounds.height() + 1));
+
+ m_p->drawText(x, y, text);
+ dy += bounds.height() + gap;
+ }
+ }
+
+ if (!normalMarksAreAbove)
+ dy = gap;
+ if (params.m_safeVertDistance > 0) {
+ if (normalMarksAreAbove) {
+ dy = std::max(dy, params.m_safeVertDistance);
+ } else {
+ dy = params.m_safeVertDistance;
+ }
+ } else if (isStemmed && params.m_stemGoesUp) {
+ dy += stemLength;
+ }
+
+ for (std::vector<Mark>::iterator i = aboveMarks.begin();
+ i != aboveMarks.end(); ++i) {
+
+ if (m_selected)
+ m_p->painter().setPen(GUIPalette::getColour(GUIPalette::SelectedElement));
+ else
+ m_p->painter().setPen(Qt::black);
+ if (!Marks::isFingeringMark(*i)) {
+
+ int x = m_left + m_noteBodyWidth / 2;
+ int y = m_above - dy - 1;
+
+ if (*i != Marks::TrillLine) {
+
+ NoteCharacter character
+ (getCharacter
+ (m_style->getMarkCharName(*i), PlainColour,
+ false));
+
+ x -= character.getWidth() / 2;
+ y -= character.getHeight();
+
+ m_p->drawNoteCharacter(x, y, character);
+
+ y += character.getHeight() / 2;
+ x += character.getWidth();
+
+ dy += character.getHeight() + gap;
+
+ } else {
+
+ NoteCharacter character
+ (getCharacter
+ (m_style->getMarkCharName(Marks::Trill), PlainColour,
+ false));
+ y -= character.getHeight() / 2;
+ dy += character.getHeight() + gap;
+ }
+
+ if (*i == Marks::LongTrill ||
+ *i == Marks::TrillLine) {
+ NoteCharacter extension;
+ if (getCharacter(NoteCharacterNames::TRILL_LINE, extension,
+ PlainColour, false)) {
+ x += extension.getHotspot().x();
+ while (x < m_left + params.m_width - extension.getWidth()) {
+ x -= extension.getHotspot().x();
+ m_p->drawNoteCharacter(x, y, extension);
+ x += extension.getWidth();
+ }
+ }
+ if (*i == Marks::TrillLine)
+ dy += extension.getHeight() + gap;
+ }
+
+ } else {
+ QString text = strtoqstr(Marks::getFingeringFromMark(*i));
+ QRect bounds = m_fingeringFontMetrics.boundingRect(text);
+
+ m_p->painter().setFont(m_fingeringFont);
+ if (!m_inPrinterMethod)
+ m_p->maskPainter().setFont(m_fingeringFont);
+
+ int x = m_left + m_noteBodyWidth / 2 - bounds.width() / 2;
+ int y = m_above - dy - 3;
+
+ m_p->drawText(x, y, text);
+ dy += bounds.height() + gap;
+ }
+ }
+}
+
+void
+NotePixmapFactory::makeRoomForLegerLines(const NotePixmapParameters &params)
+{
+ 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 &params)
+{
+ 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 &params,
+ 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 &params,
+ 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 &params,
+ 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 &params)
+{
+ int beamSpacing = (int)(params.m_width * params.m_gradient);
+
+ if (params.m_stemGoesUp) {
+
+ beamSpacing = -beamSpacing;
+ if (beamSpacing < 0)
+ beamSpacing = 0;
+ m_above += beamSpacing + 2;
+
+ // allow a bit extra in case the h fixpoint is non-normal
+ m_right = std::max(m_right, params.m_width + m_noteBodyWidth);
+
+ } else {
+
+ if (beamSpacing < 0)
+ beamSpacing = 0;
+ m_below += beamSpacing + 2;
+
+ m_right = std::max(m_right, params.m_width);
+ }
+}
+
+void
+NotePixmapFactory::drawShallowLine(int x0, int y0, int x1, int y1,
+ int thickness, bool smooth)
+{
+ if (!smooth || m_inPrinterMethod || (y0 == y1)) {
+
+ if (!m_inPrinterMethod) {
+ if (m_selected)
+ m_p->painter().setBrush(GUIPalette::getColour(GUIPalette::SelectedElement));
+ else
+ m_p->painter().setBrush(Qt::black);
+ }
+ if (thickness < 4) {
+ for (int i = 0; i < thickness; ++i) {
+ m_p->drawLine(x0, y0 + i, x1, y1 + i);
+ }
+ } else {
+ Profiler profiler("NotePixmapFactory::drawShallowLine(polygon)");
+ QPointArray qp(4);
+ qp.setPoint(0, x0, y0);
+ qp.setPoint(1, x0, y0 + thickness);
+ qp.setPoint(2, x1, y1 + thickness);
+ qp.setPoint(3, x1, y1);
+ m_p->drawPolygon(qp);
+ }
+
+ return ;
+ }
+
+ Profiler profiler("NotePixmapFactory::drawShallowLine(points)");
+
+ int dv = y1 - y0;
+ int dh = x1 - x0;
+
+ static std::vector<QColor> colours, selectedColours;
+ if (colours.size() == 0) {
+ int h, s, v;
+ QColor c = GUIPalette::getColour(GUIPalette::SelectedElement);
+ c.hsv(&h, &s, &v);
+ for (int step = 0; step < 256; step += (step == 0 ? 63 : 64)) {
+ colours.push_back(QColor( -1, 0, step, QColor::Hsv));
+ selectedColours.push_back(QColor(h, 255 - step, v, QColor::Hsv));
+ }
+ }
+
+ int cx = x0, cy = y0;
+
+ int inc = 1;
+
+ if (dv < 0) {
+ dv = -dv;
+ inc = -1;
+ }
+
+ int g = 2 * dv - dh;
+ int dg1 = 2 * (dv - dh);
+ int dg2 = 2 * dv;
+
+ int segment = (dg2 - dg1) / 4;
+
+ while (cx < x1) {
+
+ if (g > 0) {
+ g += dg1;
+ cy += inc;
+ } else {
+ g += dg2;
+ }
+
+ int quartile = segment ? ((dg2 - g) / segment) : 0;
+ if (quartile < 0)
+ quartile = 0;
+ if (quartile > 3)
+ quartile = 3;
+ if (inc > 0)
+ quartile = 4 - quartile;
+ /*
+ NOTATION_DEBUG
+ << "x = " << cx << ", y = " << cy
+ << ", g = " << g << ", dg1 = " << dg1 << ", dg2 = " << dg2
+ << ", seg = " << segment << ", q = " << quartile << endl;
+ */
+ // I don't know enough about Qt to be sure of this, but I
+ // suspect this may be some of the most inefficient code ever
+ // written:
+
+ int off = 0;
+
+ if (m_selected) {
+ m_p->painter().setPen(selectedColours[quartile]);
+ } else {
+ m_p->painter().setPen(colours[quartile]);
+ }
+
+ m_p->drawPoint(cx, cy);
+ drawBeamsCount ++;
+
+ if (thickness > 1) {
+ if (m_selected) {
+ m_p->painter().setPen(GUIPalette::getColour(GUIPalette::SelectedElement));
+ } else {
+ m_p->painter().setPen(Qt::black);
+ }
+ }
+
+ while (++off < thickness) {
+ m_p->drawPoint(cx, cy + off);
+ drawBeamsCount ++;
+ }
+
+ if (m_selected) {
+ m_p->painter().setPen(selectedColours[4 - quartile]);
+ } else {
+ m_p->painter().setPen(colours[4 - quartile]);
+ }
+
+ m_p->drawPoint(cx, cy + off);
+ drawBeamsCount ++;
+
+ ++cx;
+ }
+
+ m_p->painter().setPen(Qt::black);
+}
+
+void
+NotePixmapFactory::drawBeams(const QPoint &s1,
+ const NotePixmapParameters &params,
+ 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 &params,
+ 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 &params)
+{
+ 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 &params)
+{
+ 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 &params)
+{
+ Profiler profiler("NotePixmapFactory::makeRestPixmap");
+
+ CharName charName(m_style->getRestCharName(params.m_noteType,
+ params.m_restOutsideStave));
+ // Check whether the font has the glyph for this charName;
+ // if not, substitute a rest-on-stave glyph for a rest-outside-stave glyph,
+ // and vice-versa.
+ NoteCharacter character;
+ if (!getCharacter(charName, character, PlainColour, false))
+ charName = m_style->getRestCharName(params.m_noteType,
+ !params.m_restOutsideStave);
+
+ bool encache = false;
+
+ if (params.m_tupletCount == 0 && !m_selected && !m_shaded &&
+ !params.m_restOutsideStave) {
+
+ if (params.m_dots == 0) {
+ return getCharacter(charName, PlainColour, false).getCanvasPixmap();
+ } else {
+ NotePixmapCache::iterator ci(m_dottedRestCache->find(charName));
+ if (ci != m_dottedRestCache->end())
+ return new QCanvasPixmap
+ (*ci->second, QPoint(ci->second->offsetX(),
+ ci->second->offsetY()));
+ else
+ encache = true;
+ }
+ }
+
+ QPoint hotspot(m_font->getHotspot(charName));
+ drawRestAux(params, hotspot, 0, 0, 0);
+
+ QCanvasPixmap* canvasMap = makeCanvasPixmap(hotspot);
+ if (encache) {
+ m_dottedRestCache->insert(std::pair<CharName, QCanvasPixmap*>
+ (charName, new QCanvasPixmap
+ (*canvasMap, hotspot)));
+ }
+ return canvasMap;
+}
+
+void
+NotePixmapFactory::drawRest(const NotePixmapParameters &params,
+ 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 &params,
+ QPoint &hotspot, QPainter *painter, int x, int y)
+{
+ CharName charName(m_style->getRestCharName(params.m_noteType,
+ params.m_restOutsideStave));
+ NoteCharacter character = getCharacter(charName,
+ params.m_quantized ? QuantizedColour :
+ PlainColour,
+ false);
+
+ NoteCharacter dot = getCharacter(NoteCharacterNames::DOT, PlainColour, false);
+
+ int dotWidth = dot.getWidth();
+ if (dotWidth < getNoteBodyWidth() / 2)
+ dotWidth = getNoteBodyWidth() / 2;
+
+ m_above = m_left = 0;
+ m_below = dot.getHeight() / 2; // for dotted shallow rests like semibreve
+ m_right = dotWidth / 2 + dotWidth * params.m_dots;
+ m_noteBodyWidth = character.getWidth();
+ m_noteBodyHeight = character.getHeight();
+
+ if (params.m_tupletCount)
+ makeRoomForTuplingLine(params);
+
+ // we'll adjust this for tupling line after drawing rest character:
+ hotspot = m_font->getHotspot(charName);
+
+ if (params.m_restOutsideStave &&
+ (charName == NoteCharacterNames::MULTI_REST ||
+ charName == NoteCharacterNames::MULTI_REST_ON_STAFF)) {
+ makeRoomForLegerLines(params);
+ }
+ if (painter) {
+ painter->save();
+ m_p->beginExternal(painter);
+ painter->translate(x - m_left, y - m_above - hotspot.y());
+ } else {
+ createPixmapAndMask(m_noteBodyWidth + m_left + m_right,
+ m_noteBodyHeight + m_above + m_below);
+ }
+
+ m_p->drawNoteCharacter(m_left, m_above, character);
+
+ if (params.m_tupletCount)
+ drawTuplingLine(params);
+
+ hotspot.setX(m_left);
+ hotspot.setY(m_above + hotspot.y());
+
+ int restY = hotspot.y() - dot.getHeight() - getStaffLineThickness();
+ if (params.m_noteType == Note::Semibreve ||
+ params.m_noteType == Note::Breve) {
+ restY += getLineSpacing();
+ }
+
+ for (int i = 0; i < params.m_dots; ++i) {
+ int x = m_left + m_noteBodyWidth + i * dotWidth + dotWidth / 2;
+ m_p->drawNoteCharacter(x, restY, dot);
+ }
+
+ if (params.m_restOutsideStave &&
+ (charName == NoteCharacterNames::MULTI_REST ||
+ charName == NoteCharacterNames::MULTI_REST_ON_STAFF)) {
+ drawLegerLines(params);
+ }
+
+ if (painter) {
+ painter->restore();
+ }
+}
+
+QCanvasPixmap*
+NotePixmapFactory::makeClefPixmap(const Clef &clef)
+{
+ Profiler profiler("NotePixmapFactory::makeClefPixmap");
+ NoteCharacter plain = getCharacter(m_style->getClefCharName(clef),
+ PlainColour, false);
+
+ int oct = clef.getOctaveOffset();
+ if (oct == 0)
+ return plain.getCanvasPixmap();
+
+ // fix #1522784 and use 15 rather than 16 for double octave offset
+ int adjustedOctave = (8 * (oct < 0 ? -oct : oct));
+ if (adjustedOctave > 8)
+ adjustedOctave--;
+ else if (adjustedOctave < 8)
+ adjustedOctave++;
+
+ QString text = QString("%1").arg(adjustedOctave);
+ QRect rect = m_clefOttavaFontMetrics.boundingRect(text);
+
+ createPixmapAndMask(plain.getWidth(),
+ plain.getHeight() + rect.height());
+
+ if (m_selected) {
+ m_p->painter().setPen(GUIPalette::getColour(GUIPalette::SelectedElement));
+ }
+
+ m_p->drawNoteCharacter(0, oct < 0 ? 0 : rect.height(), plain);
+
+ m_p->painter().setFont(m_clefOttavaFont);
+ if (!m_inPrinterMethod)
+ m_p->maskPainter().setFont(m_clefOttavaFont);
+
+ m_p->drawText(plain.getWidth() / 2 - rect.width() / 2,
+ oct < 0 ? plain.getHeight() + rect.height() - 1 :
+ rect.height(), text);
+
+ m_p->painter().setPen(Qt::black);
+ QPoint hotspot(plain.getHotspot());
+ if (oct > 0) hotspot.setY(hotspot.y() + rect.height());
+ return makeCanvasPixmap(hotspot, true);
+}
+
+QCanvasPixmap*
+NotePixmapFactory::makePedalDownPixmap()
+{
+ return getCharacter(NoteCharacterNames::PEDAL_MARK, PlainColour, false)
+ .getCanvasPixmap();
+}
+
+QCanvasPixmap*
+NotePixmapFactory::makePedalUpPixmap()
+{
+ return getCharacter(NoteCharacterNames::PEDAL_UP_MARK, PlainColour, false)
+ .getCanvasPixmap();
+}
+
+QCanvasPixmap*
+NotePixmapFactory::makeUnknownPixmap()
+{
+ Profiler profiler("NotePixmapFactory::makeUnknownPixmap");
+ return getCharacter(NoteCharacterNames::UNKNOWN, PlainColour, false)
+ .getCanvasPixmap();
+}
+
+QCanvasPixmap*
+NotePixmapFactory::makeToolbarPixmap(const char *name, bool menuSize)
+{
+ QString pixmapDir = KGlobal::dirs()->findResource("appdata", "pixmaps/");
+ QString fileBase = pixmapDir + "/toolbar/";
+ if (menuSize) fileBase += "menu-";
+ fileBase += name;
+ if (QFile(fileBase + ".png").exists()) {
+ return new QCanvasPixmap(fileBase + ".png");
+ } else if (QFile(fileBase + ".xpm").exists()) {
+ return new QCanvasPixmap(fileBase + ".xpm");
+ } else if (menuSize) {
+ return makeToolbarPixmap(name, false);
+ } else {
+ // this will fail, but we don't want to return a null pointer
+ return new QCanvasPixmap(fileBase + ".png");
+ }
+}
+
+QCanvasPixmap*
+NotePixmapFactory::makeNoteMenuPixmap(timeT duration,
+ timeT &errorReturn)
+{
+ Note nearestNote = Note::getNearestNote(duration);
+ bool triplet = false;
+ errorReturn = 0;
+
+ if (nearestNote.getDuration() != duration) {
+ Note tripletNote = Note::getNearestNote(duration * 3 / 2);
+ if (tripletNote.getDuration() == duration * 3 / 2) {
+ nearestNote = tripletNote;
+ triplet = true;
+ } else {
+ errorReturn = duration - nearestNote.getDuration();
+ }
+ }
+
+ QString noteName = NotationStrings::getReferenceName(nearestNote);
+ if (triplet)
+ noteName = "3-" + noteName;
+ noteName = "menu-" + noteName;
+ return makeToolbarPixmap(noteName);
+}
+
+QCanvasPixmap *
+NotePixmapFactory::makeMarkMenuPixmap(Mark mark)
+{
+ if (mark == Marks::Sforzando ||
+ mark == Marks::Rinforzando) {
+ return makeToolbarPixmap(mark.c_str());
+ } else {
+ NoteFont *font = 0;
+ try {
+ font = NoteFontFactory::getFont
+ (NoteFontFactory::getDefaultFontName(), 6);
+ } catch (Exception) {
+ font = NoteFontFactory::getFont
+ (NoteFontFactory::getDefaultFontName(),
+ NoteFontFactory::getDefaultSize(NoteFontFactory::getDefaultFontName()));
+ }
+ NoteCharacter character = font->getCharacter
+ (NoteStyleFactory::getStyle(NoteStyleFactory::DefaultStyle)->
+ getMarkCharName(mark));
+ return character.getCanvasPixmap();
+ }
+}
+
+QCanvasPixmap*
+NotePixmapFactory::makeKeyPixmap(const Key &key,
+ const Clef &clef,
+ Key previousKey)
+{
+ Profiler profiler("NotePixmapFactory::makeKeyPixmap");
+
+ std::vector<int> ah0 = previousKey.getAccidentalHeights(clef);
+ std::vector<int> ah1 = key.getAccidentalHeights(clef);
+
+ int cancelCount = 0;
+ if (key.isSharp() != previousKey.isSharp())
+ cancelCount = ah0.size();
+ else if (ah1.size() < ah0.size())
+ cancelCount = ah0.size() - ah1.size();
+
+ CharName keyCharName;
+ if (key.isSharp())
+ keyCharName = NoteCharacterNames::SHARP;
+ else
+ keyCharName = NoteCharacterNames::FLAT;
+
+ NoteCharacter keyCharacter;
+ NoteCharacter cancelCharacter;
+
+ keyCharacter = getCharacter(keyCharName, PlainColour, false);
+ if (cancelCount > 0) {
+ cancelCharacter = getCharacter(NoteCharacterNames::NATURAL, PlainColour, false);
+ }
+
+ int x = 0;
+ int lw = getLineSpacing();
+ int keyDelta = keyCharacter.getWidth() - keyCharacter.getHotspot().x();
+
+ int cancelDelta = 0;
+ int between = 0;
+ if (cancelCount > 0) {
+ cancelDelta = cancelCharacter.getWidth() + cancelCharacter.getWidth() / 3;
+ between = cancelCharacter.getWidth();
+ }
+
+ createPixmapAndMask(keyDelta * ah1.size() + cancelDelta * cancelCount + between +
+ keyCharacter.getWidth() / 4, lw * 8 + 1);
+
+ if (key.isSharp() != previousKey.isSharp()) {
+
+ // cancellation first
+
+ for (int i = 0; i < cancelCount; ++i) {
+
+ int h = ah0[ah0.size() - cancelCount + i];
+ int y = (lw * 2) + ((8 - h) * lw) / 2 - cancelCharacter.getHotspot().y();
+
+ m_p->drawNoteCharacter(x, y, cancelCharacter);
+
+ x += cancelDelta;
+ }
+
+ if (cancelCount > 0) {
+ x += between;
+ }
+ }
+
+ for (unsigned int i = 0; i < ah1.size(); ++i) {
+
+ int h = ah1[i];
+ int y = (lw * 2) + ((8 - h) * lw) / 2 - keyCharacter.getHotspot().y();
+
+ m_p->drawNoteCharacter(x, y, keyCharacter);
+
+ x += keyDelta;
+ }
+
+ if (key.isSharp() == previousKey.isSharp()) {
+
+ // cancellation afterwards
+
+ if (cancelCount > 0) {
+ x += between;
+ }
+
+ for (int i = 0; i < cancelCount; ++i) {
+
+ int h = ah0[ah0.size() - cancelCount + i];
+ int y = (lw * 2) + ((8 - h) * lw) / 2 - cancelCharacter.getHotspot().y();
+
+ m_p->drawNoteCharacter(x, y, cancelCharacter);
+
+ x += cancelDelta;
+ }
+ }
+
+ return makeCanvasPixmap(m_pointZero);
+}
+
+QCanvasPixmap*
+NotePixmapFactory::makeClefDisplayPixmap(const Clef &clef)
+{
+ QCanvasPixmap* clefPixmap = makeClefPixmap(clef);
+
+ int lw = getLineSpacing();
+ int width = clefPixmap->width() + 6 * getNoteBodyWidth();
+
+ createPixmapAndMask(width, lw * 10 + 1);
+
+ int h = clef.getAxisHeight();
+ int y = (lw * 3) + ((8 - h) * lw) / 2;
+ int x = 3 * getNoteBodyWidth();
+ m_p->drawPixmap(x, y - clefPixmap->offsetY(), *clefPixmap);
+
+ for (h = 0; h <= 8; h += 2) {
+ y = (lw * 3) + ((8 - h) * lw) / 2;
+ m_p->drawLine(x / 2, y, m_generatedWidth - x / 2 - 1, y);
+ }
+
+ delete clefPixmap;
+
+ return makeCanvasPixmap(m_pointZero);
+}
+
+QCanvasPixmap*
+NotePixmapFactory::makeKeyDisplayPixmap(const Key &key, const Clef &clef)
+{
+ std::vector<int> ah = key.getAccidentalHeights(clef);
+
+ CharName charName = (key.isSharp() ?
+ NoteCharacterNames::SHARP :
+ NoteCharacterNames::FLAT);
+
+ QCanvasPixmap* clefPixmap = makeClefPixmap(clef);
+ QPixmap accidentalPixmap(*m_font->getCharacter(charName).getPixmap());
+ QPoint hotspot(m_font->getHotspot(charName));
+
+ int lw = getLineSpacing();
+ int delta = accidentalPixmap.width() - hotspot.x();
+ int maxDelta = getAccidentalWidth(Sharp);
+ int width = clefPixmap->width() + 5 * maxDelta + 7 * maxDelta;
+ int x = clefPixmap->width() + 5 * maxDelta / 2;
+
+ createPixmapAndMask(width, lw * 10 + 1);
+
+ int h = clef.getAxisHeight();
+ int y = (lw * 3) + ((8 - h) * lw) / 2;
+ m_p->drawPixmap(2 * maxDelta, y - clefPixmap->offsetY(), *clefPixmap);
+
+ for (unsigned int i = 0; i < ah.size(); ++i) {
+
+ h = ah[i];
+ y = (lw * 3) + ((8 - h) * lw) / 2 - hotspot.y();
+
+ m_p->drawPixmap(x, y, accidentalPixmap);
+
+ x += delta;
+ }
+
+ for (h = 0; h <= 8; h += 2) {
+ y = (lw * 3) + ((8 - h) * lw) / 2;
+ m_p->drawLine(maxDelta, y, m_generatedWidth - 2*maxDelta - 1, y);
+ }
+
+ delete clefPixmap;
+ return makeCanvasPixmap(m_pointZero);
+}
+
+int
+NotePixmapFactory::getClefAndKeyWidth(const Key &key, const Clef &clef)
+{
+ std::vector<int> ah = key.getAccidentalHeights(clef);
+ Accidental accidental = key.isSharp() ? Sharp : Flat;
+ NoteCharacter plain = getCharacter(m_style->getClefCharName(clef),
+ PlainColour, false);
+
+ int clefWidth = plain.getWidth();
+ int accWidth = getAccidentalWidth(accidental);
+ int maxDelta = getAccidentalWidth(Sharp);
+
+ int width = clefWidth + 2 * maxDelta + ah.size() * accWidth;
+
+ return width;
+}
+
+QCanvasPixmap*
+NotePixmapFactory::makeTrackHeaderPixmap(
+ int width, int height, TrackHeader *header)
+{
+
+ height -= 4; // Make room to the label frame :
+ // 4 = 2 * (margin + lineWidth)
+
+ createPixmapAndMask(width, height);
+
+ int lw = getLineSpacing();
+ int h;
+ QColor colour;
+ int maxDelta = getAccidentalWidth(Sharp);
+
+ // Staff Y position inside the whole header
+ int offset = (height - 10 * lw -1) / 2;
+
+ // Draw staff lines
+ m_p->painter().setPen(QPen(Qt::black, getStaffLineThickness()));
+ for (h = 0; h <= 8; h += 2) {
+ int y = (lw * 3) + ((8 - h) * lw) / 2;
+ m_p->drawLine(maxDelta/2, y + offset, m_generatedWidth - maxDelta/2, y + offset);
+ }
+
+ if (header->isAClefToDraw()) {
+ const Clef &clef = header->getClef();
+ // TODO : use colours from GUIPalette
+ colour = header->isClefInconsistent() ? Qt::red : Qt::black;
+
+ int hue, sat, val;
+ colour.getHsv(&hue, &sat, &val);
+ NoteCharacter clefChar = m_font->getCharacterColoured
+ (m_style->getClefCharName(clef),
+ hue, val, NoteFont::Screen, false);
+
+ // Draw clef
+ h = clef.getAxisHeight();
+ int y = (lw * 3) + ((8 - h) * lw) / 2;
+ m_p->drawNoteCharacter(maxDelta,
+ y - clefChar.getHotspot().y() + offset, clefChar);
+
+ // If necessary, write 8 or 15 above or under the clef
+ int oct = clef.getOctaveOffset();
+ if (oct != 0) {
+
+ int adjustedOctave = (8 * (oct < 0 ? -oct : oct));
+ if (adjustedOctave > 8)
+ adjustedOctave--;
+ else if (adjustedOctave < 8)
+ adjustedOctave++;
+
+ QString text = QString("%1").arg(adjustedOctave);
+ QRect rect = m_clefOttavaFontMetrics.boundingRect(text);
+
+ m_p->painter().setPen(colour);
+
+ m_p->painter().setFont(m_clefOttavaFont);
+ // m_p->maskPainter().setFont(m_clefOttavaFont);
+ int xpos = maxDelta + clefChar.getWidth() / 2 - rect.width() / 2;
+ int ypos = y - clefChar.getHotspot().y() + offset
+ + (oct < 0 ? clefChar.getHeight() + rect.height() - 1 : - rect.height() / 3);
+ m_p->drawText(xpos, ypos, text);
+ }
+
+ // TODO : use colours from GUIPalette
+ colour = header->isKeyInconsistent() ? Qt::red : Qt::black;
+
+
+ // Draw the key signature if any
+
+ const Key &key = header->getKey();
+ std::vector<int> ah = key.getAccidentalHeights(clef);
+
+ CharName charName = key.isSharp() ?
+ NoteCharacterNames::SHARP :
+ NoteCharacterNames::FLAT;
+
+ colour.getHsv(&hue, &sat, &val);
+ NoteCharacter accident = m_font->getCharacterColoured(charName,
+ hue, val, NoteFont::Screen, false);
+
+ QPoint hotspot(m_font->getHotspot(charName));
+ int delta = accident.getWidth() - hotspot.x();
+
+ int x = clefChar.getWidth() + maxDelta;
+ for (unsigned int i = 0; i < ah.size(); ++i) {
+ h = ah[i];
+ y = (lw * 3) + ((8 - h) * lw) / 2 - hotspot.y() + offset;
+ m_p->drawNoteCharacter(x, y, accident);
+
+ x += delta;
+ }
+
+ }
+
+ m_p->painter().setFont(m_trackHeaderFont);
+ // m_p->maskPainter().setFont(m_trackHeaderFont);
+
+ QString text;
+ QString textLine;
+
+ int charHeight = m_trackHeaderFontMetrics.height();
+ int charWidth = m_trackHeaderFontMetrics.maxWidth();
+
+ const QString transposeText = header->getTransposeText();
+ QRect bounds = m_trackHeaderBoldFontMetrics.boundingRect(transposeText);
+ int transposeWidth = bounds.width();
+
+
+ // Write upper text (track name and track label)
+
+ m_p->painter().setPen(Qt::black);
+ text = header->getUpperText();
+ int numberOfTextLines = header->getNumberOfTextLines();
+
+ for (int l=1; l<=numberOfTextLines; l++) {
+ int upperTextY = charHeight + (l - 1) * getTrackHeaderTextLineSpacing();
+ if (l == numberOfTextLines) {
+ int transposeSpace = transposeWidth ? transposeWidth + charWidth / 4 : 0;
+ textLine = getOneLine(text, width - transposeSpace - charWidth / 2);
+ if (!text.isEmpty()) {
+ // String too long : cut it and replace last character by dots
+ int len = textLine.length();
+ if (len > 1) textLine.replace(len - 1, 1, i18n("..."));
+ }
+ } else {
+ textLine = getOneLine(text, width - charWidth / 2);
+ }
+ if (textLine.isEmpty()) break;
+ m_p->drawText(charWidth / 4, upperTextY, textLine);
+ }
+
+
+ // Write transposition text
+
+ // TODO : use colours from GUIPalette
+ colour = header->isTransposeInconsistent() ? Qt::red : Qt::black;
+ m_p->painter().setFont(m_trackHeaderBoldFont);
+ // m_p->maskPainter().setFont(m_trackHeaderBoldFont);
+ m_p->painter().setPen(colour);
+
+ m_p->drawText(width - transposeWidth - charWidth / 4,
+ charHeight
+ + (numberOfTextLines - 1) * getTrackHeaderTextLineSpacing(),
+ transposeText);
+
+
+ // Write lower text (segment label)
+
+ // TODO : use colours from GUIPalette
+ colour = header->isLabelInconsistent() ? Qt::red : Qt::black;
+ m_p->painter().setFont(m_trackHeaderFont);
+ // m_p->maskPainter().setFont(m_trackHeaderFont);
+
+ m_p->painter().setPen(colour);
+ text = header->getLowerText();
+
+ for (int l=1; l<=numberOfTextLines; l++) {
+ int lowerTextY = m_generatedHeight - 4 // -4 : adjust
+ - (numberOfTextLines - l) * getTrackHeaderTextLineSpacing();
+
+ QString textLine = getOneLine(text, width - charWidth / 2);
+ if (textLine.isEmpty()) break;
+
+ if ((l == numberOfTextLines) && !text.isEmpty()) {
+ // String too long : cut it and replace last character by dots
+ int len = textLine.length();
+ if (len > 1) textLine.replace(len - 1, 1, i18n("..."));
+ }
+
+ m_p->drawText(charWidth / 4, lowerTextY, textLine);
+ }
+
+ return makeCanvasPixmap(m_pointZero, true);
+}
+
+int
+NotePixmapFactory::getTrackHeaderNTL(int height)
+{
+ int clefMaxHeight = 12 * getLineSpacing();
+ int textLineHeight = getTrackHeaderTextLineSpacing();
+ int numberOfLines = ((height - clefMaxHeight) / 2) / textLineHeight;
+ return (numberOfLines > 0) ? numberOfLines : 1;
+}
+
+int
+NotePixmapFactory::getTrackHeaderTextWidth(QString str)
+{
+ QRect bounds = m_trackHeaderFontMetrics.boundingRect(str);
+ return bounds.width();
+}
+
+int
+NotePixmapFactory::getTrackHeaderTextLineSpacing()
+{
+ // 3/2 is some arbitrary line spacing
+ return m_trackHeaderFont.pixelSize() * 3 / 2;
+}
+
+QString
+NotePixmapFactory::getOneLine(QString &text, int width)
+{
+ QString str;
+ int n;
+
+ // Immediately stop if string is empty or only contains white spaces ...
+ if (text.stripWhiteSpace().isEmpty()) return QString("");
+
+ // ... or if width is too small.
+ if (width < m_trackHeaderFontMetrics.boundingRect(text.left(1)).width())
+ return QString("");
+
+ // Get a first approx. string length
+ int totalLength = text.length();
+ n = totalLength * width / getTrackHeaderTextWidth(text) + 1;
+ if (n > totalLength) n = totalLength;
+
+ // Verify string size is less than width then correct it if necessary
+ while (((getTrackHeaderTextWidth(text.left(n))) > width) && n) n--;
+
+ if (n == 0) {
+ str = text;
+ text = QString("");
+ } else {
+ str = text.left(n);
+ text.remove(0, n);
+ }
+
+ return str;
+}
+
+QCanvasPixmap*
+NotePixmapFactory::makePitchDisplayPixmap(int p, const Clef &clef,
+ bool useSharps)
+{
+ NotationRules rules;
+
+ Pitch pitch(p);
+ Accidental accidental(pitch.getAccidental(useSharps));
+ NotePixmapParameters params(Note::Crotchet, 0, accidental);
+
+ QCanvasPixmap* clefPixmap = makeClefPixmap(clef);
+
+ int lw = getLineSpacing();
+ int width = getClefWidth(Clef::Bass) + 10 * getNoteBodyWidth();
+
+ int h = pitch.getHeightOnStaff(clef, useSharps);
+ params.setStemGoesUp(rules.isStemUp(h));
+
+ if (h < -1)
+ params.setStemLength(lw * (4 - h) / 2);
+ else if (h > 9)
+ params.setStemLength(lw * (h - 4) / 2);
+ if (h > 8)
+ params.setLegerLines(h - 8);
+ else if (h < 0)
+ params.setLegerLines(h);
+
+ params.setIsOnLine(h % 2 == 0);
+ params.setSelected(m_selected);
+
+ QCanvasPixmap *notePixmap = makeNotePixmap(params);
+
+ int pixmapHeight = lw * 12 + 1;
+ int yoffset = lw * 3;
+ if (h > 12) {
+ pixmapHeight += 6 * lw;
+ yoffset += 6 * lw;
+ } else if (h < -4) {
+ pixmapHeight += 6 * lw;
+ }
+
+ createPixmapAndMask(width, pixmapHeight);
+
+ int x =
+ getClefWidth(Clef::Bass) + 5 * getNoteBodyWidth() -
+ getAccidentalWidth(accidental);
+ int y = yoffset + ((8 - h) * lw) / 2 - notePixmap->offsetY();
+ m_p->drawPixmap(x, y, *notePixmap);
+
+ h = clef.getAxisHeight();
+ x = 3 * getNoteBodyWidth();
+ y = yoffset + ((8 - h) * lw) / 2;
+ m_p->drawPixmap(x, y - clefPixmap->offsetY(), *clefPixmap);
+
+ for (h = 0; h <= 8; h += 2) {
+ y = yoffset + ((8 - h) * lw) / 2;
+ m_p->drawLine(x / 2, y, m_generatedWidth - x / 2, y);
+ }
+
+ delete clefPixmap;
+ delete notePixmap;
+
+ return makeCanvasPixmap(m_pointZero);
+}
+
+QCanvasPixmap*
+NotePixmapFactory::makePitchDisplayPixmap(int p, const Clef &clef,
+ int octave, int step)
+{
+ NotationRules rules;
+
+ Pitch pitch(step, octave, p, 0);
+ Accidental accidental = pitch.getDisplayAccidental(Key("C major"));
+ NotePixmapParameters params(Note::Crotchet, 0, accidental);
+
+ QCanvasPixmap* clefPixmap = makeClefPixmap(clef);
+
+ int lw = getLineSpacing();
+ int width = getClefWidth(Clef::Bass) + 10 * getNoteBodyWidth();
+
+ int h = pitch.getHeightOnStaff
+ (clef,
+ Key("C major"));
+ params.setStemGoesUp(rules.isStemUp(h));
+
+ if (h < -1)
+ params.setStemLength(lw * (4 - h) / 2);
+ else if (h > 9)
+ params.setStemLength(lw * (h - 4) / 2);
+ if (h > 8)
+ params.setLegerLines(h - 8);
+ else if (h < 0)
+ params.setLegerLines(h);
+
+ params.setIsOnLine(h % 2 == 0);
+ params.setSelected(m_selected);
+
+ QCanvasPixmap *notePixmap = makeNotePixmap(params);
+
+ int pixmapHeight = lw * 12 + 1;
+ int yoffset = lw * 3;
+ if (h > 12) {
+ pixmapHeight += 6 * lw;
+ yoffset += 6 * lw;
+ } else if (h < -4) {
+ pixmapHeight += 6 * lw;
+ }
+
+ createPixmapAndMask(width, pixmapHeight);
+
+ int x =
+ getClefWidth(Clef::Bass) + 5 * getNoteBodyWidth() -
+ getAccidentalWidth(accidental);
+ int y = yoffset + ((8 - h) * lw) / 2 - notePixmap->offsetY();
+ m_p->drawPixmap(x, y, *notePixmap);
+
+ h = clef.getAxisHeight();
+ x = 3 * getNoteBodyWidth();
+ y = yoffset + ((8 - h) * lw) / 2;
+ m_p->drawPixmap(x, y - clefPixmap->offsetY(), *clefPixmap);
+
+ for (h = 0; h <= 8; h += 2) {
+ y = yoffset + ((8 - h) * lw) / 2;
+ m_p->drawLine(x / 2, y, m_generatedWidth - x / 2, y);
+ }
+
+ delete clefPixmap;
+ delete notePixmap;
+
+ return makeCanvasPixmap(m_pointZero);
+}
+
+QCanvasPixmap*
+NotePixmapFactory::makeHairpinPixmap(int length, bool isCrescendo)
+{
+ Profiler profiler("NotePixmapFactory::makeHairpinPixmap");
+ drawHairpinAux(length, isCrescendo, 0, 0, 0);
+ return makeCanvasPixmap(QPoint(0, m_generatedHeight / 2));
+}
+
+void
+NotePixmapFactory::drawHairpin(int length, bool isCrescendo,
+ QPainter &painter, int x, int y)
+{
+ Profiler profiler("NotePixmapFactory::drawHairpin");
+ m_inPrinterMethod = true;
+ drawHairpinAux(length, isCrescendo, &painter, x, y);
+ m_inPrinterMethod = false;
+}
+
+void
+NotePixmapFactory::drawHairpinAux(int length, bool isCrescendo,
+ QPainter *painter, int x, int y)
+{
+ int nbh = getNoteBodyHeight();
+ int nbw = getNoteBodyWidth();
+
+ int height = (int)(((double)nbh / (double)(nbw * 40)) * length) + nbh;
+ int thickness = getStaffLineThickness() * 3 / 2;
+
+ // NOTATION_DEBUG << "NotePixmapFactory::makeHairpinPixmap: mapped length " << length << " to height " << height << " (nbh = " << nbh << ", nbw = " << nbw << ")" << endl;
+
+ if (height < nbh)
+ height = nbh;
+ if (height > nbh*2)
+ height = nbh * 2;
+
+ height += thickness - 1;
+
+ if (painter) {
+ painter->save();
+ m_p->beginExternal(painter);
+ painter->translate(x, y - height / 2);
+ } else {
+ createPixmapAndMask(length, height);
+ }
+
+ if (m_selected) {
+ m_p->painter().setPen(GUIPalette::getColour(GUIPalette::SelectedElement));
+ }
+
+ int left = 1, right = length - 2 * nbw / 3 + 1;
+
+ bool smooth = m_font->isSmooth();
+
+ if (isCrescendo) {
+ drawShallowLine(left, height / 2 - 1,
+ right, height - thickness - 1, thickness, smooth);
+ drawShallowLine(left, height / 2 - 1, right, 0, thickness, smooth);
+ } else {
+ drawShallowLine(left, 0, right, height / 2 - 1, thickness, smooth);
+ drawShallowLine(left, height - thickness - 1,
+ right, height / 2 - 1, thickness, smooth);
+ }
+
+ m_p->painter().setPen(Qt::black);
+
+ if (painter) {
+ painter->restore();
+ }
+}
+
+QCanvasPixmap*
+NotePixmapFactory::makeSlurPixmap(int length, int dy, bool above, bool phrasing)
+{
+ Profiler profiler("NotePixmapFactory::makeSlurPixmap");
+
+ //!!! could remove "height > 5" requirement if we did a better job of
+ // sizing so that any horizontal part was rescaled down to exactly
+ // 1 pixel wide instead of blurring
+ bool smooth = m_font->isSmooth() && getNoteBodyHeight() > 5;
+ QPoint hotspot;
+ if (length < getNoteBodyWidth()*2)
+ length = getNoteBodyWidth() * 2;
+ drawSlurAux(length, dy, above, smooth, false, phrasing, hotspot, 0, 0, 0);
+
+ m_p->end();
+
+ if (smooth) {
+
+ QImage i = m_generatedPixmap->convertToImage();
+ if (i.depth() == 1)
+ i = i.convertDepth(32);
+ i = i.smoothScale(i.width() / 2, i.height() / 2);
+
+ delete m_generatedPixmap;
+ delete m_generatedMask;
+ QPixmap newPixmap(i);
+ QCanvasPixmap *p = new QCanvasPixmap(newPixmap, hotspot);
+ p->setMask(PixmapFunctions::generateMask(newPixmap,
+ Qt::white.rgb()));
+ return p;
+
+ } else {
+
+ QCanvasPixmap *p = new QCanvasPixmap(*m_generatedPixmap, hotspot);
+ p->setMask(PixmapFunctions::generateMask(*m_generatedPixmap,
+ Qt::white.rgb()));
+ delete m_generatedPixmap;
+ delete m_generatedMask;
+ return p;
+ }
+}
+
+void
+NotePixmapFactory::drawSlur(int length, int dy, bool above, bool phrasing,
+ QPainter &painter, int x, int y)
+{
+ Profiler profiler("NotePixmapFactory::drawSlur");
+ QPoint hotspot;
+ m_inPrinterMethod = true;
+ if (length < getNoteBodyWidth()*2)
+ length = getNoteBodyWidth() * 2;
+ drawSlurAux(length, dy, above, false, false, phrasing, hotspot, &painter, x, y);
+ m_inPrinterMethod = false;
+}
+
+void
+NotePixmapFactory::drawSlurAux(int length, int dy, bool above,
+ bool smooth, bool flat, bool phrasing,
+ QPoint &hotspot, QPainter *painter, int x, int y)
+{
+ QWMatrix::TransformationMode mode = QWMatrix::transformationMode();
+ QWMatrix::setTransformationMode(QWMatrix::Points);
+
+ int thickness = getStaffLineThickness() * 2;
+ if (phrasing)
+ thickness = thickness * 3 / 4;
+ int nbh = getNoteBodyHeight(), nbw = getNoteBodyWidth();
+
+ // Experiment with rotating the painter rather than the control points.
+ double theta = 0;
+ bool rotate = false;
+ if (dy != 0) {
+ // We have opposite (dy) and adjacent (length).
+ theta = atan(double(dy) / double(length)) * 180.0 / M_PI;
+ // NOTATION_DEBUG << "slur: dy is " << dy << ", length " << length << ", rotating through " << theta << endl;
+ rotate = true;
+ }
+
+ // draw normal slur for very slopey phrasing slur:
+ if (theta < -5 || theta > 5)
+ phrasing = false;
+
+ int y0 = 0, my = 0;
+
+ float noteLengths = float(length) / nbw;
+ if (noteLengths < 1)
+ noteLengths = 1;
+
+ my = int(0 - nbh * sqrt(noteLengths) / 2);
+ if (flat)
+ my = my * 2 / 3;
+ else if (phrasing)
+ my = my * 3 / 4;
+ if (!above)
+ my = -my;
+
+ bool havePixmap = false;
+ QPoint topLeft, bottomRight;
+
+ if (smooth)
+ thickness += 2;
+
+ for (int i = 0; i < thickness; ++i) {
+
+ Spline::PointList pl;
+
+ if (!phrasing) {
+ pl.push_back(QPoint(length / 6, my));
+ pl.push_back(QPoint(length - length / 6, my));
+ } else {
+ pl.push_back(QPoint(abs(my) / 4, my / 3));
+ pl.push_back(QPoint(length / 6, my));
+
+ if (theta > 1) {
+ pl.push_back(QPoint(length * 3 / 8, my * 3 / 2));
+ } else if (theta < -1) {
+ pl.push_back(QPoint(length * 5 / 8, my * 3 / 2));
+ } else {
+ pl.push_back(QPoint(length / 2, my * 4 / 3));
+ }
+
+ pl.push_back(QPoint(length - length / 6, my));
+ pl.push_back(QPoint(length - abs(my) / 4, my / 3));
+ }
+
+ Spline::PointList *polyPoints = Spline::calculate
+ (QPoint(0, y0), QPoint(length - 1, y0), pl, topLeft, bottomRight);
+
+ if (!havePixmap) {
+ int width = bottomRight.x() - topLeft.x();
+ int height = bottomRight.y() - topLeft.y() + thickness - 1 + abs(dy);
+ hotspot = QPoint(0, -topLeft.y() + (dy < 0 ? -dy : 0));
+
+ // NOTATION_DEBUG << "slur: bottomRight (" << bottomRight.x() << "," << bottomRight.y() << "), topLeft (" << topLeft.x() << "," << topLeft.y() << "), width " << width << ", height " << height << ", hotspot (" << hotspot.x() << "," << hotspot.y() << "), dy " << dy << ", thickness " << thickness << endl;
+
+ if (painter) {
+
+ // This conditional is because we're also called with
+ // a painter arg from non-printer drawTie. It's a big
+ // hack.
+
+ if (m_inPrinterMethod) {
+ painter->save();
+ m_p->beginExternal(painter);
+ painter->translate(x, y);
+ if (rotate)
+ painter->rotate(theta);
+ } else {
+ m_p->painter().save();
+ m_p->maskPainter().save();
+ m_p->painter().translate(x, y);
+ m_p->maskPainter().translate(x, y);
+ if (rotate) {
+ m_p->painter().rotate(theta);
+ m_p->maskPainter().rotate(theta);
+ }
+ }
+
+ } else {
+ createPixmapAndMask(smooth ? width*2 + 1 : width,
+ smooth ? height*2 + thickness*2 : height + thickness,
+ width, height);
+
+ QWMatrix m;
+ if (smooth)
+ m.translate(2 * hotspot.x(), 2 * hotspot.y());
+ else
+ m.translate(hotspot.x(), hotspot.y());
+ m.rotate(theta);
+ m_p->painter().setWorldMatrix(m);
+ m_p->maskPainter().setWorldMatrix(m);
+ }
+
+ if (m_selected)
+ m_p->painter().setPen(GUIPalette::getColour(GUIPalette::SelectedElement));
+ else if (m_shaded) {
+ m_p->painter().setPen(Qt::gray);
+ }
+ havePixmap = true;
+ }
+ /*
+ for (int j = 0; j < pl.size(); ++j) {
+ if (smooth) {
+ m_p->drawPoint(pl[j].x()*2, pl[j].y()*2);
+ } else {
+ m_p->drawPoint(pl[j].x(), pl[j].y());
+ }
+ }
+ */
+ int ppc = polyPoints->size();
+ QPointArray qp(ppc);
+
+ for (int j = 0; j < ppc; ++j) {
+ qp.setPoint(j, (*polyPoints)[j].x(), (*polyPoints)[j].y());
+ }
+
+ delete polyPoints;
+
+ if (!smooth || (i > 0 && i < thickness - 1)) {
+ if (smooth) {
+ for (int j = 0; j < ppc; ++j) {
+ qp.setPoint(j, qp.point(j).x()*2, qp.point(j).y()*2);
+ }
+ m_p->drawPolyline(qp);
+ for (int j = 0; j < ppc; ++j) {
+ qp.setPoint(j, qp.point(j).x(), qp.point(j).y() + 1);
+ }
+ m_p->drawPolyline(qp);
+ } else {
+ m_p->drawPolyline(qp);
+ }
+ }
+
+ if (above) {
+ ++my;
+ if (i % 2)
+ ++y0;
+ } else {
+ --my;
+ if (i % 2)
+ --y0;
+ }
+ }
+
+ if (m_selected) {
+ m_p->painter().setPen(Qt::black);
+ }
+
+ QWMatrix::setTransformationMode(mode);
+
+ if (painter) {
+ painter->restore();
+ if (!m_inPrinterMethod)
+ m_p->maskPainter().restore();
+ }
+}
+
+QCanvasPixmap*
+NotePixmapFactory::makeOttavaPixmap(int length, int octavesUp)
+{
+ Profiler profiler("NotePixmapFactory::makeOttavaPixmap");
+ m_inPrinterMethod = false;
+ drawOttavaAux(length, octavesUp, 0, 0, 0);
+ return makeCanvasPixmap(QPoint(0, m_generatedHeight - 1));
+}
+
+void
+NotePixmapFactory::drawOttava(int length, int octavesUp,
+ QPainter &painter, int x, int y)
+{
+ Profiler profiler("NotePixmapFactory::drawOttava");
+ m_inPrinterMethod = true;
+ drawOttavaAux(length, octavesUp, &painter, x, y);
+ m_inPrinterMethod = false;
+}
+
+void
+NotePixmapFactory::drawOttavaAux(int length, int octavesUp,
+ QPainter *painter, int x, int y)
+{
+ int height = m_ottavaFontMetrics.height();
+ int backpedal = 0;
+ QString label;
+ QRect r;
+
+ if (octavesUp == 2 || octavesUp == -2) {
+ label = "15ma ";
+ backpedal = m_ottavaFontMetrics.width("15") / 2;
+ } else {
+ label = "8va ";
+ backpedal = m_ottavaFontMetrics.width("8") / 2;
+ }
+
+ int width = length + backpedal;
+
+ if (painter) {
+ painter->save();
+ m_p->beginExternal(painter);
+ painter->translate(x - backpedal, y - height);
+ } else {
+ NOTATION_DEBUG << "NotePixmapFactory::drawOttavaAux: making pixmap and mask " << width << "x" << height << endl;
+ createPixmapAndMask(width, height);
+ }
+
+ int thickness = getStemThickness();
+ QPen pen(Qt::black, thickness, Qt::DotLine);
+
+ if (m_selected) {
+ m_p->painter().setPen(GUIPalette::getColour(GUIPalette::SelectedElement));
+ pen.setColor(GUIPalette::getColour(GUIPalette::SelectedElement));
+ } else if (m_shaded) {
+ m_p->painter().setPen(Qt::gray);
+ pen.setColor(Qt::gray);
+ }
+
+ m_p->painter().setFont(m_ottavaFont);
+ if (!m_inPrinterMethod)
+ m_p->maskPainter().setFont(m_ottavaFont);
+
+ m_p->drawText(0, m_ottavaFontMetrics.ascent(), label);
+
+ m_p->painter().setPen(pen);
+ // if (!m_inPrinterMethod) m_p->maskPainter().setPen(pen);
+
+ int x0 = m_ottavaFontMetrics.width(label) + thickness;
+ int x1 = width - thickness;
+ int y0 = m_ottavaFontMetrics.ascent() * 2 / 3 - thickness / 2;
+ int y1 = (octavesUp < 0 ? 0 : m_ottavaFontMetrics.ascent());
+
+ NOTATION_DEBUG << "NotePixmapFactory::drawOttavaAux: drawing " << x0 << "," << y0 << " to " << x1 << "," << y0 << ", thickness " << thickness << endl;
+
+ m_p->drawLine(x0, y0, x1, y0);
+
+ pen.setStyle(Qt::SolidLine);
+ m_p->painter().setPen(pen);
+ // if (!m_inPrinterMethod) m_p->maskPainter().setPen(pen);
+
+ NOTATION_DEBUG << "NotePixmapFactory::drawOttavaAux: drawing " << x1 << "," << y0 << " to " << x1 << "," << y1 << ", thickness " << thickness << endl;
+
+ m_p->drawLine(x1, y0, x1, y1);
+
+ m_p->painter().setPen(QPen());
+ if (!m_inPrinterMethod)
+ m_p->maskPainter().setPen(QPen());
+
+ if (painter) {
+ painter->restore();
+ }
+}
+
+void
+NotePixmapFactory::drawBracket(int length, bool left, bool curly, int x, int y)
+{
+ // curly mode not yet implemented
+
+ int thickness = getStemThickness() * 2;
+
+ int m1 = length / 6;
+ int m2 = length - length / 6 - 1;
+
+ int off0 = 0, moff = 0;
+
+ int nbh = getNoteBodyHeight(), nbw = getNoteBodyWidth();
+ float noteLengths = float(length) / nbw;
+ if (noteLengths < 1)
+ noteLengths = 1;
+ moff = int(nbh * sqrt(noteLengths) / 2);
+ moff = moff * 2 / 3;
+
+ if (left)
+ moff = -moff;
+
+ QPoint topLeft, bottomRight;
+
+ for (int i = 0; i < thickness; ++i) {
+
+ Spline::PointList pl;
+ pl.push_back(QPoint((int)moff, m1));
+ pl.push_back(QPoint((int)moff, m2));
+ /*
+ NOTATION_DEBUG << "bracket spline controls: " << moff << "," << m1
+ << ", " << moff << "," << m2 << "; end points "
+ << off0 << ",0, " << off0 << "," << length-1
+ << endl;
+ */
+ Spline::PointList *polyPoints = Spline::calculate
+ (QPoint(off0, 0), QPoint(off0, length - 1), pl, topLeft, bottomRight);
+
+ int ppc = polyPoints->size();
+ QPointArray qp(ppc);
+ /*
+ NOTATION_DEBUG << "bracket spline polypoints: " << endl;
+ for (int j = 0; j < ppc; ++j) {
+ NOTATION_DEBUG << (*polyPoints)[j].x() << "," << (*polyPoints)[j].y() << endl;
+ }
+ */
+
+ for (int j = 0; j < ppc; ++j) {
+ qp.setPoint(j, x + (*polyPoints)[j].x(), y + (*polyPoints)[j].y());
+ }
+
+ delete polyPoints;
+
+ m_p->drawPolyline(qp);
+
+ if (!left) {
+ ++moff;
+ if (i % 2)
+ ++off0;
+ } else {
+ --moff;
+ if (i % 2)
+ --off0;
+ }
+ }
+}
+
+QCanvasPixmap*
+NotePixmapFactory::makeTimeSigPixmap(const TimeSignature& sig)
+{
+ Profiler profiler("NotePixmapFactory::makeTimeSigPixmap");
+
+ if (sig.isCommon()) {
+
+ NoteCharacter character;
+
+ CharName charName;
+ if (sig.getNumerator() == 2) {
+ charName = NoteCharacterNames::CUT_TIME;
+ } else {
+ charName = NoteCharacterNames::COMMON_TIME;
+ }
+
+ if (getCharacter(charName, character, PlainColour, false)) {
+ createPixmapAndMask(character.getWidth(), character.getHeight());
+ m_p->drawNoteCharacter(0, 0, character);
+ return makeCanvasPixmap(QPoint(0, character.getHeight() / 2));
+ }
+
+ QString c("c");
+ QRect r = m_bigTimeSigFontMetrics.boundingRect(c);
+
+ int dy = getLineSpacing() / 4;
+ createPixmapAndMask(r.width(), r.height() + dy*2);
+
+ if (m_selected) {
+ m_p->painter().setPen(GUIPalette::getColour(GUIPalette::SelectedElement));
+ } else if (m_shaded) {
+ m_p->painter().setPen(Qt::gray);
+ }
+
+ m_p->painter().setFont(m_bigTimeSigFont);
+ if (!m_inPrinterMethod)
+ m_p->maskPainter().setFont(m_bigTimeSigFont);
+
+ m_p->drawText(0, r.height() + dy, c);
+
+ if (sig.getNumerator() == 2) { // cut common
+
+ int x = r.width() * 3 / 5 - getStemThickness();
+
+ for (int i = 0; i < getStemThickness() * 2; ++i, ++x) {
+ m_p->drawLine(x, 0, x, r.height() + dy*2 - 1);
+ }
+ }
+
+ m_p->painter().setPen(Qt::black);
+ return makeCanvasPixmap(QPoint(0, r.height() / 2 + dy));
+
+ } else {
+
+ int numerator = sig.getNumerator(),
+ denominator = sig.getDenominator();
+
+ QString numS, denomS;
+
+ numS.setNum(numerator);
+ denomS.setNum(denominator);
+
+ NoteCharacter character;
+ if (getCharacter(m_style->getTimeSignatureDigitName(0), character,
+ PlainColour, false)) {
+
+ // if the 0 digit exists, we assume 1-9 also all exist
+ // and all have the same width
+
+ int numW = character.getWidth() * numS.length();
+ int denomW = character.getWidth() * denomS.length();
+
+ int width = std::max(numW, denomW);
+ int height = getLineSpacing() * 4 - getStaffLineThickness();
+
+ createPixmapAndMask(width, height);
+
+ for (unsigned int i = 0; i < numS.length(); ++i) {
+ int x = width - (width - numW) / 2 - (i + 1) * character.getWidth();
+ int y = height / 4 - (character.getHeight() / 2);
+ NoteCharacter charCharacter = getCharacter
+ (m_style->getTimeSignatureDigitName(numerator % 10),
+ PlainColour, false);
+ m_p->drawNoteCharacter(x, y, charCharacter);
+ numerator /= 10;
+ }
+
+ for (unsigned int i = 0; i < denomS.length(); ++i) {
+ int x = width - (width - denomW) / 2 - (i + 1) * character.getWidth();
+ int y = height - height / 4 - (character.getHeight() / 2);
+ NoteCharacter charCharacter = getCharacter
+ (m_style->getTimeSignatureDigitName(denominator % 10),
+ PlainColour, false);
+ m_p->drawNoteCharacter(x, y, charCharacter);
+ denominator /= 10;
+ }
+
+ return makeCanvasPixmap(QPoint(0, height / 2));
+ }
+
+ QRect numR = m_timeSigFontMetrics.boundingRect(numS);
+ QRect denomR = m_timeSigFontMetrics.boundingRect(denomS);
+ int width = std::max(numR.width(), denomR.width()) + 2;
+ int x;
+
+ createPixmapAndMask(width, denomR.height() * 2 + getNoteBodyHeight());
+
+ if (m_selected) {
+ m_p->painter().setPen(GUIPalette::getColour(GUIPalette::SelectedElement));
+ } else if (m_shaded) {
+ m_p->painter().setPen(Qt::gray);
+ }
+
+ m_p->painter().setFont(m_timeSigFont);
+ if (!m_inPrinterMethod)
+ m_p->maskPainter().setFont(m_timeSigFont);
+
+ x = (width - numR.width()) / 2 - 1;
+ m_p->drawText(x, denomR.height(), numS);
+
+ x = (width - denomR.width()) / 2 - 1;
+ m_p->drawText(x, denomR.height() * 2 + (getNoteBodyHeight() / 2) - 1, denomS);
+
+ m_p->painter().setPen(Qt::black);
+
+ return makeCanvasPixmap(QPoint(0, denomR.height() +
+ (getNoteBodyHeight() / 4) - 1),
+ true);
+ }
+}
+
+int NotePixmapFactory::getTimeSigWidth(const TimeSignature &sig) const
+{
+ if (sig.isCommon()) {
+
+ QRect r(m_bigTimeSigFontMetrics.boundingRect("c"));
+ return r.width() + 2;
+
+ } else {
+
+ int numerator = sig.getNumerator(),
+ denominator = sig.getDenominator();
+
+ QString numS, denomS;
+
+ numS.setNum(numerator);
+ denomS.setNum(denominator);
+
+ QRect numR = m_timeSigFontMetrics.boundingRect(numS);
+ QRect denomR = m_timeSigFontMetrics.boundingRect(denomS);
+ int width = std::max(numR.width(), denomR.width()) + 2;
+
+ return width;
+ }
+}
+
+QFont
+NotePixmapFactory::getTextFont(const Text &text) const
+{
+ std::string type(text.getTextType());
+ TextFontCache::iterator i = m_textFontCache.find(type.c_str());
+ if (i != m_textFontCache.end())
+ return i->second;
+
+ /*
+ * Text types:
+ *
+ * UnspecifiedType: Nothing known, use small roman
+ * StaffName: Large roman, to left of start of staff
+ * ChordName: Not normally shown in score, use small roman
+ * KeyName: Not normally shown in score, use small roman
+ * Lyric: Small roman, below staff and dynamic texts
+ * Chord: Small bold roman, above staff
+ * Dynamic: Small italic, below staff
+ * Direction: Large roman, above staff (by barline?)
+ * LocalDirection: Small bold italic, below staff (by barline?)
+ * Tempo: Large bold roman, above staff
+ * LocalTempo: Small bold roman, above staff
+ * Annotation: Very small sans-serif, in a yellow box
+ * LilyPondDirective: Very small sans-serif, in a green box
+ */
+
+ int weight = QFont::Normal;
+ bool italic = false;
+ bool large = false;
+ bool tiny = false;
+ bool serif = true;
+
+ if (type == Text::Tempo ||
+ type == Text::LocalTempo ||
+ type == Text::LocalDirection ||
+ type == Text::Chord) {
+ weight = QFont::Bold;
+ }
+
+ if (type == Text::Dynamic ||
+ type == Text::LocalDirection) {
+ italic = true;
+ }
+
+ if (type == Text::StaffName ||
+ type == Text::Direction ||
+ type == Text::Tempo) {
+ large = true;
+ }
+
+ if (type == Text::Annotation ||
+ type == Text::LilyPondDirective) {
+ serif = false;
+ tiny = true;
+ }
+
+ KConfig* config = kapp->config();
+
+ QFont textFont;
+
+ if (serif) {
+ textFont = QFont(defaultSerifFontFamily);
+ textFont = config->readFontEntry("textfont", &textFont);
+ } else {
+ textFont = QFont(defaultSansSerifFontFamily);
+ textFont = config->readFontEntry("sansfont", &textFont);
+ }
+
+ textFont.setStyleStrategy(QFont::StyleStrategy(QFont::PreferDefault |
+ QFont::PreferMatch));
+
+ int size;
+ if (large)
+ size = (getLineSpacing() * 7) / 2;
+ else if (tiny)
+ size = (getLineSpacing() * 4) / 3;
+ else if (serif)
+ size = (getLineSpacing() * 2);
+ else
+ size = (getLineSpacing() * 3) / 2;
+
+ textFont.setPixelSize(size);
+ textFont.setStyleHint(serif ? QFont::Serif : QFont::SansSerif);
+ textFont.setWeight(weight);
+ textFont.setItalic(italic);
+
+ NOTATION_DEBUG << "NotePixmapFactory::getTextFont: requested size " << size
+ << " for type " << type << endl;
+
+ NOTATION_DEBUG << "NotePixmapFactory::getTextFont: returning font '"
+ << textFont.toString() << "' for type " << type.c_str()
+ << " text : " << text.getText().c_str() << endl;
+
+ m_textFontCache[type.c_str()] = textFont;
+ return textFont;
+}
+
+QCanvasPixmap*
+NotePixmapFactory::makeTextPixmap(const Text &text)
+{
+ Profiler profiler("NotePixmapFactory::makeTextPixmap");
+
+ std::string type(text.getTextType());
+
+ if (type == Text::Annotation ||
+ type == Text::LilyPondDirective) {
+ return makeAnnotationPixmap(text, (type == Text::LilyPondDirective));
+ }
+
+ drawTextAux(text, 0, 0, 0);
+ return makeCanvasPixmap(QPoint(2, 2), true);
+}
+
+QCanvasPixmap*
+NotePixmapFactory::makeGuitarChordPixmap(const Guitar::Fingering &fingering,
+ int x,
+ int y)
+{
+ using namespace Guitar;
+ Profiler profiler("NotePixmapFactory::makeGuitarChordPixmap");
+
+ int guitarChordWidth = getLineSpacing() * 6;
+ int guitarChordHeight = getLineSpacing() * 6;
+
+ createPixmapAndMask(guitarChordWidth, guitarChordHeight);
+
+ if (m_selected) {
+ m_p->painter().setPen(GUIPalette::getColour(GUIPalette::SelectedElement));
+ m_p->painter().setBrush(GUIPalette::getColour(GUIPalette::SelectedElement));
+ } else {
+ m_p->painter().setPen(Qt::black);
+ m_p->painter().setBrush(Qt::black);
+ }
+
+ Guitar::NoteSymbols ns(Guitar::Fingering::DEFAULT_NB_STRINGS, FingeringBox::DEFAULT_NB_DISPLAYED_FRETS);
+ Guitar::NoteSymbols::drawFingeringPixmap(fingering, ns, &(m_p->painter()));
+
+ return makeCanvasPixmap(QPoint (x, y), true);
+}
+
+void
+NotePixmapFactory::drawText(const Text &text,
+ QPainter &painter, int x, int y)
+{
+ Profiler profiler("NotePixmapFactory::drawText");
+
+ // NOTATION_DEBUG << "NotePixmapFactory::drawText() " << text.getText().c_str()
+ // << " - type : " << text.getTextType().c_str() << endl;
+
+ std::string type(text.getTextType());
+
+ if (type == Text::Annotation ||
+ type == Text::LilyPondDirective) {
+ QCanvasPixmap *map = makeAnnotationPixmap(text, (type == Text::LilyPondDirective));
+ painter.drawPixmap(x, y, *map);
+ return ;
+ }
+
+ m_inPrinterMethod = true;
+ drawTextAux(text, &painter, x, y);
+ m_inPrinterMethod = false;
+}
+
+void
+NotePixmapFactory::drawTextAux(const Text &text,
+ QPainter *painter, int x, int y)
+{
+ QString s(strtoqstr(text.getText()));
+ QFont textFont(getTextFont(text));
+ QFontMetrics textMetrics(textFont);
+
+ int offset = 2;
+ int width = textMetrics.width(s) + 2 * offset;
+ int height = textMetrics.height() + 2 * offset;
+
+ if (painter) {
+ painter->save();
+ m_p->beginExternal(painter);
+ painter->translate(x - offset, y - offset);
+ } else {
+ createPixmapAndMask(width, height);
+ }
+
+ if (m_selected)
+ m_p->painter().setPen(GUIPalette::getColour(GUIPalette::SelectedElement));
+ else if (m_shaded)
+ m_p->painter().setPen(Qt::gray);
+
+ m_p->painter().setFont(textFont);
+ if (!m_inPrinterMethod)
+ m_p->maskPainter().setFont(textFont);
+
+ m_p->drawText(offset, textMetrics.ascent() + offset, s);
+
+ m_p->painter().setPen(Qt::black);
+
+ if (painter) {
+ painter->restore();
+ }
+}
+
+QCanvasPixmap*
+NotePixmapFactory::makeAnnotationPixmap(const Text &text)
+{
+ return makeAnnotationPixmap(text, false);
+}
+
+QCanvasPixmap*
+NotePixmapFactory::makeAnnotationPixmap(const Text &text, const bool isLilyPondDirective)
+{
+ QString s(strtoqstr(text.getText()));
+
+ QFont textFont(getTextFont(text));
+ QFontMetrics textMetrics(textFont);
+
+ int annotationWidth = getLineSpacing() * 16;
+ int annotationHeight = getLineSpacing() * 6;
+
+ int topGap = getLineSpacing() / 4 + 1;
+ int bottomGap = getLineSpacing() / 3 + 1;
+ int sideGap = getLineSpacing() / 4 + 1;
+
+ QRect r = textMetrics.boundingRect
+ (0, 0, annotationWidth, annotationHeight, Qt::WordBreak, s);
+
+ int pixmapWidth = r.width() + sideGap * 2;
+ int pixmapHeight = r.height() + topGap + bottomGap;
+
+ createPixmapAndMask(pixmapWidth, pixmapHeight);
+
+ if (m_selected)
+ m_p->painter().setPen(GUIPalette::getColour(GUIPalette::SelectedElement));
+ else if (m_shaded)
+ m_p->painter().setPen(Qt::gray);
+
+ m_p->painter().setFont(textFont);
+ if (!m_inPrinterMethod)
+ m_p->maskPainter().setFont(textFont);
+
+ if (isLilyPondDirective) {
+ m_p->painter().setBrush(GUIPalette::getColour(GUIPalette::TextLilyPondDirectiveBackground));
+ } else {
+ m_p->painter().setBrush(GUIPalette::getColour(GUIPalette::TextAnnotationBackground));
+ }
+
+ m_p->drawRect(0, 0, pixmapWidth, pixmapHeight);
+
+ m_p->painter().setBrush(Qt::black);
+ m_p->painter().drawText(QRect(sideGap, topGap,
+ annotationWidth + sideGap,
+ pixmapHeight - bottomGap),
+ Qt::WordBreak, s);
+
+ /* unnecessary following the rectangle draw
+ m_pm.drawText(QRect(sideGap, topGap,
+ annotationWidth + sideGap, annotationHeight + topGap),
+ Qt::WordBreak, s);
+ */
+
+ return makeCanvasPixmap(QPoint(0, 0));
+}
+
+void
+NotePixmapFactory::createPixmapAndMask(int width, int height,
+ int maskWidth, int maskHeight)
+{
+ if (maskWidth < 0)
+ maskWidth = width;
+ if (maskHeight < 0)
+ maskHeight = height;
+
+ m_generatedWidth = width;
+ m_generatedHeight = height;
+ m_generatedPixmap = new QPixmap(width, height);
+ m_generatedMask = new QBitmap(maskWidth, maskHeight);
+
+ static unsigned long total = 0;
+ total += width * height;
+// NOTATION_DEBUG << "createPixmapAndMask: " << width << "x" << height << " (" << (width*height) << " px, " << total << " total)" << endl;
+
+ // clear up pixmap and mask
+ m_generatedPixmap->fill();
+ m_generatedMask->fill(Qt::color0);
+
+ // initiate painting
+ m_p->begin(m_generatedPixmap, m_generatedMask);
+
+ m_p->painter().setPen(Qt::black);
+ m_p->painter().setBrush(Qt::black);
+ m_p->maskPainter().setPen(Qt::white);
+ m_p->maskPainter().setBrush(Qt::white);
+}
+
+QCanvasPixmap*
+NotePixmapFactory::makeCanvasPixmap(QPoint hotspot, bool generateMask)
+{
+ m_p->end();
+
+ QCanvasPixmap* p = new QCanvasPixmap(*m_generatedPixmap, hotspot);
+
+ if (generateMask) {
+ p->setMask(PixmapFunctions::generateMask(*p));
+ } else {
+ p->setMask(*m_generatedMask);
+ }
+
+ delete m_generatedPixmap;
+ delete m_generatedMask;
+ return p;
+}
+
+NoteCharacter
+NotePixmapFactory::getCharacter(CharName name, ColourType type, bool inverted)
+{
+ NoteCharacter ch;
+ getCharacter(name, ch, type, inverted);
+ return ch;
+}
+
+bool
+NotePixmapFactory::getCharacter(CharName name, NoteCharacter &ch,
+ ColourType type, bool inverted)
+{
+ NoteFont::CharacterType charType =
+ m_inPrinterMethod ? NoteFont::Printer : NoteFont::Screen;
+
+ if (m_selected) {
+ return m_font->getCharacterColoured
+ (name,
+ GUIPalette::SelectedElementHue,
+ GUIPalette::SelectedElementMinValue,
+ ch, charType, inverted);
+ }
+
+ if (m_shaded) {
+ return m_font->getCharacterShaded(name, ch, charType, inverted);
+ }
+
+ switch (type) {
+
+ case PlainColour:
+ return m_font->getCharacter(name, ch, charType, inverted);
+
+ case QuantizedColour:
+ return m_font->getCharacterColoured
+ (name,
+ GUIPalette::QuantizedNoteHue,
+ GUIPalette::QuantizedNoteMinValue,
+ ch, charType, inverted);
+
+ case HighlightedColour:
+ return m_font->getCharacterColoured
+ (name,
+ GUIPalette::HighlightedElementHue,
+ GUIPalette::HighlightedElementMinValue,
+ ch, charType, inverted);
+
+ case TriggerColour:
+ return m_font->getCharacterColoured
+ (name,
+ GUIPalette::TriggerNoteHue,
+ GUIPalette::TriggerNoteMinValue,
+ ch, charType, inverted);
+
+ case OutRangeColour:
+ return m_font->getCharacterColoured
+ (name,
+ GUIPalette::OutRangeNoteHue,
+ GUIPalette::OutRangeNoteMinValue,
+ ch, charType, inverted);
+ }
+
+ return m_font->getCharacter(name, ch, charType, inverted);
+}
+
+QPoint
+NotePixmapFactory::m_pointZero;
+
+
+int NotePixmapFactory::getNoteBodyWidth(Note::Type type)
+const
+{
+ CharName charName(m_style->getNoteHeadCharName(type).first);
+ int hx, hy;
+ if (!m_font->getHotspot(charName, hx, hy))
+ hx = 0;
+ return m_font->getWidth(charName) - hx * 2;
+}
+
+int NotePixmapFactory::getNoteBodyHeight(Note::Type )
+const
+{
+ // this is by definition
+ return m_font->getSize();
+}
+
+int NotePixmapFactory::getLineSpacing() const
+{
+ return m_font->getSize() + getStaffLineThickness();
+}
+
+int NotePixmapFactory::getAccidentalWidth(const Accidental &a,
+ int shift, bool extraShift) const
+{
+ if (a == Accidentals::NoAccidental)
+ return 0;
+ int w = m_font->getWidth(m_style->getAccidentalCharName(a));
+ if (!shift)
+ return w;
+ else {
+ int sw = w;
+ if (extraShift) {
+ --shift;
+ w += getNoteBodyWidth() + getStemThickness();
+ }
+ w += shift *
+ (sw - m_font->getHotspot(m_style->getAccidentalCharName(a)).x());
+ }
+ return w;
+}
+
+int NotePixmapFactory::getAccidentalHeight(const Accidental &a) const
+{
+ return m_font->getHeight(m_style->getAccidentalCharName(a));
+}
+
+int NotePixmapFactory::getStemLength() const
+{
+ unsigned int l = 1;
+ (void)m_font->getStemLength(l);
+ return l;
+}
+
+int NotePixmapFactory::getStemThickness() const
+{
+ unsigned int i = 1;
+ (void)m_font->getStemThickness(i);
+ return i;
+}
+
+int NotePixmapFactory::getStaffLineThickness() const
+{
+ unsigned int i;
+ (void)m_font->getStaffLineThickness(i);
+ return i;
+}
+
+int NotePixmapFactory::getLegerLineThickness() const
+{
+ unsigned int i;
+ (void)m_font->getLegerLineThickness(i);
+ return i;
+}
+
+int NotePixmapFactory::getDotWidth() const
+{
+ return m_font->getWidth(NoteCharacterNames::DOT);
+}
+
+int NotePixmapFactory::getClefWidth(const Clef &clef) const
+{
+ return m_font->getWidth(m_style->getClefCharName(clef.getClefType()));
+}
+
+int NotePixmapFactory::getBarMargin() const
+{
+ return getNoteBodyWidth() * 2;
+}
+
+int NotePixmapFactory::getRestWidth(const Note &restType) const
+{
+ return m_font->getWidth(m_style->getRestCharName(restType.getNoteType(),
+ false)) // small inaccuracy!
+ + (restType.getDots() * getDotWidth());
+}
+
+int NotePixmapFactory::getKeyWidth(const Key &key,
+ Key previousKey) const
+{
+ std::vector<int> ah0 = previousKey.getAccidentalHeights(Clef());
+ std::vector<int> ah1 = key.getAccidentalHeights(Clef());
+
+ int cancelCount = 0;
+ if (key.isSharp() != previousKey.isSharp())
+ cancelCount = ah0.size();
+ else if (ah1.size() < ah0.size())
+ cancelCount = ah0.size() - ah1.size();
+
+ CharName keyCharName;
+ if (key.isSharp())
+ keyCharName = NoteCharacterNames::SHARP;
+ else
+ keyCharName = NoteCharacterNames::FLAT;
+
+ NoteCharacter keyCharacter;
+ NoteCharacter cancelCharacter;
+
+ keyCharacter = m_font->getCharacter(keyCharName);
+ if (cancelCount > 0) {
+ cancelCharacter = m_font->getCharacter(NoteCharacterNames::NATURAL);
+ }
+
+ //int x = 0;
+ //int lw = getLineSpacing();
+ int keyDelta = keyCharacter.getWidth() - keyCharacter.getHotspot().x();
+
+ int cancelDelta = 0;
+ int between = 0;
+ if (cancelCount > 0) {
+ cancelDelta = cancelCharacter.getWidth() + cancelCharacter.getWidth() / 3;
+ between = cancelCharacter.getWidth();
+ }
+
+ return (keyDelta * ah1.size() + cancelDelta * cancelCount + between +
+ keyCharacter.getWidth() / 4);
+}
+
+int NotePixmapFactory::getTextWidth(const Text &text) const
+{
+ QFontMetrics metrics(getTextFont(text));
+ return metrics.boundingRect(strtoqstr(text.getText())).width() + 4;
+}
+
+}
diff --git a/src/gui/editors/notation/NotePixmapFactory.h b/src/gui/editors/notation/NotePixmapFactory.h
new file mode 100644
index 0000000..14b4773
--- /dev/null
+++ b/src/gui/editors/notation/NotePixmapFactory.h
@@ -0,0 +1,358 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
+
+/*
+ Rosegarden
+ A MIDI and audio sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_NOTEPIXMAPFACTORY_H_
+#define _RG_NOTEPIXMAPFACTORY_H_
+
+#include "base/NotationTypes.h"
+#include <map>
+#include "NoteCharacter.h"
+#include <string>
+#include <qfont.h>
+#include <qfontmetrics.h>
+#include <qpixmap.h>
+#include <qpoint.h>
+#include "base/Event.h"
+#include "gui/editors/notation/NoteCharacterNames.h"
+
+
+class QPainter;
+class QCanvasPixmap;
+class QBitmap;
+
+
+namespace Rosegarden
+{
+
+namespace Guitar { class Fingering; }
+
+class TimeSignature;
+class Text;
+class NoteStyle;
+class NotePixmapParameters;
+class NoteFont;
+class NotePixmapPainter;
+class NotePixmapCache;
+class Clef;
+class TrackHeader;
+
+/**
+ * Generates QCanvasPixmaps for various notation items.
+ */
+
+class NotePixmapFactory
+{
+public:
+ NotePixmapFactory(std::string fontName = "", int size = -1);
+ NotePixmapFactory(const NotePixmapFactory &);
+ NotePixmapFactory &operator=(const NotePixmapFactory &);
+ ~NotePixmapFactory();
+
+ std::string getFontName() const;
+ int getSize() const;
+
+ void setSelected(bool selected) { m_selected = selected; }
+ bool isSelected() const { return m_selected; }
+
+ void setShaded(bool shaded) { m_shaded = shaded; }
+ bool isShaded() const { return m_shaded; }
+
+ void setNoteStyle(NoteStyle *style) { m_style = style; }
+ const NoteStyle *getNoteStyle() const { return m_style; }
+
+ // Display methods -- create canvas pixmaps:
+
+ QCanvasPixmap* makeNotePixmap(const NotePixmapParameters &parameters);
+ QCanvasPixmap* makeRestPixmap(const NotePixmapParameters &parameters);
+ 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 &parameters);
+
+ // Printing methods -- draw direct to a paint device:
+
+ void drawNote(const NotePixmapParameters &parameters,
+ QPainter &painter, int x, int y);
+ void drawRest(const NotePixmapParameters &parameters,
+ 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 &timesig) 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 &parameters,
+ QPainter *painter, int x, int y);
+ void drawRestAux(const NotePixmapParameters &parameters, 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 &params, int stemLength);
+ void drawMarks(bool isStemmed, const NotePixmapParameters &params, int stemLength);
+
+ void makeRoomForLegerLines(const NotePixmapParameters &params);
+ void drawLegerLines(const NotePixmapParameters &params);
+
+ void makeRoomForStemAndFlags(int flagCount, int stemLength,
+ const NotePixmapParameters &params,
+ QPoint &startPoint, QPoint &endPoint);
+ void drawFlags(int flagCount, const NotePixmapParameters &params,
+ const QPoint &startPoint, const QPoint &endPoint);
+ void drawStem(const NotePixmapParameters &params,
+ const QPoint &startPoint, const QPoint &endPoint,
+ int shortening);
+
+ void makeRoomForBeams(const NotePixmapParameters &params);
+ void drawBeams(const QPoint &, const NotePixmapParameters &params,
+ int beamCount);
+
+ void drawSlashes(const QPoint &, const NotePixmapParameters &params,
+ int slashCount);
+
+ void makeRoomForTuplingLine(const NotePixmapParameters &params);
+ void drawTuplingLine(const NotePixmapParameters &params);
+
+ void drawShallowLine(int x0, int y0, int x1, int y1, int thickness,
+ bool smooth);
+ void drawTie(bool above, int length, int shift);
+
+ void drawBracket(int length, bool left, bool curly, int x, int y);
+
+ QFont getTextFont(const Text &text) const;
+
+ QCanvasPixmap* makeAnnotationPixmap(const Text &text);
+ QCanvasPixmap* makeAnnotationPixmap(const Text &text, const bool isLilyPondDirective);
+
+ void createPixmapAndMask(int width, int height,
+ int maskWidth = -1,
+ int maskHeight = -1);
+ QCanvasPixmap* makeCanvasPixmap(QPoint hotspot, bool generateMask = false);
+
+ enum ColourType {
+ PlainColour,
+ QuantizedColour,
+ HighlightedColour,
+ TriggerColour,
+ OutRangeColour
+ };
+
+ /// draws selected/shaded status from m_selected/m_shaded:
+ NoteCharacter getCharacter(CharName name, ColourType type, bool inverted);
+
+ /// draws selected/shaded status from m_selected/m_shaded:
+ bool getCharacter(CharName name, NoteCharacter &ch, ColourType type, bool inverted);
+
+ void drawNoteHalo(int x, int y, int w, int h);
+
+ //--------------- Data members ---------------------------------
+
+ NoteFont *m_font;
+ NoteStyle *m_style;
+ bool m_selected;
+ bool m_shaded;
+
+ int m_noteBodyWidth, m_noteBodyHeight;
+ int m_left, m_right, m_above, m_below;
+ int m_borderX, m_borderY;
+
+ QFont m_tupletCountFont;
+ QFontMetrics m_tupletCountFontMetrics;
+
+ QFont m_textMarkFont;
+ QFontMetrics m_textMarkFontMetrics;
+
+ QFont m_fingeringFont;
+ QFontMetrics m_fingeringFontMetrics;
+
+ QFont m_timeSigFont;
+ QFontMetrics m_timeSigFontMetrics;
+
+ QFont m_bigTimeSigFont;
+ QFontMetrics m_bigTimeSigFontMetrics;
+
+ QFont m_ottavaFont;
+ QFontMetrics m_ottavaFontMetrics;
+
+ QFont m_clefOttavaFont;
+ QFontMetrics m_clefOttavaFontMetrics;
+
+ QFont m_trackHeaderFont;
+ QFontMetrics m_trackHeaderFontMetrics;
+
+ QFont m_trackHeaderBoldFont;
+ QFontMetrics m_trackHeaderBoldFontMetrics;
+
+ QPixmap *m_generatedPixmap;
+ QBitmap *m_generatedMask;
+
+ int m_generatedWidth;
+ int m_generatedHeight;
+ bool m_inPrinterMethod;
+
+ NotePixmapPainter *m_p;
+
+ mutable NotePixmapCache *m_dottedRestCache;
+
+ typedef std::map<const char *, QFont> TextFontCache;
+ mutable TextFontCache m_textFontCache;
+
+ static QPoint m_pointZero;
+};
+
+
+
+}
+
+#endif
diff --git a/src/gui/editors/notation/NotePixmapPainter.h b/src/gui/editors/notation/NotePixmapPainter.h
new file mode 100644
index 0000000..ed9d541
--- /dev/null
+++ b/src/gui/editors/notation/NotePixmapPainter.h
@@ -0,0 +1,148 @@
+
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
+
+/*
+ Rosegarden
+ A MIDI and audio sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_NOTEPIXMAPPAINTER_H_
+#define _RG_NOTEPIXMAPPAINTER_H_
+
+#include <qpainter.h>
+
+namespace Rosegarden {
+
+class NotePixmapPainter
+{
+ // Just a trivial class that instructs two painters to do the
+ // same thing (one for the pixmap, one for the mask). We only
+ // duplicate those methods we actually use in NotePixmapFactory
+
+public:
+ NotePixmapPainter() :
+ m_painter(&m_myPainter) { }
+
+ void beginExternal(QPainter *painter) {
+
+ m_externalPainter = painter;
+ m_useMask = false;
+
+ painter->setPen(QPen(Qt::black, 1, Qt::SolidLine,
+ Qt::RoundCap, Qt::RoundJoin));
+
+ if (m_externalPainter) {
+ m_painter = m_externalPainter;
+ } else {
+ m_painter = &m_myPainter;
+ }
+ }
+
+ bool begin(QPaintDevice *device, QPaintDevice *mask = 0, bool unclipped = false) {
+
+ m_externalPainter = 0;
+
+ if (mask) {
+ m_useMask = true;
+ m_maskPainter.begin(mask, unclipped);
+ } else {
+ m_useMask = false;
+ }
+
+ m_painter = &m_myPainter;
+ return m_painter->begin(device, unclipped);
+ }
+
+ bool end() {
+ if (m_useMask) m_maskPainter.end();
+ return m_painter->end();
+ }
+
+ QPainter &painter() {
+ return *m_painter;
+ }
+
+ QPainter &maskPainter() {
+ return m_maskPainter;
+ }
+
+ void drawPoint(int x, int y) {
+ m_painter->drawPoint(x, y);
+ if (m_useMask) m_maskPainter.drawPoint(x, y);
+ }
+
+ void drawLine(int x1, int y1, int x2, int y2) {
+ m_painter->drawLine(x1, y1, x2, y2);
+ if (m_useMask) m_maskPainter.drawLine(x1, y1, x2, y2);
+ }
+
+ void drawRect(int x, int y, int w, int h) {
+ m_painter->drawRect(x, y, w, h);
+ if (m_useMask) m_maskPainter.drawRect(x, y, w, h);
+ }
+
+ void drawArc(int x, int y, int w, int h, int a, int alen) {
+ m_painter->drawArc(x, y, w, h, a, alen);
+ if (m_useMask) m_maskPainter.drawArc(x, y, w, h, a, alen);
+ }
+
+ void drawPolygon(const QPointArray &a, bool winding = false,
+ int index = 0, int n = -1) {
+ m_painter->drawPolygon(a, winding, index, n);
+ if (m_useMask) m_maskPainter.drawPolygon(a, winding, index, n);
+ }
+
+ void drawPolyline(const QPointArray &a, int index = 0, int n = -1) {
+ m_painter->drawPolyline(a, index, n);
+ if (m_useMask) m_maskPainter.drawPolyline(a, index, n);
+ }
+
+ void drawPixmap(int x, int y, const QPixmap &pm,
+ int sx = 0, int sy = 0, int sw = -1, int sh = -1) {
+ m_painter->drawPixmap(x, y, pm, sx, sy, sw, sh);
+ if (m_useMask) m_maskPainter.drawPixmap(x, y, *(pm.mask()), sx, sy, sw, sh);
+ }
+
+ void drawText(int x, int y, const QString &string) {
+ m_painter->drawText(x, y, string);
+ if (m_useMask) m_maskPainter.drawText(x, y, string);
+ }
+
+ void drawNoteCharacter(int x, int y, const NoteCharacter &character) {
+ character.draw(m_painter, x, y);
+ if (m_useMask) character.drawMask(&m_maskPainter, x, y);
+ }
+
+ void drawEllipse(int x, int y, int w, int h) {
+ m_painter->drawEllipse(x, y, w, h);
+ if (m_useMask) m_maskPainter.drawEllipse(x, y, w, h);
+ }
+
+private:
+ bool m_useMask;
+ QPainter m_myPainter;
+ QPainter m_maskPainter;
+ QPainter *m_externalPainter;
+ QPainter *m_painter;
+};
+
+}
+
+#endif
diff --git a/src/gui/editors/notation/NotePixmapParameters.cpp b/src/gui/editors/notation/NotePixmapParameters.cpp
new file mode 100644
index 0000000..b6dd7fb
--- /dev/null
+++ b/src/gui/editors/notation/NotePixmapParameters.cpp
@@ -0,0 +1,151 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
+
+/*
+ Rosegarden
+ A MIDI and audio sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "NotePixmapParameters.h"
+
+#include "base/NotationTypes.h"
+
+
+namespace Rosegarden
+{
+
+NotePixmapParameters::NotePixmapParameters(Note::Type noteType,
+ int dots,
+ Accidental accidental) :
+ m_noteType(noteType),
+ m_dots(dots),
+ m_accidental(accidental),
+ m_cautionary(false),
+ m_shifted(false),
+ m_dotShifted(false),
+ m_accidentalShift(0),
+ m_drawFlag(true),
+ m_drawStem(true),
+ m_stemGoesUp(true),
+ m_stemLength( -1),
+ m_legerLines(0),
+ m_slashes(0),
+ m_selected(false),
+ m_highlighted(false),
+ m_quantized(false),
+ m_trigger(false),
+ m_onLine(false),
+ m_safeVertDistance(0),
+ m_restOutsideStave(false),
+ m_beamed(false),
+ m_nextBeamCount(0),
+ m_thisPartialBeams(false),
+ m_nextPartialBeams(false),
+ m_width(1),
+ m_gradient(0.0),
+ m_tupletCount(0),
+ m_tuplingLineY(0),
+ m_tuplingLineWidth(0),
+ m_tuplingLineGradient(0.0),
+ m_tied(false),
+ m_tieLength(0),
+ m_tiePositionExplicit(false),
+ m_tieAbove(false),
+ m_inRange(true)
+{
+ // nothing else
+}
+
+NotePixmapParameters::~NotePixmapParameters()
+{
+ // nothing to see here
+}
+
+void
+NotePixmapParameters::setMarks(const std::vector<Mark> &marks)
+{
+ m_marks.clear();
+ for (unsigned int i = 0; i < marks.size(); ++i)
+ m_marks.push_back(marks[i]);
+}
+
+void
+NotePixmapParameters::removeMarks()
+{
+ m_marks.clear();
+}
+
+std::vector<Rosegarden::Mark>
+NotePixmapParameters::getNormalMarks() const
+{
+ std::vector<Mark> marks;
+
+ for (std::vector<Mark>::const_iterator mi = m_marks.begin();
+ mi != m_marks.end(); ++mi) {
+
+ if (*mi == Marks::Pause ||
+ *mi == Marks::UpBow ||
+ *mi == Marks::DownBow ||
+ *mi == Marks::Trill ||
+ *mi == Marks::LongTrill ||
+ *mi == Marks::TrillLine ||
+ *mi == Marks::Turn ||
+ Marks::isFingeringMark(*mi))
+ continue;
+
+ marks.push_back(*mi);
+ }
+
+ return marks;
+}
+
+std::vector<Rosegarden::Mark>
+NotePixmapParameters::getAboveMarks() const
+{
+ std::vector<Mark> marks;
+
+ // fingerings before other marks
+
+ for (std::vector<Mark>::const_iterator mi = m_marks.begin();
+ mi != m_marks.end(); ++mi) {
+
+ if (Marks::isFingeringMark(*mi)) {
+ marks.push_back(*mi);
+ }
+ }
+
+ for (std::vector<Mark>::const_iterator mi = m_marks.begin();
+ mi != m_marks.end(); ++mi) {
+
+ if (*mi == Marks::Pause ||
+ *mi == Marks::UpBow ||
+ *mi == Marks::DownBow ||
+ *mi == Marks::Trill ||
+ *mi == Marks::LongTrill ||
+ *mi == Marks::TrillLine ||
+ *mi == Marks::Turn) {
+ marks.push_back(*mi);
+ }
+ }
+
+ return marks;
+}
+
+}
diff --git a/src/gui/editors/notation/NotePixmapParameters.h b/src/gui/editors/notation/NotePixmapParameters.h
new file mode 100644
index 0000000..f7bfee7
--- /dev/null
+++ b/src/gui/editors/notation/NotePixmapParameters.h
@@ -0,0 +1,161 @@
+
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
+
+/*
+ Rosegarden
+ A MIDI and audio sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_NOTEPIXMAPPARAMETERS_H_
+#define _RG_NOTEPIXMAPPARAMETERS_H_
+
+#include "base/NotationTypes.h"
+#include <vector>
+
+
+
+
+namespace Rosegarden
+{
+
+
+
+class NotePixmapParameters
+{
+public:
+ NotePixmapParameters(Note::Type noteType,
+ int dots,
+ Accidental accidental =
+ Accidentals::NoAccidental);
+ ~NotePixmapParameters();
+
+ void setNoteType(Note::Type type) { m_noteType = type; }
+ void setDots(int dots) { m_dots = dots; }
+ void setAccidental(Accidental acc) { m_accidental = acc; }
+
+ void setAccidentalCautionary(bool cautionary) { m_cautionary = cautionary; }
+ void setNoteHeadShifted(bool shifted) { m_shifted = shifted; }
+ void setNoteDotShifted(bool shifted) { m_dotShifted = shifted; }
+ void setAccidentalShift(int shift) { m_accidentalShift = shift; }
+ void setAccExtraShift(bool extra) { m_accidentalExtra = extra; }
+
+ void setDrawFlag(bool df) { m_drawFlag = df; }
+ void setDrawStem(bool ds) { m_drawStem = ds; }
+ void setStemGoesUp(bool up) { m_stemGoesUp = up; }
+ void setStemLength(int length) { m_stemLength = length; }
+ void setLegerLines(int lines) { m_legerLines = lines; }
+ void setSlashes(int slashes) { m_slashes = slashes; }
+ void setRestOutside(bool os) { m_restOutsideStave = os; }
+
+ void setSelected(bool selected) { m_selected = selected; }
+ void setHighlighted(bool highlighted) { m_highlighted = highlighted;}
+ void setQuantized(bool quantized) { m_quantized = quantized; }
+ void setTrigger(bool trigger) { m_trigger = trigger; }
+ void setIsOnLine(bool isOnLine) { m_onLine = isOnLine; }
+ void setSafeVertDistance(int safe) { m_safeVertDistance = safe; }
+
+ void setBeamed(bool beamed) { m_beamed = beamed; }
+ void setNextBeamCount(int tc) { m_nextBeamCount = tc; }
+ void setThisPartialBeams(bool pt) { m_thisPartialBeams = pt; }
+ void setNextPartialBeams(bool pt) { m_nextPartialBeams = pt; }
+ void setWidth(int width) { m_width = width; }
+ void setGradient(double gradient) { m_gradient = gradient; }
+
+ void setTupletCount(int count) { m_tupletCount = count; }
+ void setTuplingLineY(int y) { m_tuplingLineY = y; }
+ void setTuplingLineWidth(int width) { m_tuplingLineWidth = width; }
+ void setTuplingLineGradient(double g) { m_tuplingLineGradient = g; }
+ void setTuplingLineFollowsBeam(bool b){ m_tuplingLineFollowsBeam = b; }
+
+ void setTied(bool tied) { m_tied = tied; }
+ void setTieLength(int tieLength) { m_tieLength = tieLength; }
+
+ void setTiePosition(bool expl, bool above) {
+ m_tiePositionExplicit = expl;
+ m_tieAbove = above;
+ }
+
+ void setMarks(const std::vector<Mark> &marks);
+ void removeMarks();
+
+ void setInRange(bool inRange) { m_inRange = inRange; }
+
+ std::vector<Mark> getNormalMarks() const;
+ std::vector<Mark> getAboveMarks() const; // bowings, pause etc
+
+
+private:
+ friend class NotePixmapFactory;
+
+ //--------------- Data members ---------------------------------
+
+ Note::Type m_noteType;
+ int m_dots;
+ Accidental m_accidental;
+
+ bool m_cautionary;
+ bool m_shifted;
+ bool m_dotShifted;
+ int m_accidentalShift;
+ bool m_accidentalExtra;
+ bool m_drawFlag;
+ bool m_drawStem;
+ bool m_stemGoesUp;
+ int m_stemLength;
+ int m_legerLines;
+ int m_slashes;
+ bool m_selected;
+ bool m_highlighted;
+ bool m_quantized;
+ bool m_trigger;
+ bool m_onLine;
+ int m_safeVertDistance;
+ bool m_restOutsideStave;
+
+ bool m_beamed;
+ int m_nextBeamCount;
+ bool m_thisPartialBeams;
+ bool m_nextPartialBeams;
+ int m_width;
+ double m_gradient;
+
+ int m_tupletCount;
+ int m_tuplingLineY;
+ int m_tuplingLineWidth;
+ double m_tuplingLineGradient;
+ bool m_tuplingLineFollowsBeam;
+
+ bool m_tied;
+ int m_tieLength;
+ bool m_tiePositionExplicit;
+ bool m_tieAbove;
+
+ bool m_inRange;
+
+ std::vector<Mark> m_marks;
+};
+
+
+class NotePixmapPainter;
+
+
+}
+
+#endif
diff --git a/src/gui/editors/notation/NoteStyle.cpp b/src/gui/editors/notation/NoteStyle.cpp
new file mode 100644
index 0000000..0b3332d
--- /dev/null
+++ b/src/gui/editors/notation/NoteStyle.cpp
@@ -0,0 +1,485 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
+
+/*
+ Rosegarden
+ A MIDI and audio sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_NOTESTYLE_H_
+#define _RG_NOTESTYLE_H_
+
+#include "base/NotationTypes.h"
+#include <map>
+#include "NoteCharacterNames.h"
+#include <string>
+#include <utility>
+#include "gui/editors/notation/NoteCharacterNames.h"
+
+
+class Mark;
+class Accidental;
+
+
+namespace Rosegarden
+{
+
+class Clef;
+
+typedef std::string NoteStyleName;
+
+
+class NoteStyle
+{
+public:
+ virtual ~NoteStyle();
+
+ typedef std::string NoteHeadShape;
+
+ static const NoteHeadShape AngledOval;
+ static const NoteHeadShape LevelOval;
+ static const NoteHeadShape Breve;
+ static const NoteHeadShape Cross;
+ static const NoteHeadShape TriangleUp;
+ static const NoteHeadShape TriangleDown;
+ static const NoteHeadShape Diamond;
+ static const NoteHeadShape Rectangle;
+ static const NoteHeadShape CustomCharName;
+ static const NoteHeadShape Number;
+
+ enum HFixPoint { Normal, Central, Reversed };
+ enum VFixPoint { Near, Middle, Far };
+
+ NoteStyleName getName() const { return m_name; }
+
+ NoteHeadShape getShape (Note::Type);
+ bool isFilled (Note::Type);
+ bool hasStem (Note::Type);
+ int getFlagCount (Note::Type);
+ int getSlashCount(Note::Type);
+
+ typedef std::pair<CharName, bool> CharNameRec; // bool is "inverted"
+ CharNameRec getNoteHeadCharName(Note::Type);
+
+ CharName getRestCharName(Note::Type, bool restOutsideStave);
+ CharName getPartialFlagCharName(bool final);
+ CharName getFlagCharName(int flagCount);
+ CharName getAccidentalCharName(const Accidental &);
+ CharName getMarkCharName(const Mark &);
+ CharName getClefCharName(const Clef &);
+ CharName getTimeSignatureDigitName(int digit);
+
+ void setBaseStyle (NoteStyleName name);
+ void setShape (Note::Type, NoteHeadShape);
+ void setCharName (Note::Type, CharName);
+ void setFilled (Note::Type, bool);
+ void setStem (Note::Type, bool);
+ void setFlagCount (Note::Type, int);
+ void setSlashCount(Note::Type, int);
+
+ void getStemFixPoints(Note::Type, HFixPoint &, VFixPoint &);
+ void setStemFixPoints(Note::Type, HFixPoint, VFixPoint);
+
+protected:
+ struct NoteDescription {
+ NoteHeadShape shape; // if CustomCharName, use charName
+ CharName charName; // only used if shape == CustomCharName
+ bool filled;
+ bool stem;
+ int flags;
+ int slashes;
+ HFixPoint hfix;
+ VFixPoint vfix;
+
+ NoteDescription() :
+ shape(AngledOval), charName(NoteCharacterNames::UNKNOWN),
+ filled(true), stem(true), flags(0), slashes(0),
+ hfix(Normal), vfix(Middle) { }
+
+ NoteDescription(NoteHeadShape _shape, CharName _charName,
+ bool _filled, bool _stem, int _flags, int _slashes,
+ HFixPoint _hfix, VFixPoint _vfix) :
+ shape(_shape), charName(_charName),
+ filled(_filled), stem(_stem), flags(_flags), slashes(_slashes),
+ hfix(_hfix), vfix(_vfix) { }
+ };
+
+ typedef std::map<Note::Type, NoteDescription> NoteDescriptionMap;
+
+ NoteDescriptionMap m_notes;
+ NoteStyle *m_baseStyle;
+ NoteStyleName m_name;
+
+ void checkDescription(Note::Type type);
+
+protected: // for use by NoteStyleFileReader
+ NoteStyle(NoteStyleName name) : m_baseStyle(0), m_name(name) { }
+ friend class NoteStyleFileReader;
+};
+
+
+
+
+}
+
+#endif
diff --git a/src/gui/editors/notation/NoteStyleFactory.cpp b/src/gui/editors/notation/NoteStyleFactory.cpp
new file mode 100644
index 0000000..d4a8be8
--- /dev/null
+++ b/src/gui/editors/notation/NoteStyleFactory.cpp
@@ -0,0 +1,124 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
+
+/*
+ Rosegarden
+ A MIDI and audio sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "NoteStyleFactory.h"
+
+#include <kstddirs.h>
+#include "misc/Strings.h"
+#include "base/Event.h"
+#include "base/Exception.h"
+#include "NotationProperties.h"
+#include "NoteStyle.h"
+#include "NoteStyleFileReader.h"
+#include <kglobal.h>
+#include <qdir.h>
+#include <qfileinfo.h>
+#include <qstring.h>
+#include <qstringlist.h>
+
+
+namespace Rosegarden
+{
+
+const NoteStyleName NoteStyleFactory::DefaultStyle = "Classical";
+
+std::vector<NoteStyleName>
+NoteStyleFactory::getAvailableStyleNames()
+{
+ std::vector<NoteStyleName> names;
+
+ QString styleDir = KGlobal::dirs()->findResource("appdata", "styles/");
+ QDir dir(styleDir);
+ if (!dir.exists()) {
+ std::cerr << "NoteStyle::getAvailableStyleNames: directory \"" << styleDir
+ << "\" not found" << std::endl;
+ return names;
+ }
+
+ dir.setFilter(QDir::Files | QDir::Readable);
+ QStringList files = dir.entryList();
+ bool foundDefault = false;
+
+ for (QStringList::Iterator i = files.begin(); i != files.end(); ++i) {
+ if ((*i).length() > 4 && (*i).right(4) == ".xml") {
+ QFileInfo fileInfo(QString("%1/%2").arg(styleDir).arg(*i));
+ if (fileInfo.exists() && fileInfo.isReadable()) {
+ std::string styleName = qstrtostr((*i).left((*i).length() - 4));
+ if (styleName == DefaultStyle)
+ foundDefault = true;
+ names.push_back(styleName);
+ }
+ }
+ }
+
+ if (!foundDefault) {
+ std::cerr << "NoteStyleFactory::getAvailableStyleNames: WARNING: Default style name \"" << DefaultStyle << "\" not found" << std::endl;
+ }
+
+ return names;
+}
+
+NoteStyle *
+NoteStyleFactory::getStyle(NoteStyleName name)
+{
+ StyleMap::iterator i = m_styles.find(name);
+
+ if (i == m_styles.end()) {
+
+ try {
+ NoteStyle *newStyle = NoteStyleFileReader(name).getStyle();
+ m_styles[name] = newStyle;
+ return newStyle;
+
+ } catch (NoteStyleFileReader::StyleFileReadFailed f) {
+ std::cerr
+ << "NoteStyleFactory::getStyle: Style file read failed: "
+ << f.getMessage() << std::endl;
+ throw StyleUnavailable("Style file read failed: " +
+ f.getMessage());
+ }
+
+ } else {
+ return i->second;
+ }
+}
+
+NoteStyle *
+NoteStyleFactory::getStyleForEvent(Event *event)
+{
+ NoteStyleName styleName;
+ if (event->get
+ <String>(NotationProperties::NOTE_STYLE, styleName)) {
+ return getStyle(styleName);
+ }
+ else {
+ return getStyle(DefaultStyle);
+ }
+}
+
+NoteStyleFactory::StyleMap NoteStyleFactory::m_styles;
+
+
+}
diff --git a/src/gui/editors/notation/NoteStyleFactory.h b/src/gui/editors/notation/NoteStyleFactory.h
new file mode 100644
index 0000000..795537d
--- /dev/null
+++ b/src/gui/editors/notation/NoteStyleFactory.h
@@ -0,0 +1,61 @@
+
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
+
+/*
+ Rosegarden
+ A MIDI and audio sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_NOTESTYLEFACTORY_H_
+#define _RG_NOTESTYLEFACTORY_H_
+
+#include "base/Exception.h"
+#include <map>
+#include <vector>
+#include "NoteStyle.h"
+
+
+namespace Rosegarden
+{
+
+class NoteStyle;
+class Event;
+
+class NoteStyleFactory
+{
+public:
+ static std::vector<NoteStyleName> getAvailableStyleNames();
+ static const NoteStyleName DefaultStyle;
+
+ static NoteStyle *getStyle(NoteStyleName name);
+ static NoteStyle *getStyleForEvent(Event *event);
+
+ typedef Exception StyleUnavailable;
+
+private:
+ typedef std::map<std::string, NoteStyle *> StyleMap;
+ static StyleMap m_styles;
+};
+
+
+
+}
+
+#endif
diff --git a/src/gui/editors/notation/NoteStyleFileReader.cpp b/src/gui/editors/notation/NoteStyleFileReader.cpp
new file mode 100644
index 0000000..b3f3464
--- /dev/null
+++ b/src/gui/editors/notation/NoteStyleFileReader.cpp
@@ -0,0 +1,193 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
+
+/*
+ Rosegarden
+ A MIDI and audio sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#include "NoteStyleFileReader.h"
+
+#include <string>
+#include "NoteStyle.h"
+#include <qfileinfo.h>
+#include <qdir.h>
+
+#include <kglobal.h>
+#include <kstddirs.h>
+#include <klocale.h>
+
+#include "misc/Strings.h"
+#include "NotationStrings.h"
+#include "misc/Debug.h"
+
+namespace Rosegarden {
+
+
+NoteStyleFileReader::NoteStyleFileReader(std::string name) :
+ m_style(new NoteStyle(name)),
+ m_haveNote(false)
+{
+ QString styleDirectory =
+ KGlobal::dirs()->findResource("appdata", "styles/");
+
+ QString styleFileName =
+ QString("%1/%2.xml").arg(styleDirectory).arg(strtoqstr(name));
+
+ QFileInfo fileInfo(styleFileName);
+
+ if (!fileInfo.isReadable()) {
+ throw StyleFileReadFailed
+ (qstrtostr(i18n("Can't open style file %1").arg(styleFileName)));
+ }
+
+ QFile styleFile(styleFileName);
+
+ QXmlInputSource source(styleFile);
+ QXmlSimpleReader reader;
+ reader.setContentHandler(this);
+ reader.setErrorHandler(this);
+ bool ok = reader.parse(source);
+ styleFile.close();
+
+ if (!ok) {
+ throw StyleFileReadFailed(qstrtostr(m_errorString));
+ }
+}
+
+bool
+NoteStyleFileReader::startElement(const QString &, const QString &,
+ const QString &qName,
+ const QXmlAttributes &attributes)
+{
+ QString lcName = qName.lower();
+
+ if (lcName == "rosegarden-note-style") {
+
+ QString s = attributes.value("base-style");
+ if (s) m_style->setBaseStyle(qstrtostr(s));
+
+ } else if (lcName == "note") {
+
+ m_haveNote = true;
+
+ QString s = attributes.value("type");
+ if (!s) {
+ m_errorString = i18n("type is a required attribute of note");
+ return false;
+ }
+
+ try {
+ Note::Type type = NotationStrings::getNoteForName(s).getNoteType();
+ if (!setFromAttributes(type, attributes)) return false;
+
+ } catch (NotationStrings::MalformedNoteName n) {
+ m_errorString = i18n("Unrecognised note name %1").arg(s);
+ return false;
+ }
+
+ } else if (lcName == "global") {
+
+ if (m_haveNote) {
+ m_errorString = i18n("global element must precede note elements");
+ return false;
+ }
+
+ for (Note::Type type = Note::Shortest; type <= Note::Longest; ++type) {
+ if (!setFromAttributes(type, attributes)) return false;
+ }
+ }
+
+ return true;
+}
+
+
+bool
+NoteStyleFileReader::setFromAttributes(Note::Type type,
+ const QXmlAttributes &attributes)
+{
+ QString s;
+ bool haveShape = false;
+
+ s = attributes.value("shape");
+ if (s) {
+ m_style->setShape(type, qstrtostr(s.lower()));
+ haveShape = true;
+ }
+
+ s = attributes.value("charname");
+ if (s) {
+ if (haveShape) {
+ m_errorString = i18n("global and note elements may have shape "
+ "or charname attribute, but not both");
+ return false;
+ }
+ m_style->setShape(type, NoteStyle::CustomCharName);
+ m_style->setCharName(type, qstrtostr(s));
+ }
+
+ s = attributes.value("filled");
+ if (s) m_style->setFilled(type, s.lower() == "true");
+
+ s = attributes.value("stem");
+ if (s) m_style->setStem(type, s.lower() == "true");
+
+ s = attributes.value("flags");
+ if (s) m_style->setFlagCount(type, s.toInt());
+
+ s = attributes.value("slashes");
+ if (s) m_style->setSlashCount(type, s.toInt());
+
+ NoteStyle::HFixPoint hfix;
+ NoteStyle::VFixPoint vfix;
+ m_style->getStemFixPoints(type, hfix, vfix);
+ bool haveHFix = false;
+ bool haveVFix = false;
+
+ s = attributes.value("hfixpoint");
+ if (s) {
+ s = s.lower();
+ haveHFix = true;
+ if (s == "normal") hfix = NoteStyle::Normal;
+ else if (s == "central") hfix = NoteStyle::Central;
+ else if (s == "reversed") hfix = NoteStyle::Reversed;
+ else haveHFix = false;
+ }
+
+ s = attributes.value("vfixpoint");
+ if (s) {
+ s = s.lower();
+ haveVFix = true;
+ if (s == "near") vfix = NoteStyle::Near;
+ else if (s == "middle") vfix = NoteStyle::Middle;
+ else if (s == "far") vfix = NoteStyle::Far;
+ else haveVFix = false;
+ }
+
+ if (haveHFix || haveVFix) {
+ m_style->setStemFixPoints(type, hfix, vfix);
+ // otherwise they inherit from base style, so avoid setting here
+ }
+
+ return true;
+}
+
+
+}
+
diff --git a/src/gui/editors/notation/NoteStyleFileReader.h b/src/gui/editors/notation/NoteStyleFileReader.h
new file mode 100644
index 0000000..d3dfbbe
--- /dev/null
+++ b/src/gui/editors/notation/NoteStyleFileReader.h
@@ -0,0 +1,59 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
+
+/*
+ Rosegarden
+ A MIDI and audio sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_NOTESTYLEFILEREADER_H_
+#define _RG_NOTESTYLEFILEREADER_H_
+
+#include <qxml.h>
+
+#include "NoteStyle.h"
+
+namespace Rosegarden {
+
+class NoteStyleFileReader : public QXmlDefaultHandler
+{
+public:
+ NoteStyleFileReader(NoteStyleName name);
+
+ typedef Rosegarden::Exception StyleFileReadFailed;
+
+ NoteStyle *getStyle() { return m_style; }
+
+ // Xml handler methods:
+
+ virtual bool startElement
+ (const QString& namespaceURI, const QString& localName,
+ const QString& qName, const QXmlAttributes& atts);
+
+private:
+ bool setFromAttributes(Note::Type type, const QXmlAttributes &attributes);
+
+ QString m_errorString;
+ NoteStyle *m_style;
+ bool m_haveNote;
+};
+
+}
+
+#endif
diff --git a/src/gui/editors/notation/RestInserter.cpp b/src/gui/editors/notation/RestInserter.cpp
new file mode 100644
index 0000000..399cf2d
--- /dev/null
+++ b/src/gui/editors/notation/RestInserter.cpp
@@ -0,0 +1,150 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
+
+/*
+ Rosegarden
+ A MIDI and audio sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "RestInserter.h"
+
+#include <klocale.h>
+#include "base/Event.h"
+#include "base/NotationTypes.h"
+#include "base/BaseProperties.h"
+#include "base/Segment.h"
+#include "commands/notation/NoteInsertionCommand.h"
+#include "commands/notation/RestInsertionCommand.h"
+#include "commands/notation/TupletCommand.h"
+#include "gui/general/EditTool.h"
+#include "NotationStrings.h"
+#include "NotationTool.h"
+#include "NotationView.h"
+#include "NoteInserter.h"
+#include "NotePixmapFactory.h"
+#include <kaction.h>
+#include <kcommand.h>
+#include <qiconset.h>
+#include <qregexp.h>
+#include <qstring.h>
+
+
+namespace Rosegarden
+{
+
+using namespace BaseProperties;
+
+RestInserter::RestInserter(NotationView* view)
+ : NoteInserter("RestInserter", view)
+{
+ QIconSet icon;
+
+ icon = QIconSet
+ (NotePixmapFactory::toQPixmap(NotePixmapFactory::
+ makeToolbarPixmap("dotted-rest-crotchet")));
+ new KToggleAction(i18n("Dotted rest"), icon, 0, this,
+ SLOT(slotToggleDot()), actionCollection(),
+ "toggle_dot");
+
+ icon = QIconSet(NotePixmapFactory::toQPixmap(NotePixmapFactory::
+ makeToolbarPixmap("select")));
+ new KAction(i18n("Switch to Select Tool"), icon, 0, this,
+ SLOT(slotSelectSelected()), actionCollection(),
+ "select");
+
+ new KAction(i18n("Switch to Erase Tool"), "eraser", 0, this,
+ SLOT(slotEraseSelected()), actionCollection(),
+ "erase");
+
+ icon = QIconSet
+ (NotePixmapFactory::toQPixmap(NotePixmapFactory::
+ makeToolbarPixmap("crotchet")));
+ new KAction(i18n("Switch to Inserting Notes"), icon, 0, this,
+ SLOT(slotNotesSelected()), actionCollection(),
+ "notes");
+
+ createMenu("restinserter.rc");
+}
+
+void
+RestInserter::showPreview()
+{
+ // no preview available for now
+}
+
+Event *
+RestInserter::doAddCommand(Segment &segment, timeT time, timeT endTime,
+ const Note &note, 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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_RESTINSERTER_H_
+#define _RG_RESTINSERTER_H_
+
+#include "NoteInserter.h"
+#include <qstring.h>
+#include "base/Event.h"
+
+
+
+
+namespace Rosegarden
+{
+
+class Segment;
+class Note;
+class NotationView;
+class Event;
+
+
+/**
+ * This tool will insert rests on mouse click events
+ */
+class RestInserter : public NoteInserter
+{
+ Q_OBJECT
+
+ friend class NotationToolBox;
+
+public:
+
+ static const QString ToolName;
+
+protected:
+ RestInserter(NotationView*);
+
+ virtual Event *doAddCommand(Segment &,
+ timeT time,
+ timeT endTime,
+ const Note &,
+ int pitch, Accidental);
+ virtual void showPreview();
+
+protected slots:
+ void slotToggleDot();
+ void slotNotesSelected();
+};
+
+
+}
+
+#endif
diff --git a/src/gui/editors/notation/SystemFont.cpp b/src/gui/editors/notation/SystemFont.cpp
new file mode 100644
index 0000000..71f0ce7
--- /dev/null
+++ b/src/gui/editors/notation/SystemFont.cpp
@@ -0,0 +1,165 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
+
+/*
+ Rosegarden
+ A MIDI and audio sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "SystemFont.h"
+#include "SystemFontQt.h"
+#include "SystemFontXft.h"
+
+#include "misc/Debug.h"
+
+#include <kstddirs.h>
+#include "NoteFontMap.h"
+#include <qfont.h>
+#include <qfontinfo.h>
+#include <qpixmap.h>
+#include <qstring.h>
+
+
+namespace Rosegarden
+{
+
+SystemFont *
+SystemFont::loadSystemFont(const SystemFontSpec &spec)
+{
+ QString name = spec.first;
+ int size = spec.second;
+
+ NOTATION_DEBUG << "SystemFont::loadSystemFont: name is " << name << ", size " << size << endl;
+
+ if (name == "DEFAULT") {
+ QFont font;
+ font.setPixelSize(size);
+ return new SystemFontQt(font);
+ }
+
+#ifdef HAVE_XFT
+
+ FcPattern *pattern, *match;
+ FcResult result;
+ FcChar8 *matchFamily;
+ XftFont *xfont = 0;
+
+ Display *dpy = QPaintDevice::x11AppDisplay();
+ static bool haveFcDirectory = false;
+
+ if (!dpy) {
+ std::cerr << "SystemFont::loadSystemFont[Xft]: Xft support requested but no X11 display available!" << std::endl;
+ goto qfont;
+ }
+
+ if (!haveFcDirectory) {
+ QString fontDir = KGlobal::dirs()->findResource("appdata", "fonts/");
+ if (!FcConfigAppFontAddDir(FcConfigGetCurrent(),
+ (const FcChar8 *)fontDir.latin1())) {
+ NOTATION_DEBUG << "SystemFont::loadSystemFont[Xft]: Failed to add font directory " << fontDir << " to fontconfig, continuing without it" << endl;
+ }
+ haveFcDirectory = true;
+ }
+
+ pattern = FcPatternCreate();
+ FcPatternAddString(pattern, FC_FAMILY, (FcChar8 *)name.latin1());
+ FcPatternAddInteger(pattern, FC_PIXEL_SIZE, size);
+ FcConfigSubstitute(FcConfigGetCurrent(), pattern, FcMatchPattern);
+
+ result = FcResultMatch;
+ match = FcFontMatch(FcConfigGetCurrent(), pattern, &result);
+ FcPatternDestroy(pattern);
+
+ if (!match || result != FcResultMatch) {
+ NOTATION_DEBUG << "SystemFont::loadSystemFont[Xft]: No match for font "
+ << name << " (result is " << result
+ << "), falling back on QFont" << endl;
+ if (match)
+ FcPatternDestroy(match);
+ goto qfont;
+ }
+
+ FcPatternGetString(match, FC_FAMILY, 0, &matchFamily);
+ NOTATION_DEBUG << "SystemFont::loadSystemFont[Xft]: match family is "
+ << (char *)matchFamily << endl;
+
+ if (QString((char *)matchFamily).lower() != name.lower()) {
+ NOTATION_DEBUG << "SystemFont::loadSystemFont[Xft]: Wrong family returned, falling back on QFont" << endl;
+ FcPatternDestroy(match);
+ goto qfont;
+ }
+
+ xfont = XftFontOpenPattern(dpy, match);
+ if (!xfont) {
+ FcPatternDestroy(match);
+ NOTATION_DEBUG << "SystemFont::loadSystemFont[Xft]: Unable to load font "
+ << name << " via Xft, falling back on QFont" << endl;
+ goto qfont;
+ }
+
+ NOTATION_DEBUG << "SystemFont::loadSystemFont[Xft]: successfully loaded font "
+ << name << " through Xft" << endl;
+
+ return new SystemFontXft(dpy, xfont);
+
+
+qfont:
+
+#endif
+
+ QFont qfont(name, size, QFont::Normal);
+ qfont.setPixelSize(size);
+
+ QFontInfo info(qfont);
+
+ NOTATION_DEBUG << "SystemFont::loadSystemFont[Qt]: have family " << info.family() << " (exactMatch " << info.exactMatch() << ")" << endl;
+
+ // return info.exactMatch();
+
+ // The Qt documentation says:
+ //
+ // bool QFontInfo::exactMatch() const
+ // Returns TRUE if the matched window system font is exactly the
+ // same as the one specified by the font; otherwise returns FALSE.
+ //
+ // My arse. I specify "feta", I get "Verdana", and exactMatch
+ // returns true. Uh huh.
+ //
+ // UPDATE: in newer versions of Qt, I specify "fughetta", I get
+ // "Fughetta [macromedia]", and exactMatch returns false. Just as
+ // useless, but in a different way.
+
+ QString family = info.family().lower();
+
+ if (family == name.lower())
+ return new SystemFontQt(qfont);
+ else {
+ int bracket = family.find(" [");
+ if (bracket > 1)
+ family = family.left(bracket);
+ if (family == name.lower())
+ return new SystemFontQt(qfont);
+ }
+
+ NOTATION_DEBUG << "SystemFont::loadSystemFont[Qt]: Wrong family returned, failing" << endl;
+ return 0;
+}
+
+}
diff --git a/src/gui/editors/notation/SystemFont.h b/src/gui/editors/notation/SystemFont.h
new file mode 100644
index 0000000..0acc2dd
--- /dev/null
+++ b/src/gui/editors/notation/SystemFont.h
@@ -0,0 +1,63 @@
+
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
+
+/*
+ Rosegarden
+ A MIDI and audio sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_SYSTEMFONT_H_
+#define _RG_SYSTEMFONT_H_
+
+#include <qpixmap.h>
+#include "gui/editors/notation/NoteCharacterNames.h"
+
+
+class SystemFontSpec;
+
+
+namespace Rosegarden
+{
+
+typedef std::pair<QString, int> SystemFontSpec;
+
+
+class SystemFont
+{
+public:
+ enum Strategy {
+ PreferGlyphs, PreferCodes, OnlyGlyphs, OnlyCodes
+ };
+
+ virtual QPixmap renderChar(CharName charName,
+ int glyph, int code,
+ Strategy strategy,
+ bool &success) = 0;
+
+ static SystemFont *loadSystemFont(const SystemFontSpec &spec);
+};
+
+
+// Helper class for looking up information about a font
+
+
+}
+
+#endif
diff --git a/src/gui/editors/notation/SystemFontQt.cpp b/src/gui/editors/notation/SystemFontQt.cpp
new file mode 100644
index 0000000..f9c99b1
--- /dev/null
+++ b/src/gui/editors/notation/SystemFontQt.cpp
@@ -0,0 +1,78 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
+
+/*
+ Rosegarden
+ A MIDI and audio sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#include "SystemFontQt.h"
+
+#include "misc/Debug.h"
+#include "gui/general/PixmapFunctions.h"
+
+#include <qfont.h>
+#include <qfontmetrics.h>
+#include <qpainter.h>
+#include <qpixmap.h>
+
+namespace Rosegarden {
+
+QPixmap
+SystemFontQt::renderChar(CharName charName, int glyph, int code,
+ Strategy strategy, bool &success)
+{
+ success = false;
+
+ if (strategy == OnlyGlyphs) {
+ NOTATION_DEBUG << "SystemFontQt::renderChar: OnlyGlyphs strategy not supported by Qt renderer, can't render character " << charName.getName() << " (glyph " << glyph << ")" << endl;
+ return QPixmap();
+ }
+
+ if (code < 0) {
+ NOTATION_DEBUG << "SystemFontQt::renderChar: Can't render using Qt with only glyph value (" << glyph << ") for character " << charName.getName() << ", need a code point" << endl;
+ return QPixmap();
+ }
+
+ QFontMetrics metrics(m_font);
+ QChar qc(code);
+
+ QPixmap map;
+ map = QPixmap(metrics.width(qc), metrics.height());
+
+ map.fill();
+ QPainter painter;
+ painter.begin(&map);
+ painter.setFont(m_font);
+ painter.setPen(Qt::black);
+
+ NOTATION_DEBUG << "NoteFont: Drawing character code "
+ << code << " for " << charName.getName()
+ << " using QFont" << endl;
+
+ painter.drawText(0, metrics.ascent(), qc);
+
+ painter.end();
+ map.setMask(PixmapFunctions::generateMask(map, Qt::white.rgb()));
+
+ success = true;
+ return map;
+}
+
+}
diff --git a/src/gui/editors/notation/SystemFontQt.h b/src/gui/editors/notation/SystemFontQt.h
new file mode 100644
index 0000000..ea8f5f2
--- /dev/null
+++ b/src/gui/editors/notation/SystemFontQt.h
@@ -0,0 +1,49 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
+
+/*
+ Rosegarden
+ A MIDI and audio sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_SYSTEMFONTQT_H_
+#define _RG_SYSTEMFONTQT_H_
+
+#include "SystemFont.h"
+
+#include <qfont.h>
+
+namespace Rosegarden {
+
+class SystemFontQt : public SystemFont
+{
+public:
+ SystemFontQt(QFont &font) : m_font(font) { }
+ virtual ~SystemFontQt() { }
+
+ virtual QPixmap renderChar(CharName charName, int glyph, int code,
+ Strategy strategy, bool &success);
+
+private:
+ QFont m_font;
+};
+
+}
+
+#endif
diff --git a/src/gui/editors/notation/SystemFontXft.cpp b/src/gui/editors/notation/SystemFontXft.cpp
new file mode 100644
index 0000000..ce42f61
--- /dev/null
+++ b/src/gui/editors/notation/SystemFontXft.cpp
@@ -0,0 +1,193 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
+
+/*
+ Rosegarden
+ A MIDI and audio sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#include "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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_SYSTEMFONTXFT_H_
+#define _RG_SYSTEMFONTXFT_H_
+
+#ifdef HAVE_XFT
+
+#include "SystemFont.h"
+
+#include <ft2build.h>
+#include FT_FREETYPE_H
+#include FT_OUTLINE_H
+#include FT_GLYPH_H
+#include <X11/Xft/Xft.h>
+
+namespace Rosegarden {
+
+class SystemFontXft : public SystemFont
+{
+public:
+ SystemFontXft(Display *dpy, XftFont *font) : m_dpy(dpy), m_font(font) { }
+ virtual ~SystemFontXft() { if (m_font) XftFontClose(m_dpy, m_font); }
+
+ virtual QPixmap renderChar(CharName charName, int glyph, int code,
+ Strategy strategy, bool &success);
+
+private:
+ Display *m_dpy;
+ XftFont *m_font;
+};
+
+}
+
+#endif
+
+#endif
diff --git a/src/gui/editors/notation/TextInserter.cpp b/src/gui/editors/notation/TextInserter.cpp
new file mode 100644
index 0000000..aa8e1ff
--- /dev/null
+++ b/src/gui/editors/notation/TextInserter.cpp
@@ -0,0 +1,169 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
+
+/*
+ Rosegarden
+ A MIDI and audio sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "TextInserter.h"
+
+#include <klocale.h>
+#include "base/Event.h"
+#include "base/Exception.h"
+#include "base/NotationTypes.h"
+#include "base/ViewElement.h"
+#include "commands/notation/EraseEventCommand.h"
+#include "commands/notation/TextInsertionCommand.h"
+#include "gui/dialogs/TextEventDialog.h"
+#include "gui/general/EditTool.h"
+#include "gui/general/LinedStaff.h"
+#include "NotationTool.h"
+#include "NotationView.h"
+#include "NotePixmapFactory.h"
+#include "NotationElement.h"
+#include <kaction.h>
+#include <qdialog.h>
+#include <qiconset.h>
+#include <qstring.h>
+
+
+namespace Rosegarden
+{
+
+TextInserter::TextInserter(NotationView* view)
+ : NotationTool("TextInserter", view),
+ m_text("", Text::Dynamic)
+{
+ QIconSet icon = QIconSet(NotePixmapFactory::toQPixmap(NotePixmapFactory::
+ makeToolbarPixmap("select")));
+ new KAction(i18n("Switch to Select Tool"), icon, 0, this,
+ SLOT(slotSelectSelected()), actionCollection(),
+ "select");
+
+ new KAction(i18n("Switch to Erase Tool"), "eraser", 0, this,
+ SLOT(slotEraseSelected()), actionCollection(),
+ "erase");
+
+ icon = QIconSet
+ (NotePixmapFactory::toQPixmap(NotePixmapFactory::
+ makeToolbarPixmap("crotchet")));
+ new KAction(i18n("Switch to Inserting Notes"), icon, 0, this,
+ SLOT(slotNotesSelected()), actionCollection(),
+ "notes");
+
+ createMenu("textinserter.rc");
+}
+
+void TextInserter::slotNotesSelected()
+{
+ m_nParentView->slotLastNoteAction();
+}
+
+void TextInserter::slotEraseSelected()
+{
+ m_parentView->actionCollection()->action("erase")->activate();
+}
+
+void TextInserter::slotSelectSelected()
+{
+ m_parentView->actionCollection()->action("select")->activate();
+}
+
+void TextInserter::ready()
+{
+ m_nParentView->setCanvasCursor(Qt::crossCursor);
+ m_nParentView->setHeightTracking(false);
+}
+
+void TextInserter::handleLeftButtonPress(timeT,
+ int,
+ int staffNo,
+ QMouseEvent* e,
+ ViewElement *element)
+{
+ if (staffNo < 0)
+ return ;
+ LinedStaff *staff = m_nParentView->getLinedStaff(staffNo);
+
+ Text defaultText(m_text);
+ timeT insertionTime;
+ Event *eraseEvent = 0;
+
+ if (element && element->event()->isa(Text::EventType)) {
+
+ // edit an existing text, if that's what we clicked on
+
+ try {
+ defaultText = Text(*element->event());
+ } catch (Exception e) {}
+
+ insertionTime = element->event()->getAbsoluteTime(); // not getViewAbsoluteTime()
+
+ eraseEvent = element->event();
+
+ } else {
+
+ Event *clef = 0, *key = 0;
+
+ NotationElementList::iterator closestElement =
+ staff->getClosestElementToCanvasCoords(e->x(), (int)e->y(),
+ clef, key, false, -1);
+
+ if (closestElement == staff->getViewElementList()->end())
+ return ;
+
+ insertionTime = (*closestElement)->event()->getAbsoluteTime(); // not getViewAbsoluteTime()
+
+ }
+
+ TextEventDialog *dialog = new TextEventDialog
+ (m_nParentView, m_nParentView->getNotePixmapFactory(), defaultText);
+
+ if (dialog->exec() == QDialog::Accepted) {
+
+ m_text = dialog->getText();
+
+ TextInsertionCommand *command =
+ new TextInsertionCommand
+ (staff->getSegment(), insertionTime, m_text);
+
+ if (eraseEvent) {
+ KMacroCommand *macroCommand = new KMacroCommand(command->name());
+ macroCommand->addCommand(new EraseEventCommand(staff->getSegment(),
+ eraseEvent, false));
+ macroCommand->addCommand(command);
+ m_nParentView->addCommandToHistory(macroCommand);
+ } else {
+ m_nParentView->addCommandToHistory(command);
+ }
+
+ Event *event = command->getLastInsertedEvent();
+ if (event)
+ m_nParentView->setSingleSelectedEvent(staffNo, event);
+ }
+
+ delete dialog;
+}
+
+const QString TextInserter::ToolName = "textinserter";
+
+}
+#include "TextInserter.moc"
diff --git a/src/gui/editors/notation/TextInserter.h b/src/gui/editors/notation/TextInserter.h
new file mode 100644
index 0000000..3b4821b
--- /dev/null
+++ b/src/gui/editors/notation/TextInserter.h
@@ -0,0 +1,78 @@
+
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
+
+/*
+ Rosegarden
+ A MIDI and audio sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_TEXTINSERTER_H_
+#define _RG_TEXTINSERTER_H_
+
+#include "base/NotationTypes.h"
+#include "NotationTool.h"
+#include <qstring.h>
+#include "base/Event.h"
+
+
+class QMouseEvent;
+
+
+namespace Rosegarden
+{
+
+class ViewElement;
+class NotationView;
+
+
+/**
+ * This tool will request and insert text on mouse click events
+ */
+class TextInserter : public NotationTool
+{
+ Q_OBJECT
+
+ friend class NotationToolBox;
+
+public:
+ virtual void ready();
+
+ virtual void handleLeftButtonPress(timeT,
+ int height,
+ int staffNo,
+ QMouseEvent*,
+ ViewElement* el);
+ static const QString ToolName;
+
+protected slots:
+ void slotNotesSelected();
+ void slotEraseSelected();
+ void slotSelectSelected();
+
+protected:
+ TextInserter(NotationView*);
+ Text m_text;
+};
+
+
+
+}
+
+#endif
diff --git a/src/gui/editors/notation/TrackHeader.cpp b/src/gui/editors/notation/TrackHeader.cpp
new file mode 100644
index 0000000..32fab2f
--- /dev/null
+++ b/src/gui/editors/notation/TrackHeader.cpp
@@ -0,0 +1,450 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
+
+/*
+ Rosegarden
+ A MIDI and audio sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ This file is Copyright 2007-2008
+ Yves Guillemot <yc.guillemot@wanadoo.fr>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+
+#include "TrackHeader.h"
+#include "HeadersGroup.h"
+#include "base/Composition.h"
+#include "base/NotationTypes.h"
+#include "base/StaffExportTypes.h"
+#include "base/Colour.h"
+#include "base/ColourMap.h"
+#include "base/Track.h"
+#include "gui/general/GUIPalette.h"
+#include "gui/general/LinedStaff.h"
+#include "document/RosegardenGUIDoc.h"
+#include "misc/Strings.h"
+#include "NotePixmapFactory.h"
+#include "NotationView.h"
+#include "NotationStaff.h"
+
+#include <map>
+#include <set>
+#include <string>
+#include <utility>
+
+#include <kapplication.h>
+#include <klocale.h>
+#include <qsize.h>
+#include <qwidget.h>
+#include <qhbox.h>
+#include <qvbox.h>
+#include <qpushbutton.h>
+#include <qlabel.h>
+#include <qframe.h>
+#include <qstring.h>
+#include <qtooltip.h>
+
+
+namespace Rosegarden
+{
+
+
+// Status bits
+const int TrackHeader::SEGMENT_HERE = 1 << 0;
+const int TrackHeader::SUPERIMPOSED_SEGMENTS = 1 << 1;
+const int TrackHeader::INCONSISTENT_CLEFS = 1 << 2;
+const int TrackHeader::INCONSISTENT_KEYS = 1 << 3;
+const int TrackHeader::INCONSISTENT_LABELS = 1 << 4;
+const int TrackHeader::INCONSISTENT_TRANSPOSITIONS = 1 << 5;
+const int TrackHeader::BEFORE_FIRST_SEGMENT = 1 << 6;
+
+
+TrackHeader::TrackHeader(QWidget *parent, TrackId trackId, int height, int ypos) :
+ QLabel(parent),
+ m_track(trackId),
+ m_height(height),
+ m_ypos(ypos),
+ m_lastClef(Clef()),
+ m_lastKey(Rosegarden::Key()),
+ m_lastLabel(QString("")),
+ m_lastTranspose(0),
+ m_lastUpperText(QString("")),
+ m_neverUpdated(true),
+ m_isCurrent(true),
+ m_lastStatusPart(0),
+ m_lastWidth(0),
+ m_key(Rosegarden::Key()),
+ m_label(QString("")),
+ m_transpose(0),
+ m_status(0),
+ m_current(false)
+{
+
+ m_notationView = static_cast<HeadersGroup *>(parent)->getNotationView();
+
+ setFrameStyle(QFrame::Box | QFrame::Plain);
+ setCurrent(false);
+
+
+ //
+ // Tooltip text creation
+
+ Composition *comp =
+ static_cast<HeadersGroup *>(parent)->getComposition();
+ Track *track = comp->getTrackById(m_track);
+ int trackPos = comp->getTrackPositionById(m_track);
+
+ QString toolTipText = QString(i18n("Track %1 : \"%2\"")
+ .arg(trackPos + 1)
+ .arg(strtoqstr(track->getLabel())));
+
+ QString preset = track->getPresetLabel();
+ if (preset != QString(""))
+ toolTipText += QString(i18n("\nNotate for: %1").arg(preset));
+
+ QString notationSize = i18n("normal");
+ switch (track->getStaffSize()) {
+ case StaffTypes::Small:
+ notationSize = i18n("small");
+ break;
+ case StaffTypes::Tiny:
+ notationSize = i18n("tiny");
+ break;
+ }
+
+ QString bracketText = i18n("--");
+ switch (track->getStaffBracket()) {
+ case Brackets::SquareOn:
+ bracketText = "[-";
+ break;
+ case Brackets::SquareOff:
+ bracketText = "-]";
+ break;
+ case Brackets::SquareOnOff:
+ bracketText = "[-]";
+ break;
+ case Brackets::CurlyOn:
+ bracketText = "{-";
+ break;
+ case Brackets::CurlyOff:
+ bracketText = "-}";
+ break;
+ case Brackets::CurlySquareOn:
+ bracketText = "{[-";
+ break;
+ case Brackets::CurlySquareOff:
+ bracketText = "-]}";
+ break;
+ }
+
+ toolTipText += QString(i18n("\nSize: %1, Bracket: %2 "))
+ .arg(notationSize)
+ .arg(bracketText);
+
+ // Sort segments by position on the track
+ SortedSegments segments;
+ for (int i=0; i<m_notationView->getStaffCount(); i++) {
+
+ NotationStaff * notationStaff = m_notationView->getNotationStaff(i);
+ Segment &segment = notationStaff->getSegment();
+ TrackId trackId = segment.getTrack();
+
+ if (trackId == m_track) {
+ segments.insert(&segment);
+ }
+ }
+
+ for (SortedSegments::iterator i=segments.begin(); i!=segments.end(); ++i) {
+ timeT segStart = (*i)->getStartTime();
+ timeT segEnd = (*i)->getEndMarkerTime();
+ int barStart = comp->getBarNumber(segStart) + 1;
+ int barEnd = comp->getBarNumber(segEnd) + 1;
+
+ int transpose = (*i)->getTranspose();
+ if (transpose) {
+ QString transposeName;
+ transposeValueToName(transpose, transposeName);
+ toolTipText += QString(i18n("\nbars [%1-%2] in %3 (tr=%4) : \"%5\""))
+ .arg(barStart)
+ .arg(barEnd)
+ .arg(transposeName)
+ .arg(transpose)
+ .arg(strtoqstr((*i)->getLabel()));
+ } else {
+ toolTipText += QString(i18n("\nbars [%1-%2] (tr=%3) : \"%4\""))
+ .arg(barStart)
+ .arg(barEnd)
+ .arg(transpose)
+ .arg(strtoqstr((*i)->getLabel()));
+ }
+ }
+
+ QToolTip::add(this, toolTipText);
+
+ m_firstSeg = *segments.begin();
+ m_firstSegStartTime = m_firstSeg->getStartTime();
+
+ /// This may not work if two segments are superimposed
+ /// at the beginning of the track (inconsistencies are
+ /// not detected).
+ /// TODO : Look for the first segment(s) in
+ /// lookAtStaff() and not here.
+}
+
+void
+TrackHeader::setCurrent(bool current)
+{
+ /// TODO : use colours from GUIPalette
+
+ if (current != m_isCurrent) {
+ m_isCurrent = current;
+ if (current) {
+ setLineWidth(2);
+ setMargin(0);
+ setPaletteForegroundColor(QColor(0, 0, 255));
+ } else {
+ setLineWidth(1);
+ setMargin(1);
+ setPaletteForegroundColor(QColor(0, 0, 0));
+ }
+ }
+}
+
+void
+TrackHeader::transposeValueToName(int transpose, QString &transposeName)
+{
+
+ /// TODO : Should be rewrited using methods from Pitch class
+
+ int noteIndex = transpose % 12;
+ if (noteIndex < 0) noteIndex += 12;
+
+ switch(noteIndex) {
+ case 0 : transposeName = i18n("C"); break;
+ case 1 : transposeName = i18n("C#"); break;
+ case 2 : transposeName = i18n("D"); break;
+ case 3 : transposeName = i18n("Eb"); break;
+ case 4 : transposeName = i18n("E"); break;
+ case 5 : transposeName = i18n("F"); break;
+ case 6 : transposeName = i18n("F#"); break;
+ case 7 : transposeName = i18n("G"); break;
+ case 8 : transposeName = i18n("G#"); break;
+ case 9 : transposeName = i18n("A"); break;
+ case 10 : transposeName = i18n("Bb"); break;
+ case 11 : transposeName = i18n("B"); break;
+ }
+}
+
+int
+TrackHeader::lookAtStaff(double x, int maxWidth)
+{
+ // Read Clef and Key on canvas at (x, m_ypos + m_height / 2)
+ // then guess the header needed width and return it
+
+ // When walking through the segments :
+ // clef, key, label and transpose are current values
+ // clef0, key0, label0 and transpose0 are preceding values used to look
+ // for inconsistencies
+ // key1, label1 and transpose1 are "visible" (opposed at invisible as are
+ // key=<C major>, label="" or transpose=0)
+ // preceding or current values which may be
+ // displayed with a red colour if some
+ // inconsistency occurs.
+ Clef clef, clef0;
+ Rosegarden::Key key, key0, key1 = Rosegarden::Key("C major");
+ QString label = QString(""), label0, label1 = QString("");
+ int transpose = 0, transpose0, transpose1 = 0;
+
+ int staff;
+
+ Composition *comp =
+ static_cast<HeadersGroup *>(parent())->getComposition();
+ Track *track = comp->getTrackById(m_track);
+ int trackPos = comp->getTrackPositionById(m_track);
+
+ int status = 0;
+ bool current = false;
+ for (int i=0; i<m_notationView->getStaffCount(); i++) {
+ NotationStaff * notationStaff = m_notationView->getNotationStaff(i);
+ Segment &segment = notationStaff->getSegment();
+ TrackId trackId = segment.getTrack();
+ if (trackId == m_track) {
+
+ /// TODO : What if a segment has been moved ???
+ timeT xTime = notationStaff->getTimeAtCanvasCoords(x, m_ypos);
+ if (xTime < m_firstSegStartTime) {
+ status |= BEFORE_FIRST_SEGMENT;
+ /// TODO : What if superimposed segments ???
+ m_firstSeg->getFirstClefAndKey(clef, key);
+ label = strtoqstr(m_firstSeg->getLabel());
+ transpose = m_firstSeg->getTranspose();
+ current = current || m_notationView->isCurrentStaff(i);
+ break;
+ }
+ timeT segStart = segment.getStartTime();
+ timeT segEnd = segment.getEndMarkerTime();
+ current = current || m_notationView->isCurrentStaff(i);
+
+ if ((xTime >= segStart) && (xTime < segEnd)) {
+
+ notationStaff->getClefAndKeyAtCanvasCoords(x,
+ m_ypos + m_height / 2, clef, key);
+ label = strtoqstr(segment.getLabel());
+ transpose = segment.getTranspose();
+
+ if (status & SEGMENT_HERE) {
+ status |= SUPERIMPOSED_SEGMENTS;
+ if (clef != clef0)
+ status |= INCONSISTENT_CLEFS;
+ if (key != key0)
+ status |= INCONSISTENT_KEYS;
+ if (label != label0)
+ status |= INCONSISTENT_LABELS;
+ if (transpose != transpose0)
+ status |= INCONSISTENT_TRANSPOSITIONS;
+ } else {
+ status |= SEGMENT_HERE;
+ }
+
+ staff = i;
+
+ // If current value is visible, remember it
+ if (key.getAccidentalCount()) key1 = key;
+ if (label.stripWhiteSpace().length()) label1 = label;
+ if (transpose) transpose1 = transpose;
+
+ // Current values become last values
+ clef0 = clef;
+ key0 = key;
+ label0 = label;
+ transpose0 = transpose;
+ } // if(xTime...)
+ } // if(trackId...)
+ }
+
+ // Remember current data (but only visible data if inconsistency)
+ m_clef = clef;
+ m_key = (status & INCONSISTENT_KEYS) ? key1 : key;
+ m_label = (status & INCONSISTENT_LABELS) ? label1 : label;
+ m_transpose = (status & INCONSISTENT_TRANSPOSITIONS) ? transpose1 : transpose;
+ m_current = current;
+ m_status = status;
+
+ QString noteName;
+ transposeValueToName(m_transpose, noteName);
+
+ m_upperText = QString(i18n("%1: %2")
+ .arg(trackPos + 1)
+ .arg(strtoqstr(track->getLabel())));
+ if (m_transpose) m_transposeText = i18n(" in %1").arg(noteName);
+ else m_transposeText = QString("");
+
+ NotePixmapFactory * npf = m_notationView->getNotePixmapFactory();
+ int clefAndKeyWidth = npf->getClefAndKeyWidth(m_key, m_clef);
+
+ // How many text lines may be written above or under the clef
+ // in track header ?
+ m_numberOfTextLines = npf->getTrackHeaderNTL(m_height);
+
+ int trackLabelWidth =
+ npf->getTrackHeaderTextWidth(m_upperText + m_transposeText)
+ / m_numberOfTextLines;
+ int segmentNameWidth =
+ npf->getTrackHeaderTextWidth(m_label) / m_numberOfTextLines;
+
+ // Get the max. width from upper text and lower text
+ int width = (segmentNameWidth > trackLabelWidth)
+ ? segmentNameWidth : trackLabelWidth;
+
+ // Text width is limited by max header Width
+ if (width > maxWidth) width = maxWidth;
+
+ // But clef and key width may override max header width
+ if (width < clefAndKeyWidth) width = clefAndKeyWidth;
+
+ return width;
+}
+
+
+
+void
+TrackHeader::updateHeader(int width)
+{
+
+ // Update the header (using given width) if necessary
+
+ // Filter out bits whose display doesn't depend from
+ int statusPart = m_status & ~(SUPERIMPOSED_SEGMENTS);
+
+ // Header should be updated only if necessary
+ if ( m_neverUpdated
+ || (width != m_lastWidth)
+ || (statusPart != m_lastStatusPart)
+ || (m_key != m_lastKey)
+ || (m_clef != m_lastClef)
+ || (m_label != m_lastLabel)
+ || (m_upperText != m_lastUpperText)
+ || (m_transpose != m_lastTranspose)) {
+
+ m_neverUpdated = false;
+ m_lastStatusPart = statusPart;
+ m_lastKey = m_key;
+ m_lastClef = m_clef;
+ m_lastLabel = m_label;
+ m_lastTranspose = m_transpose;
+ m_lastUpperText = m_upperText;
+
+ bool drawClef = true;
+ QColor clefColour;
+ if (m_status & (SEGMENT_HERE | BEFORE_FIRST_SEGMENT)) {
+ if (m_status & (INCONSISTENT_CLEFS | INCONSISTENT_KEYS))
+ clefColour = Qt::red;
+ else
+ clefColour = Qt::black;
+ } else {
+ drawClef = false;
+ }
+
+ NotePixmapFactory * npf = m_notationView->getNotePixmapFactory();
+ QPixmap pmap = NotePixmapFactory::toQPixmap(
+ npf->makeTrackHeaderPixmap(width, m_height, this));
+
+ setPixmap(pmap);
+ setFixedWidth(width);
+
+ // Forced width may differ from localy computed width
+ m_lastWidth = width;
+ }
+
+ // Highlight header if track is the current one
+ setCurrent(m_current);
+}
+
+bool
+TrackHeader::SegmentCmp::operator()(const Segment * s1, const Segment * s2) const
+{
+ // Sort segments by start time, then by end time
+ if (s1->getStartTime() < s2->getStartTime()) return true;
+ if (s1->getStartTime() > s2->getStartTime()) return false;
+ if (s1->getEndMarkerTime() < s2->getEndMarkerTime()) return true;
+ return false;
+}
+
+}
+#include "TrackHeader.moc"
diff --git a/src/gui/editors/notation/TrackHeader.h b/src/gui/editors/notation/TrackHeader.h
new file mode 100644
index 0000000..0104430
--- /dev/null
+++ b/src/gui/editors/notation/TrackHeader.h
@@ -0,0 +1,219 @@
+
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
+
+/*
+ Rosegarden
+ A MIDI and audio sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ This file is Copyright 2007-2008
+ Yves Guillemot <yc.guillemot@wanadoo.fr>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#ifndef _RG_TRACKHEADER_H_
+#define _RG_TRACKHEADER_H_
+
+#include "base/NotationTypes.h"
+#include "base/Track.h"
+
+#include <qsize.h>
+#include <qwidget.h>
+#include <qlabel.h>
+
+#include <set>
+
+class QLabel;
+
+
+namespace Rosegarden
+{
+
+class NotePixmapFactory;
+class NotationView;
+class ColourMap;
+class Segment;
+
+class TrackHeader : public QLabel
+{
+ Q_OBJECT
+public:
+ /**
+ * Create a new track header for track of id trackId.
+ * *parent is the parent widget, height the height of staff and
+ * ypos is the staff y position on canvas.
+ */
+ TrackHeader(QWidget *parent, TrackId trackId, int height, int ypos);
+
+ /**
+ * Draw a blue line around header when current is true
+ * (intended to highlight the "current" track).
+ */
+ void setCurrent(bool current);
+
+ /**
+ * Examine staff at x position and gather data needed to draw
+ * the track header.
+ * Return the minimum width required to display the track header.
+ * maxWidth is the maximum width allowed to show text. Return width
+ * may be greater than maxWidth if needed to show clef and key signature.
+ * (Header have always to show complete clef and key signature).
+ */
+ int lookAtStaff(double x, int maxWidth);
+
+ /**
+ * (Re)draw the header on the notation view using the data gathered
+ * by lookAtStaff() last call and the specified width.
+ */
+ void updateHeader(int width);
+
+ /**
+ * Return the Id of the associated track.
+ */
+ TrackId getId()
+ { return m_track;
+ }
+
+ /**
+ * Return how many text lines may be written in the header (above
+ * the clef and under the clef).
+ * This data is coming from the last call of lookAtStaff().
+ */
+ int getNumberOfTextLines() { return m_numberOfTextLines; }
+
+ /**
+ * Return the Clef to draw in the header
+ */
+ Clef & getClef() { return m_clef; }
+
+ /**
+ * Get which key signature should be drawn in the header
+ * from the last call of lookAtStaff().
+ */
+ Rosegarden::Key & getKey() { return m_key; }
+
+ /**
+ * Return true if a Clef (and a key signature) have to be drawn in the header
+ */
+ bool isAClefToDraw()
+ {
+ return (m_status & SEGMENT_HERE) || (m_status & BEFORE_FIRST_SEGMENT);
+ }
+
+ /**
+ * Return the text to write in the header top
+ */
+ QString getUpperText() { return m_upperText; }
+
+ /**
+ * Return the transposition text
+ * (to be written at the end of the upper text)
+ */
+ QString getTransposeText() { return m_transposeText; }
+
+ /**
+ * Return the text to write in the header bottom
+ */
+ QString getLowerText() { return m_label; }
+
+ /**
+ * Return true if two segments or more are superimposed and are
+ * not using the same clef
+ */
+ bool isClefInconsistent() { return m_status & INCONSISTENT_CLEFS; }
+
+ /**
+ * Return true if two segments or more are superimposed and are
+ * not using the same key signature
+ */
+ bool isKeyInconsistent() { return m_status & INCONSISTENT_KEYS; }
+
+ /**
+ * Return true if two segments or more are superimposed and are
+ * not using the same label
+ */
+ bool isLabelInconsistent() { return m_status & INCONSISTENT_LABELS; }
+
+ /**
+ * Return true if two segments or more are superimposed and are
+ * not using the same transposition
+ */
+ bool isTransposeInconsistent()
+ {
+ return m_status & INCONSISTENT_TRANSPOSITIONS;
+ }
+
+
+private :
+ /**
+ * Convert the transpose value to the instrument tune and
+ * return it in a printable string.
+ */
+ void transposeValueToName(int transpose, QString &transposeName);
+
+
+ // Status bits
+ static const int SEGMENT_HERE;
+ static const int SUPERIMPOSED_SEGMENTS;
+ static const int INCONSISTENT_CLEFS;
+ static const int INCONSISTENT_KEYS;
+ static const int INCONSISTENT_LABELS;
+ static const int INCONSISTENT_TRANSPOSITIONS;
+ static const int BEFORE_FIRST_SEGMENT;
+
+ TrackId m_track;
+ int m_height;
+ int m_ypos;
+ NotationView * m_notationView;
+
+ Clef m_lastClef;
+ Rosegarden::Key m_lastKey;
+ QString m_lastLabel;
+ int m_lastTranspose;
+ QString m_lastUpperText;
+ bool m_neverUpdated;
+ bool m_isCurrent;
+ int m_lastStatusPart;
+ int m_lastWidth;
+
+ Clef m_clef;
+ Rosegarden::Key m_key;
+ QString m_label;
+ int m_transpose;
+ int m_status;
+ bool m_current;
+
+ QString m_upperText;
+ QString m_transposeText;
+ int m_numberOfTextLines;
+
+ // Used to sort the segments listed in the header toolTipText
+ struct SegmentCmp {
+ bool operator()(const Segment *s1, const Segment *s2) const;
+ };
+ typedef std::multiset<Segment *, SegmentCmp> SortedSegments;
+
+ // First segment on the track.
+ Segment * m_firstSeg;
+ timeT m_firstSegStartTime;
+};
+
+}
+
+#endif
diff --git a/src/gui/editors/parameters/AudioInstrumentParameterPanel.cpp b/src/gui/editors/parameters/AudioInstrumentParameterPanel.cpp
new file mode 100644
index 0000000..44a202b
--- /dev/null
+++ b/src/gui/editors/parameters/AudioInstrumentParameterPanel.cpp
@@ -0,0 +1,437 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
+
+/*
+ Rosegarden
+ A MIDI and audio sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "AudioInstrumentParameterPanel.h"
+#include <qlayout.h>
+#include <kapplication.h>
+
+#include <klocale.h>
+#include "misc/Debug.h"
+#include "misc/Strings.h"
+#include "base/AudioPluginInstance.h"
+#include "base/Instrument.h"
+#include "base/MidiProgram.h"
+#include "document/RosegardenGUIDoc.h"
+#include "gui/studio/AudioPluginManager.h"
+#include "gui/studio/AudioPlugin.h"
+#include "gui/studio/StudioControl.h"
+#include "gui/widgets/AudioFaderBox.h"
+#include "gui/widgets/AudioVUMeter.h"
+#include "gui/widgets/Fader.h"
+#include "gui/widgets/Rotary.h"
+#include "gui/widgets/AudioRouteMenu.h"
+#include "InstrumentParameterPanel.h"
+#include "sound/MappedCommon.h"
+#include "sound/MappedStudio.h"
+#include <qcolor.h>
+#include <qframe.h>
+#include <qlabel.h>
+#include <qpalette.h>
+#include <qpixmap.h>
+#include <qpushbutton.h>
+#include <qstring.h>
+#include <qtooltip.h>
+#include <qwidget.h>
+#include <qsignalmapper.h>
+
+
+namespace Rosegarden
+{
+
+void
+AudioInstrumentParameterPanel::slotSelectAudioLevel(float dB)
+{
+ if (m_selectedInstrument == 0)
+ return ;
+
+ if (m_selectedInstrument->getType() == Instrument::Audio ||
+ m_selectedInstrument->getType() == Instrument::SoftSynth) {
+ m_selectedInstrument->setLevel(dB);
+
+ StudioControl::setStudioObjectProperty
+ (MappedObjectId(m_selectedInstrument->getMappedId()),
+ MappedAudioFader::FaderLevel,
+ MappedObjectValue(dB));
+ }
+
+ emit updateAllBoxes();
+ emit instrumentParametersChanged(m_selectedInstrument->getId());
+}
+
+void
+AudioInstrumentParameterPanel::slotSelectAudioRecordLevel(float dB)
+{
+ if (m_selectedInstrument == 0)
+ return ;
+
+ // std::cerr << "AudioInstrumentParameterPanel::slotSelectAudioRecordLevel("
+ // << dB << ")" << std::endl;
+
+ if (m_selectedInstrument->getType() == Instrument::Audio) {
+ m_selectedInstrument->setRecordLevel(dB);
+
+ StudioControl::setStudioObjectProperty
+ (MappedObjectId(m_selectedInstrument->getMappedId()),
+ MappedAudioFader::FaderRecordLevel,
+ MappedObjectValue(dB));
+
+ emit updateAllBoxes();
+ emit instrumentParametersChanged(m_selectedInstrument->getId());
+ }
+}
+
+void
+AudioInstrumentParameterPanel::slotPluginSelected(InstrumentId instrumentId,
+ int index, int plugin)
+{
+ if (!m_selectedInstrument ||
+ instrumentId != m_selectedInstrument->getId())
+ return ;
+
+ RG_DEBUG << "AudioInstrumentParameterPanel::slotPluginSelected - "
+ << "instrument = " << instrumentId
+ << ", index = " << index
+ << ", plugin = " << plugin << endl;
+
+ QColor pluginBackgroundColour = Qt::black;
+ bool bypassed = false;
+
+ QPushButton *button = 0;
+ QString noneText;
+
+ // updates synth gui button &c:
+ m_audioFader->slotSetInstrument(&m_doc->getStudio(), m_selectedInstrument);
+
+ if (index == (int)Instrument::SYNTH_PLUGIN_POSITION) {
+ button = m_audioFader->m_synthButton;
+ noneText = i18n("<no synth>");
+ } else {
+ button = m_audioFader->m_plugins[index];
+ noneText = i18n("<no plugin>");
+ }
+
+ if (!button)
+ return ;
+
+ if (plugin == -1) {
+
+ button->setText(noneText);
+ QToolTip::add
+ (button, noneText);
+
+ } else {
+
+ AudioPlugin *pluginClass = m_doc->getPluginManager()->getPlugin(plugin);
+
+ if (pluginClass) {
+ button->setText(pluginClass->getLabel());
+
+ QToolTip::add
+ (button, pluginClass->getLabel());
+
+ pluginBackgroundColour = pluginClass->getColour();
+ }
+ }
+
+ AudioPluginInstance *inst =
+ m_selectedInstrument->getPlugin(index);
+
+ if (inst)
+ bypassed = inst->isBypassed();
+
+ setButtonColour(index, bypassed, pluginBackgroundColour);
+
+ if (index == (int)Instrument::SYNTH_PLUGIN_POSITION) {
+ emit changeInstrumentLabel(instrumentId, button->text());
+ }
+}
+
+void
+AudioInstrumentParameterPanel::slotPluginBypassed(InstrumentId instrumentId,
+ int pluginIndex, bool bp)
+{
+ if (!m_selectedInstrument ||
+ instrumentId != m_selectedInstrument->getId())
+ return ;
+
+ AudioPluginInstance *inst =
+ m_selectedInstrument->getPlugin(pluginIndex);
+
+ QColor backgroundColour = Qt::black; // default background colour
+
+ if (inst && inst->isAssigned()) {
+ AudioPlugin *pluginClass
+ = m_doc->getPluginManager()->getPlugin(
+ m_doc->getPluginManager()->
+ getPositionByIdentifier(inst->getIdentifier().c_str()));
+
+ /// Set the colour on the button
+ //
+ if (pluginClass)
+ backgroundColour = pluginClass->getColour();
+ }
+
+ setButtonColour(pluginIndex, bp, backgroundColour);
+}
+
+void
+AudioInstrumentParameterPanel::setButtonColour(
+ int pluginIndex, bool bypassState, const QColor &colour)
+{
+ RG_DEBUG << "AudioInstrumentParameterPanel::setButtonColour "
+ << "pluginIndex = " << pluginIndex
+ << ", bypassState = " << bypassState
+ << ", rgb = " << colour.name() << endl;
+
+ QPushButton *button = 0;
+
+ if (pluginIndex == Instrument::SYNTH_PLUGIN_POSITION) {
+ button = m_audioFader->m_synthButton;
+ } else {
+ button = m_audioFader->m_plugins[pluginIndex];
+ }
+
+ if (!button)
+ return ;
+
+ // Set the bypass colour on the plugin button
+ if (bypassState) {
+ button->
+ setPaletteForegroundColor(kapp->palette().
+ color(QPalette::Active, QColorGroup::Button));
+
+ button->
+ setPaletteBackgroundColor(kapp->palette().
+ color(QPalette::Active, QColorGroup::ButtonText));
+ } else if (colour == Qt::black) {
+ button->
+ setPaletteForegroundColor(kapp->palette().
+ color(QPalette::Active, QColorGroup::ButtonText));
+
+ button->
+ setPaletteBackgroundColor(kapp->palette().
+ color(QPalette::Active, QColorGroup::Button));
+ } else {
+ button->
+ setPaletteForegroundColor(Qt::white);
+
+ button->
+ setPaletteBackgroundColor(colour);
+ }
+}
+
+AudioInstrumentParameterPanel::AudioInstrumentParameterPanel(RosegardenGUIDoc* doc, QWidget* parent)
+ : InstrumentParameterPanel(doc, parent),
+ m_audioFader(new AudioFaderBox(this))
+{
+ QGridLayout *gridLayout = new QGridLayout(this, 3, 2, 5, 5);
+
+ // Instrument label : first row, all cols
+ gridLayout->addMultiCellWidget(m_instrumentLabel, 0, 0, 0, 1, AlignCenter);
+
+ // fader and connect it
+ gridLayout->addMultiCellWidget(m_audioFader, 1, 1, 0, 1);
+
+ gridLayout->setRowStretch(2, 1);
+
+ connect(m_audioFader, SIGNAL(audioChannelsChanged(int)),
+ this, SLOT(slotAudioChannels(int)));
+
+ connect(m_audioFader->m_signalMapper, SIGNAL(mapped(int)),
+ this, SLOT(slotSelectPlugin(int)));
+
+ connect(m_audioFader->m_fader, SIGNAL(faderChanged(float)),
+ this, SLOT(slotSelectAudioLevel(float)));
+
+ connect(m_audioFader->m_recordFader, SIGNAL(faderChanged(float)),
+ this, SLOT(slotSelectAudioRecordLevel(float)));
+
+ connect(m_audioFader->m_pan, SIGNAL(valueChanged(float)),
+ this, SLOT(slotSetPan(float)));
+
+ connect(m_audioFader->m_audioOutput, SIGNAL(changed()),
+ this, SLOT(slotAudioRoutingChanged()));
+
+ connect(m_audioFader->m_audioInput, SIGNAL(changed()),
+ this, SLOT(slotAudioRoutingChanged()));
+
+ connect(m_audioFader->m_synthButton, SIGNAL(clicked()),
+ this, SLOT(slotSynthButtonClicked()));
+
+ connect(m_audioFader->m_synthGUIButton, SIGNAL(clicked()),
+ this, SLOT(slotSynthGUIButtonClicked()));
+}
+
+void
+AudioInstrumentParameterPanel::slotSynthButtonClicked()
+{
+ slotSelectPlugin(Instrument::SYNTH_PLUGIN_POSITION);
+}
+
+void
+AudioInstrumentParameterPanel::slotSynthGUIButtonClicked()
+{
+ emit showPluginGUI(m_selectedInstrument->getId(),
+ Instrument::SYNTH_PLUGIN_POSITION);
+}
+
+void
+AudioInstrumentParameterPanel::slotSetPan(float pan)
+{
+ RG_DEBUG << "AudioInstrumentParameterPanel::slotSetPan - "
+ << "pan = " << pan << endl;
+
+ StudioControl::setStudioObjectProperty
+ (MappedObjectId(m_selectedInstrument->getMappedId()),
+ MappedAudioFader::Pan,
+ MappedObjectValue(pan));
+
+ m_selectedInstrument->setPan(MidiByte(pan + 100.0));
+ emit instrumentParametersChanged(m_selectedInstrument->getId());
+}
+
+void
+AudioInstrumentParameterPanel::setAudioMeter(float dBleft, float dBright,
+ float recDBleft, float recDBright)
+{
+ // RG_DEBUG << "AudioInstrumentParameterPanel::setAudioMeter: (" << dBleft
+ // << "," << dBright << ")" << endl;
+
+ if (m_selectedInstrument) {
+ // Always set stereo, because we have to reflect what's happening
+ // with the pan setting even on mono tracks
+ m_audioFader->m_vuMeter->setLevel(dBleft, dBright);
+ m_audioFader->m_vuMeter->setRecordLevel(recDBleft, recDBright);
+ }
+}
+
+void
+AudioInstrumentParameterPanel::setupForInstrument(Instrument* instrument)
+{
+ blockSignals(true);
+
+ m_selectedInstrument = instrument;
+
+ m_instrumentLabel->setText(strtoqstr(instrument->getName()));
+
+ m_audioFader->m_recordFader->setFader(instrument->getRecordLevel());
+ m_audioFader->m_fader->setFader(instrument->getLevel());
+
+ m_audioFader->slotSetInstrument(&m_doc->getStudio(), instrument);
+
+ int start = 0;
+
+ if (instrument->getType() == Instrument::SoftSynth)
+ start = -1;
+
+ for (int i = start; i < int(m_audioFader->m_plugins.size()); i++) {
+ int index;
+ QPushButton *button;
+ QString noneText;
+
+ if (i == -1) {
+ index = Instrument::SYNTH_PLUGIN_POSITION;
+ button = m_audioFader->m_synthButton;
+ noneText = i18n("<no synth>");
+ } else {
+ index = i;
+ button = m_audioFader->m_plugins[i];
+ noneText = i18n("<no plugin>");
+ }
+
+ button->show();
+
+ AudioPluginInstance *inst = instrument->getPlugin(index);
+
+ if (inst && inst->isAssigned()) {
+ AudioPlugin *pluginClass
+ = m_doc->getPluginManager()->getPlugin(
+ m_doc->getPluginManager()->
+ getPositionByIdentifier(inst->getIdentifier().c_str()));
+
+ if (pluginClass) {
+ button->setText(pluginClass->getLabel());
+ QToolTip::add
+ (button, pluginClass->getLabel());
+ setButtonColour(index, inst->isBypassed(),
+ pluginClass->getColour());
+ }
+ } else {
+ button->setText(noneText);
+ QToolTip::add
+ (button, noneText);
+ setButtonColour(index, inst ? inst->isBypassed() : false, Qt::black);
+ }
+ }
+
+ // Set the number of channels on the fader widget
+ //
+ m_audioFader->setAudioChannels(instrument->getAudioChannels());
+
+ // Pan - adjusted backwards
+ //
+ m_audioFader->m_pan->setPosition(instrument->getPan() - 100);
+
+ // Tell fader box whether to include e.g. audio input selection
+ //
+ m_audioFader->setIsSynth(instrument->getType() == Instrument::SoftSynth);
+
+ blockSignals(false);
+}
+
+void
+AudioInstrumentParameterPanel::slotAudioChannels(int channels)
+{
+ RG_DEBUG << "AudioInstrumentParameterPanel::slotAudioChannels - "
+ << "channels = " << channels << endl;
+
+ m_selectedInstrument->setAudioChannels(channels);
+
+ StudioControl::setStudioObjectProperty
+ (MappedObjectId(m_selectedInstrument->getMappedId()),
+ MappedAudioFader::Channels,
+ MappedObjectValue(channels));
+
+ emit instrumentParametersChanged(m_selectedInstrument->getId());
+
+}
+
+void
+AudioInstrumentParameterPanel::slotAudioRoutingChanged()
+{
+ if (m_selectedInstrument)
+ emit instrumentParametersChanged(m_selectedInstrument->getId());
+}
+
+void
+AudioInstrumentParameterPanel::slotSelectPlugin(int index)
+{
+ if (m_selectedInstrument) {
+ emit selectPlugin(0, m_selectedInstrument->getId(), index);
+ }
+}
+
+}
+#include "AudioInstrumentParameterPanel.moc"
diff --git a/src/gui/editors/parameters/AudioInstrumentParameterPanel.h b/src/gui/editors/parameters/AudioInstrumentParameterPanel.h
new file mode 100644
index 0000000..932e6bc
--- /dev/null
+++ b/src/gui/editors/parameters/AudioInstrumentParameterPanel.h
@@ -0,0 +1,107 @@
+
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
+
+/*
+ Rosegarden
+ A MIDI and audio sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_AUDIOINSTRUMENTPARAMETERPANEL_H_
+#define _RG_AUDIOINSTRUMENTPARAMETERPANEL_H_
+
+#include "base/MidiProgram.h"
+#include "InstrumentParameterPanel.h"
+#include <qpixmap.h>
+#include <qstring.h>
+
+
+class QWidget;
+class QColor;
+
+
+namespace Rosegarden
+{
+
+class RosegardenGUIDoc;
+class Instrument;
+class AudioFaderBox;
+
+
+class AudioInstrumentParameterPanel : public InstrumentParameterPanel
+{
+ Q_OBJECT
+public:
+ AudioInstrumentParameterPanel(RosegardenGUIDoc* doc, QWidget* parent);
+
+ virtual void setupForInstrument(Instrument*);
+
+ // Set the audio meter to a given level for a maximum of
+ // two channels.
+ //
+ void setAudioMeter(float dBleft, float dBright,
+ float recDBleft, float recDBright);
+
+ // Set the button colour
+ //
+ void setButtonColour(int pluginIndex, bool bypassState,
+ const QColor &color);
+
+public slots:
+ // From AudioFaderBox
+ //
+ void slotSelectAudioLevel(float dB);
+ void slotSelectAudioRecordLevel(float dB);
+ void slotAudioChannels(int channels);
+ void slotAudioRoutingChanged();
+ void slotSelectPlugin(int index);
+
+ // From the parameter box clicks
+ void slotSetPan(float pan);
+
+ // From Plugin dialog
+ //
+ void slotPluginSelected(InstrumentId id, int index, int plugin);
+ void slotPluginBypassed(InstrumentId id, int pluginIndex, bool bp);
+
+ void slotSynthButtonClicked();
+ void slotSynthGUIButtonClicked();
+
+signals:
+ void selectPlugin(QWidget *, InstrumentId, int index);
+ void instrumentParametersChanged(InstrumentId);
+ void showPluginGUI(InstrumentId, int index);
+ void changeInstrumentLabel(InstrumentId id, QString label);
+
+protected:
+ //--------------- Data members ---------------------------------
+
+ AudioFaderBox *m_audioFader;
+
+private:
+
+ QPixmap m_monoPixmap;
+ QPixmap m_stereoPixmap;
+
+};
+
+
+}
+
+#endif
diff --git a/src/gui/editors/parameters/InstrumentParameterBox.cpp b/src/gui/editors/parameters/InstrumentParameterBox.cpp
new file mode 100644
index 0000000..8114e0d
--- /dev/null
+++ b/src/gui/editors/parameters/InstrumentParameterBox.cpp
@@ -0,0 +1,265 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
+
+/*
+ Rosegarden
+ A MIDI and audio sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "InstrumentParameterBox.h"
+#include <qlayout.h>
+
+#include <klocale.h>
+#include "misc/Debug.h"
+#include "AudioInstrumentParameterPanel.h"
+#include "base/Instrument.h"
+#include "base/MidiProgram.h"
+#include "document/RosegardenGUIDoc.h"
+#include "MIDIInstrumentParameterPanel.h"
+#include "RosegardenParameterArea.h"
+#include "RosegardenParameterBox.h"
+#include <ktabwidget.h>
+#include <qfont.h>
+#include <qframe.h>
+#include <qscrollview.h>
+#include <qstring.h>
+#include <qvbox.h>
+#include <qwidget.h>
+#include <qwidgetstack.h>
+
+
+namespace Rosegarden
+{
+
+InstrumentParameterBox::InstrumentParameterBox(RosegardenGUIDoc *doc,
+ QWidget *parent)
+ : RosegardenParameterBox(i18n("Instrument"),
+ i18n("Instrument Parameters"),
+ parent),
+ m_widgetStack(new QWidgetStack(this)),
+ m_noInstrumentParameters(new QVBox(this)),
+ m_midiInstrumentParameters(new MIDIInstrumentParameterPanel(doc, this)),
+ m_audioInstrumentParameters(new AudioInstrumentParameterPanel(doc, this)),
+ m_selectedInstrument(-1),
+ m_doc(doc),
+ m_lastShowAdditionalControlsArg(false)
+{
+ m_widgetStack->setFont(m_font);
+ m_noInstrumentParameters->setFont(m_font);
+ m_midiInstrumentParameters->setFont(m_font);
+ m_audioInstrumentParameters->setFont(m_font);
+
+ bool contains = false;
+
+ std::vector<InstrumentParameterBox*>::iterator it =
+ instrumentParamBoxes.begin();
+
+ for (; it != instrumentParamBoxes.end(); it++)
+ if ((*it) == this)
+ contains = true;
+
+ if (!contains)
+ instrumentParamBoxes.push_back(this);
+
+ m_widgetStack->addWidget(m_midiInstrumentParameters);
+ m_widgetStack->addWidget(m_audioInstrumentParameters);
+ m_widgetStack->addWidget(m_noInstrumentParameters);
+
+ m_midiInstrumentParameters->adjustSize();
+ m_audioInstrumentParameters->adjustSize();
+ m_noInstrumentParameters->adjustSize();
+
+ connect(m_audioInstrumentParameters, SIGNAL(updateAllBoxes()),
+ this, SLOT(slotUpdateAllBoxes()));
+
+ connect(m_audioInstrumentParameters,
+ SIGNAL(instrumentParametersChanged(InstrumentId)),
+ this,
+ SIGNAL(instrumentParametersChanged(InstrumentId)));
+
+ connect(m_audioInstrumentParameters,
+ SIGNAL(selectPlugin(QWidget *, InstrumentId, int)),
+ this,
+ SIGNAL(selectPlugin(QWidget *, InstrumentId, int)));
+
+ connect(m_audioInstrumentParameters,
+ SIGNAL(showPluginGUI(InstrumentId, int)),
+ this,
+ SIGNAL(showPluginGUI(InstrumentId, int)));
+
+ connect(m_midiInstrumentParameters, SIGNAL(updateAllBoxes()),
+ this, SLOT(slotUpdateAllBoxes()));
+
+ connect(m_midiInstrumentParameters,
+ SIGNAL(changeInstrumentLabel(InstrumentId, QString)),
+ this, SIGNAL(changeInstrumentLabel(InstrumentId, QString)));
+
+ connect(m_audioInstrumentParameters,
+ SIGNAL(changeInstrumentLabel(InstrumentId, QString)),
+ this, SIGNAL(changeInstrumentLabel(InstrumentId, QString)));
+
+ connect(m_midiInstrumentParameters,
+ SIGNAL(instrumentParametersChanged(InstrumentId)),
+ this,
+ SIGNAL(instrumentParametersChanged(InstrumentId)));
+
+ // Layout the groups left to right.
+
+ QBoxLayout* layout = new QVBoxLayout(this);
+ layout->addWidget(m_widgetStack);
+
+}
+
+InstrumentParameterBox::~InstrumentParameterBox()
+{
+ // deregister this parameter box
+ std::vector<InstrumentParameterBox*>::iterator it =
+ instrumentParamBoxes.begin();
+
+ for (; it != instrumentParamBoxes.end(); it++) {
+ if ((*it) == this) {
+ instrumentParamBoxes.erase(it);
+ break;
+ }
+ }
+}
+
+Instrument *
+InstrumentParameterBox::getSelectedInstrument()
+{
+ if (m_selectedInstrument < 0) return 0;
+ if (!m_doc) return 0;
+ return m_doc->getStudio().getInstrumentById(m_selectedInstrument);
+}
+
+QString
+InstrumentParameterBox::getPreviousBox(RosegardenParameterArea::Arrangement arrangement) const
+{
+ return i18n("Track");
+}
+
+void
+InstrumentParameterBox::setAudioMeter(float ch1, float ch2, float ch1r, float ch2r)
+{
+ m_audioInstrumentParameters->setAudioMeter(ch1, ch2, ch1r, ch2r);
+}
+
+void
+InstrumentParameterBox::setDocument(RosegardenGUIDoc* doc)
+{
+ m_doc = doc;
+ m_midiInstrumentParameters->setDocument(m_doc);
+ m_audioInstrumentParameters->setDocument(m_doc);
+}
+
+void
+InstrumentParameterBox::slotPluginSelected(InstrumentId id, int index, int plugin)
+{
+ m_audioInstrumentParameters->slotPluginSelected(id, index, plugin);
+}
+
+void
+InstrumentParameterBox::slotPluginBypassed(InstrumentId id, int index, bool bypassState)
+{
+ m_audioInstrumentParameters->slotPluginBypassed(id, index, bypassState);
+}
+
+void
+InstrumentParameterBox::useInstrument(Instrument *instrument)
+{
+ RG_DEBUG << "useInstrument() - populate Instrument\n";
+
+ if (instrument == 0) {
+ m_widgetStack->raiseWidget(m_noInstrumentParameters);
+ emit instrumentPercussionSetChanged(instrument);
+ return ;
+ }
+
+ // ok
+ if (instrument) {
+ m_selectedInstrument = instrument->getId();
+ } else {
+ m_selectedInstrument = -1;
+ }
+
+ // Hide or Show according to Instrument type
+ //
+ if (instrument->getType() == Instrument::Audio ||
+ instrument->getType() == Instrument::SoftSynth) {
+
+ m_audioInstrumentParameters->setupForInstrument(getSelectedInstrument());
+ m_widgetStack->raiseWidget(m_audioInstrumentParameters);
+
+ } else { // Midi
+
+ m_midiInstrumentParameters->setupForInstrument(getSelectedInstrument());
+ m_midiInstrumentParameters->showAdditionalControls(m_lastShowAdditionalControlsArg);
+ m_widgetStack->raiseWidget(m_midiInstrumentParameters);
+ emit instrumentPercussionSetChanged(instrument);
+
+ }
+
+}
+
+void
+InstrumentParameterBox::slotUpdateAllBoxes()
+{
+ emit instrumentPercussionSetChanged(getSelectedInstrument());
+
+ std::vector<InstrumentParameterBox*>::iterator it =
+ instrumentParamBoxes.begin();
+
+ // To update all open IPBs
+ //
+ for (; it != instrumentParamBoxes.end(); it++) {
+ if ((*it) != this && getSelectedInstrument() &&
+ (*it)->getSelectedInstrument() == getSelectedInstrument())
+ (*it)->useInstrument(getSelectedInstrument());
+ }
+}
+
+void
+InstrumentParameterBox::slotInstrumentParametersChanged(InstrumentId id)
+{
+ std::vector<InstrumentParameterBox*>::iterator it =
+ instrumentParamBoxes.begin();
+
+ blockSignals(true);
+
+ for (; it != instrumentParamBoxes.end(); it++) {
+ if ((*it)->getSelectedInstrument()) {
+ if ((*it)->getSelectedInstrument()->getId() == id) {
+ (*it)->useInstrument((*it)->getSelectedInstrument()); // refresh
+ }
+ }
+ }
+
+ blockSignals(false);
+}
+
+void
+InstrumentParameterBox::showAdditionalControls(bool showThem)
+{
+ m_midiInstrumentParameters->showAdditionalControls(showThem);
+ m_lastShowAdditionalControlsArg = showThem;
+}
+
+}
+#include "InstrumentParameterBox.moc"
diff --git a/src/gui/editors/parameters/InstrumentParameterBox.h b/src/gui/editors/parameters/InstrumentParameterBox.h
new file mode 100644
index 0000000..f406567
--- /dev/null
+++ b/src/gui/editors/parameters/InstrumentParameterBox.h
@@ -0,0 +1,126 @@
+
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
+
+/*
+ Rosegarden
+ A MIDI and audio sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_INSTRUMENTPARAMETERBOX_H_
+#define _RG_INSTRUMENTPARAMETERBOX_H_
+
+#include "base/MidiProgram.h"
+#include "RosegardenParameterArea.h"
+#include "RosegardenParameterBox.h"
+#include <qstring.h>
+#include <vector>
+
+
+class QWidgetStack;
+class QWidget;
+class QFrame;
+
+
+namespace Rosegarden
+{
+
+class RosegardenGUIDoc;
+class MIDIInstrumentParameterPanel;
+class Instrument;
+class AudioInstrumentParameterPanel;
+
+
+/**
+ * Display and allow modification of Instrument parameters
+ */
+class InstrumentParameterBox : public RosegardenParameterBox
+{
+Q_OBJECT
+
+public:
+ InstrumentParameterBox(RosegardenGUIDoc *doc,
+ QWidget *parent = 0);
+ ~InstrumentParameterBox();
+
+ void useInstrument(Instrument *instrument);
+
+ Instrument* getSelectedInstrument();
+
+ void setAudioMeter(float dBleft, float dBright,
+ float recDBleft, float recDBright);
+
+ void setDocument(RosegardenGUIDoc* doc);
+
+ virtual void showAdditionalControls(bool showThem);
+
+ virtual QString getPreviousBox(RosegardenParameterArea::Arrangement) const;
+
+public slots:
+
+ // To update all InstrumentParameterBoxen for an Instrument. Called
+ // from one of the parameter panels when something changes.
+ //
+ void slotUpdateAllBoxes();
+
+ // Update InstrumentParameterBoxes that are showing a given instrument.
+ // Called from the Outside.
+ //
+ void slotInstrumentParametersChanged(InstrumentId id);
+
+ // From Plugin dialog
+ //
+ void slotPluginSelected(InstrumentId id, int index, int plugin);
+ void slotPluginBypassed(InstrumentId id, int pluginIndex, bool bp);
+
+signals:
+
+ void changeInstrumentLabel(InstrumentId id, QString label);
+
+ void selectPlugin(QWidget*, InstrumentId id, int index);
+ void showPluginGUI(InstrumentId id, int index);
+
+ void instrumentParametersChanged(InstrumentId);
+ void instrumentPercussionSetChanged(Instrument *);
+
+protected:
+
+ //--------------- Data members ---------------------------------
+ QWidgetStack *m_widgetStack;
+ QFrame *m_noInstrumentParameters;
+ MIDIInstrumentParameterPanel *m_midiInstrumentParameters;
+ AudioInstrumentParameterPanel *m_audioInstrumentParameters;
+
+ // -1 if no instrument, InstrumentId otherwise
+ int m_selectedInstrument;
+
+ // So we can setModified()
+ //
+ RosegardenGUIDoc *m_doc;
+ bool m_lastShowAdditionalControlsArg;
+};
+
+// Global references
+//
+static std::vector<InstrumentParameterBox*> instrumentParamBoxes;
+
+
+}
+
+#endif
diff --git a/src/gui/editors/parameters/InstrumentParameterPanel.cpp b/src/gui/editors/parameters/InstrumentParameterPanel.cpp
new file mode 100644
index 0000000..9437daf
--- /dev/null
+++ b/src/gui/editors/parameters/InstrumentParameterPanel.cpp
@@ -0,0 +1,61 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
+
+/*
+ Rosegarden
+ A MIDI and audio sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "InstrumentParameterPanel.h"
+
+#include "base/Instrument.h"
+#include "document/RosegardenGUIDoc.h"
+#include <ksqueezedtextlabel.h>
+#include <qfontmetrics.h>
+#include <qframe.h>
+#include <qlabel.h>
+#include <qwidget.h>
+
+
+namespace Rosegarden
+{
+
+InstrumentParameterPanel::InstrumentParameterPanel(RosegardenGUIDoc *doc,
+ QWidget* parent)
+ : QFrame(parent),
+ m_instrumentLabel(new KSqueezedTextLabel(this)),
+ m_selectedInstrument(0),
+ m_doc(doc)
+{
+ QFontMetrics metrics(m_instrumentLabel->fontMetrics());
+ int width25 = metrics.width("1234567890123456789012345");
+
+ m_instrumentLabel->setFixedWidth(width25);
+ m_instrumentLabel->setAlignment(Qt::AlignCenter);
+}
+
+void
+InstrumentParameterPanel::setDocument(RosegardenGUIDoc* doc)
+{
+ m_doc = doc;
+}
+
+}
+#include "InstrumentParameterPanel.moc"
diff --git a/src/gui/editors/parameters/InstrumentParameterPanel.h b/src/gui/editors/parameters/InstrumentParameterPanel.h
new file mode 100644
index 0000000..9a794d0
--- /dev/null
+++ b/src/gui/editors/parameters/InstrumentParameterPanel.h
@@ -0,0 +1,78 @@
+
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
+
+/*
+ Rosegarden
+ A MIDI and audio sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_INSTRUMENTPARAMETERPANEL_H_
+#define _RG_INSTRUMENTPARAMETERPANEL_H_
+
+#include <qframe.h>
+#include <vector>
+#include <utility>
+
+class QWidget;
+class QLabel;
+
+
+namespace Rosegarden
+{
+
+class RosegardenGUIDoc;
+class Instrument;
+class Rotary;
+
+typedef std::pair<Rotary *, QLabel *> RotaryPair;
+typedef std::vector<std::pair<int, RotaryPair> > RotaryMap;
+
+
+////////////////////////////////////////////////////////////////////////
+
+class InstrumentParameterPanel : public QFrame
+{
+ Q_OBJECT
+public:
+ InstrumentParameterPanel(RosegardenGUIDoc *doc, QWidget* parent);
+
+ virtual ~InstrumentParameterPanel() {};
+
+ virtual void setupForInstrument(Instrument*) = 0;
+
+ void setDocument(RosegardenGUIDoc* doc);
+
+ void showAdditionalControls(bool showThem);
+
+signals:
+ void updateAllBoxes();
+
+protected:
+ //--------------- Data members ---------------------------------
+ QLabel *m_instrumentLabel;
+ Instrument *m_selectedInstrument;
+ RosegardenGUIDoc *m_doc;
+};
+
+
+
+}
+
+#endif
diff --git a/src/gui/editors/parameters/MIDIInstrumentParameterPanel.cpp b/src/gui/editors/parameters/MIDIInstrumentParameterPanel.cpp
new file mode 100644
index 0000000..fcd4247
--- /dev/null
+++ b/src/gui/editors/parameters/MIDIInstrumentParameterPanel.cpp
@@ -0,0 +1,1175 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
+
+/*
+ Rosegarden
+ A MIDI and audio sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "MIDIInstrumentParameterPanel.h"
+#include <qlayout.h>
+
+#include "sound/Midi.h"
+#include <klocale.h>
+#include "misc/Debug.h"
+#include "misc/Strings.h"
+#include "base/Colour.h"
+#include "base/Composition.h"
+#include "base/ControlParameter.h"
+#include "base/Instrument.h"
+#include "base/MidiDevice.h"
+#include "base/MidiProgram.h"
+#include "document/RosegardenGUIDoc.h"
+#include "gui/studio/StudioControl.h"
+#include "gui/widgets/Rotary.h"
+#include "InstrumentParameterPanel.h"
+#include "sound/MappedEvent.h"
+#include "sound/MappedInstrument.h"
+#include <kcombobox.h>
+#include <ksqueezedtextlabel.h>
+#include <qcheckbox.h>
+#include <qcolor.h>
+#include <qfontmetrics.h>
+#include <qframe.h>
+#include <qhbox.h>
+#include <qlabel.h>
+#include <qregexp.h>
+#include <qsignalmapper.h>
+#include <qstring.h>
+#include <qwidget.h>
+#include <algorithm>
+
+
+namespace Rosegarden
+{
+
+MIDIInstrumentParameterPanel::MIDIInstrumentParameterPanel(RosegardenGUIDoc *doc, QWidget* parent):
+ InstrumentParameterPanel(doc, parent),
+ m_rotaryFrame(0),
+ m_rotaryMapper(new QSignalMapper(this))
+{
+ m_mainGrid = new QGridLayout(this, 10, 3, 2, 1);
+
+ m_connectionLabel = new KSqueezedTextLabel(this);
+ m_bankValue = new KComboBox(this);
+ m_channelValue = new KComboBox(this);
+ m_programValue = new KComboBox(this);
+ m_variationValue = new KComboBox(this);
+ m_bankCheckBox = new QCheckBox(this);
+ m_programCheckBox = new QCheckBox(this);
+ m_variationCheckBox = new QCheckBox(this);
+ m_percussionCheckBox = new QCheckBox(this);
+
+ m_bankValue->setSizeLimit(20);
+ m_programValue->setSizeLimit(20);
+ m_variationValue->setSizeLimit(20);
+
+ m_bankLabel = new QLabel(i18n("Bank"), this);
+ m_variationLabel = new QLabel(i18n("Variation"), this);
+ m_programLabel = new QLabel(i18n("Program"), this);
+ QLabel *channelLabel = new QLabel(i18n("Channel out"), this);
+ QLabel *percussionLabel = new QLabel(i18n("Percussion"), this);
+
+ // Ensure a reasonable amount of space in the program dropdowns even
+ // if no instrument initially selected
+ QFontMetrics metrics(m_programValue->font());
+ int width22 = metrics.width("1234567890123456789012");
+ int width25 = metrics.width("1234567890123456789012345");
+
+ m_bankValue->setMinimumWidth(width22);
+ m_programValue->setMinimumWidth(width22);
+ m_variationValue->setMinimumWidth(width22);
+
+ m_connectionLabel->setFixedWidth(width25);
+ m_connectionLabel->setAlignment(Qt::AlignCenter);
+
+ // Configure the empty final row to accomodate any extra vertical space.
+
+ m_mainGrid->setRowStretch(m_mainGrid->numRows() - 1, 1);
+
+
+ m_mainGrid->setColStretch(2, 1);
+
+ m_mainGrid->addMultiCellWidget(m_instrumentLabel, 0, 0, 0, 2, AlignCenter);
+ m_mainGrid->addMultiCellWidget(m_connectionLabel, 1, 1, 0, 2, AlignCenter);
+
+ m_mainGrid->addMultiCellWidget(channelLabel, 2, 2, 0, 1, AlignLeft);
+ m_mainGrid->addWidget(m_channelValue, 2, 2, AlignRight);
+
+ m_mainGrid->addMultiCellWidget(percussionLabel, 3, 3, 0, 1, AlignLeft);
+ m_mainGrid->addWidget(m_percussionCheckBox, 3, 2, AlignRight);
+
+ m_mainGrid->addWidget(m_bankLabel, 4, 0, AlignLeft);
+ m_mainGrid->addWidget(m_bankCheckBox, 4, 1, AlignRight);
+ m_mainGrid->addWidget(m_bankValue, 4, 2, AlignRight);
+
+ m_mainGrid->addWidget(m_programLabel, 5, 0, AlignLeft);
+ m_mainGrid->addWidget(m_programCheckBox, 5, 1, AlignRight);
+ m_mainGrid->addWidget(m_programValue, 5, 2, AlignRight);
+
+ m_mainGrid->addWidget(m_variationLabel, 6, 0);
+ m_mainGrid->addWidget(m_variationCheckBox, 6, 1);
+ m_mainGrid->addWidget(m_variationValue, 6, 2, AlignRight);
+
+ // Populate channel lists
+ //
+ for (int i = 0; i < 16; i++) {
+ m_channelValue->insertItem(QString("%1").arg(i + 1));
+ }
+
+ m_channelValue->setSizeLimit(16);
+
+ // Disable these by default - they are activate by their
+ // checkboxes
+ //
+ m_programValue->setDisabled(true);
+ m_bankValue->setDisabled(true);
+ m_variationValue->setDisabled(true);
+
+ // Only active if we have an Instrument selected
+ //
+ m_percussionCheckBox->setDisabled(true);
+ m_programCheckBox->setDisabled(true);
+ m_bankCheckBox->setDisabled(true);
+ m_variationCheckBox->setDisabled(true);
+
+ // Connect up the toggle boxes
+ //
+ connect(m_percussionCheckBox, SIGNAL(toggled(bool)),
+ this, SLOT(slotTogglePercussion(bool)));
+
+ connect(m_programCheckBox, SIGNAL(toggled(bool)),
+ this, SLOT(slotToggleProgramChange(bool)));
+
+ connect(m_bankCheckBox, SIGNAL(toggled(bool)),
+ this, SLOT(slotToggleBank(bool)));
+
+ connect(m_variationCheckBox, SIGNAL(toggled(bool)),
+ this, SLOT(slotToggleVariation(bool)));
+
+
+ // Connect activations
+ //
+ connect(m_bankValue, SIGNAL(activated(int)),
+ this, SLOT(slotSelectBank(int)));
+
+ connect(m_variationValue, SIGNAL(activated(int)),
+ this, SLOT(slotSelectVariation(int)));
+
+ connect(m_programValue, SIGNAL(activated(int)),
+ this, SLOT(slotSelectProgram(int)));
+
+ connect(m_channelValue, SIGNAL(activated(int)),
+ this, SLOT(slotSelectChannel(int)));
+
+ // don't select any of the options in any dropdown
+ m_programValue->setCurrentItem( -1);
+ m_bankValue->setCurrentItem( -1);
+ m_channelValue->setCurrentItem( -1);
+ m_variationValue->setCurrentItem( -1);
+
+ connect(m_rotaryMapper, SIGNAL(mapped(int)),
+ this, SLOT(slotControllerChanged(int)));
+}
+
+void
+MIDIInstrumentParameterPanel::setupForInstrument(Instrument *instrument)
+{
+ RG_DEBUG << "MIDIInstrumentParameterPanel::setupForInstrument" << endl;
+ MidiDevice *md = dynamic_cast<MidiDevice*>
+ (instrument->getDevice());
+ if (!md) {
+ RG_DEBUG << "WARNING: MIDIInstrumentParameterPanel::setupForInstrument:"
+ << " No MidiDevice for Instrument "
+ << instrument->getId() << endl;
+ return ;
+ }
+
+ m_selectedInstrument = instrument;
+
+ // Set instrument name
+ //
+ m_instrumentLabel->setText(strtoqstr(instrument->getPresentationName()));
+
+ // Set Studio Device name
+ //
+ QString connection(strtoqstr(md->getConnection()));
+ if (connection == "") {
+ m_connectionLabel->setText(i18n("[ %1 ]").arg(i18n("No connection")));
+ } else {
+
+ // remove trailing "(duplex)", "(read only)", "(write only)" etc
+ connection.replace(QRegExp("\\s*\\([^)0-9]+\\)\\s*$"), "");
+
+ QString text = i18n("[ %1 ]").arg(connection);
+ /*QString origText(text);
+
+ QFontMetrics metrics(m_connectionLabel->fontMetrics());
+ int maxwidth = metrics.width
+ ("Program: [X] Acoustic Grand Piano 123");// kind of arbitrary!
+
+ int hlen = text.length() / 2;
+ while (metrics.width(text) > maxwidth && text.length() > 10) {
+ --hlen;
+ text = origText.left(hlen) + "..." + origText.right(hlen);
+ }
+
+ if (text.length() > origText.length() - 7) text = origText;*/
+ m_connectionLabel->setText(text);
+ }
+
+ // Enable all check boxes
+ //
+ m_percussionCheckBox->setDisabled(false);
+ m_programCheckBox->setDisabled(false);
+ m_bankCheckBox->setDisabled(false);
+ m_variationCheckBox->setDisabled(false);
+
+ // Activate all checkboxes
+ //
+ m_percussionCheckBox->setChecked(instrument->isPercussion());
+ m_programCheckBox->setChecked(instrument->sendsProgramChange());
+ m_bankCheckBox->setChecked(instrument->sendsBankSelect());
+ m_variationCheckBox->setChecked(instrument->sendsBankSelect());
+
+ // Basic parameters
+ //
+ m_channelValue->setCurrentItem((int)instrument->getMidiChannel());
+
+ // Check for program change
+ //
+ populateBankList();
+ populateProgramList();
+ populateVariationList();
+
+ // Setup the ControlParameters
+ //
+ setupControllers(md);
+
+ // Set all the positions by controller number
+ //
+ for (RotaryMap::iterator it = m_rotaries.begin() ;
+ it != m_rotaries.end(); ++it) {
+ MidiByte value = 0;
+
+ // Special cases
+ //
+ if (it->first == MIDI_CONTROLLER_PAN)
+ value = int(instrument->getPan());
+ else if (it->first == MIDI_CONTROLLER_VOLUME)
+ value = int(instrument->getVolume());
+ else {
+ try {
+ value = instrument->getControllerValue(
+ MidiByte(it->first));
+ } catch (...) {
+ continue;
+ }
+ }
+
+ setRotaryToValue(it->first, int(value));
+ }
+}
+
+void
+MIDIInstrumentParameterPanel::setupControllers(MidiDevice *md)
+{
+ if (!m_rotaryFrame) {
+ m_rotaryFrame = new QFrame(this);
+ m_mainGrid->addMultiCellWidget(m_rotaryFrame, 8, 8, 0, 2, Qt::AlignHCenter);
+ m_rotaryGrid = new QGridLayout(m_rotaryFrame, 10, 3, 8, 1);
+ m_rotaryGrid->addItem(new QSpacerItem(10, 4), 0, 1);
+ }
+
+ // To cut down on flicker, we avoid destroying and recreating
+ // widgets as far as possible here. If a label already exists,
+ // we just set its text; if a rotary exists, we only replace it
+ // if we actually need a different one.
+
+ Composition &comp = m_doc->getComposition();
+ ControlList list = md->getControlParameters();
+
+ // sort by IPB position
+ //
+ std::sort(list.begin(), list.end(),
+ ControlParameter::ControlPositionCmp());
+
+ int count = 0;
+ RotaryMap::iterator rmi = m_rotaries.begin();
+
+ for (ControlList::iterator it = list.begin();
+ it != list.end(); ++it) {
+ if (it->getIPBPosition() == -1)
+ continue;
+
+ // Get the knob colour - only if the colour is non-default (>0)
+ //
+ QColor knobColour = Qt::black; // special case for Rotary
+ if (it->getColourIndex() > 0) {
+ Colour c =
+ comp.getGeneralColourMap().getColourByIndex
+ (it->getColourIndex());
+ knobColour = QColor(c.getRed(), c.getGreen(), c.getBlue());
+ }
+
+ Rotary *rotary = 0;
+
+ if (rmi != m_rotaries.end()) {
+
+ // Update the controller number that is associated with the
+ // existing rotary widget.
+
+ rmi->first = it->getControllerValue();
+
+ // Update the properties of the existing rotary widget.
+
+ rotary = rmi->second.first;
+ int redraw = 0; // 1 -> position, 2 -> all
+
+ if (rotary->getMinValue() != it->getMin()) {
+ rotary->setMinValue(it->getMin());
+ redraw = 1;
+ }
+ if (rotary->getMaxValue() != it->getMax()) {
+ rotary->setMaxValue(it->getMax());
+ redraw = 1;
+ }
+ if (rotary->getKnobColour() != knobColour) {
+ rotary->setKnobColour(knobColour);
+ redraw = 2;
+ }
+ if (redraw == 1 || rotary->getPosition() != it->getDefault()) {
+ rotary->setPosition(it->getDefault());
+ if (redraw == 1)
+ redraw = 0;
+ }
+ if (redraw == 2) {
+ rotary->repaint();
+ }
+
+ // Update the controller name that is associated with
+ // with the existing rotary widget.
+
+ QLabel *label = rmi->second.second;
+ label->setText(strtoqstr(it->getName()));
+
+ ++rmi;
+
+ } else {
+
+ QHBox *hbox = new QHBox(m_rotaryFrame);
+ hbox->setSpacing(8);
+
+ float smallStep = 1.0;
+
+ float bigStep = 5.0;
+ if (it->getMax() - it->getMin() < 10)
+ bigStep = 1.0;
+ else if (it->getMax() - it->getMin() < 20)
+ bigStep = 2.0;
+
+ rotary = new Rotary
+ (hbox,
+ it->getMin(),
+ it->getMax(),
+ smallStep,
+ bigStep,
+ it->getDefault(),
+ 20,
+ Rotary::NoTicks,
+ false,
+ it->getDefault() == 64); //!!! hacky
+
+ rotary->setKnobColour(knobColour);
+
+ // Add a label
+ QLabel *label = new KSqueezedTextLabel(strtoqstr(it->getName()), hbox);
+
+ RG_DEBUG << "Adding new widget at " << (count / 2) << "," << (count % 2) << endl;
+
+ // Add the compound widget
+ //
+ m_rotaryGrid->addWidget(hbox, count / 2, (count % 2) * 2, AlignLeft);
+ hbox->show();
+
+ // Add to list
+ //
+ m_rotaries.push_back(std::pair<int, RotaryPair>
+ (it->getControllerValue(),
+ RotaryPair(rotary, label)));
+
+ // Connect
+ //
+ connect(rotary, SIGNAL(valueChanged(float)),
+ m_rotaryMapper, SLOT(map()));
+
+ rmi = m_rotaries.end();
+ }
+
+ // Add signal mapping
+ //
+ m_rotaryMapper->setMapping(rotary,
+ int(it->getControllerValue()));
+
+ count++;
+ }
+
+ if (rmi != m_rotaries.end()) {
+ for (RotaryMap::iterator rmj = rmi; rmj != m_rotaries.end(); ++rmj) {
+ delete rmj->second.first;
+ delete rmj->second.second;
+ }
+ m_rotaries = std::vector<std::pair<int, RotaryPair> >
+ (m_rotaries.begin(), rmi);
+ }
+
+ m_rotaryFrame->show();
+}
+
+void
+MIDIInstrumentParameterPanel::setRotaryToValue(int controller, int value)
+{
+ /*
+ RG_DEBUG << "MIDIInstrumentParameterPanel::setRotaryToValue - "
+ << "controller = " << controller
+ << ", value = " << value << std::endl;
+ */
+
+ for (RotaryMap::iterator it = m_rotaries.begin() ; it != m_rotaries.end(); ++it) {
+ if (it->first == controller) {
+ it->second.first->setPosition(float(value));
+ return ;
+ }
+ }
+}
+
+void
+MIDIInstrumentParameterPanel::slotSelectChannel(int index)
+{
+ if (m_selectedInstrument == 0)
+ return ;
+
+ m_selectedInstrument->setMidiChannel(index);
+
+ // don't use the emit - use this method instead
+ StudioControl::sendMappedInstrument(
+ MappedInstrument(m_selectedInstrument));
+ emit updateAllBoxes();
+}
+
+void
+MIDIInstrumentParameterPanel::populateBankList()
+{
+ if (m_selectedInstrument == 0)
+ return ;
+
+ m_bankValue->clear();
+ m_banks.clear();
+
+ MidiDevice *md = dynamic_cast<MidiDevice*>
+ (m_selectedInstrument->getDevice());
+ if (!md) {
+ RG_DEBUG << "WARNING: MIDIInstrumentParameterPanel::populateBankList:"
+ << " No MidiDevice for Instrument "
+ << m_selectedInstrument->getId() << endl;
+ return ;
+ }
+
+ int currentBank = -1;
+ BankList banks;
+
+ /*
+ RG_DEBUG << "MIDIInstrumentParameterPanel::populateBankList: "
+ << "variation type is " << md->getVariationType() << endl;
+ */
+
+ if (md->getVariationType() == MidiDevice::NoVariations) {
+
+ banks = md->getBanks(m_selectedInstrument->isPercussion());
+
+ if (!banks.empty()) {
+ if (m_bankLabel->isHidden()) {
+ m_bankLabel->show();
+ m_bankCheckBox->show();
+ m_bankValue->show();
+ }
+ } else {
+ m_bankLabel->hide();
+ m_bankCheckBox->hide();
+ m_bankValue->hide();
+ }
+
+ for (unsigned int i = 0; i < banks.size(); ++i) {
+ if (m_selectedInstrument->getProgram().getBank() == banks[i]) {
+ currentBank = i;
+ }
+ }
+
+ } else {
+
+ MidiByteList bytes;
+ bool useMSB = (md->getVariationType() == MidiDevice::VariationFromLSB);
+
+ if (useMSB) {
+ bytes = md->getDistinctMSBs(m_selectedInstrument->isPercussion());
+ } else {
+ bytes = md->getDistinctLSBs(m_selectedInstrument->isPercussion());
+ }
+
+ if (bytes.size() < 2) {
+ if (!m_bankLabel->isHidden()) {
+ m_bankLabel->hide();
+ m_bankCheckBox->hide();
+ m_bankValue->hide();
+ }
+ } else {
+ if (m_bankLabel->isHidden()) {
+ m_bankLabel->show();
+ m_bankCheckBox->show();
+ m_bankValue->show();
+ }
+ }
+
+ if (useMSB) {
+ for (unsigned int i = 0; i < bytes.size(); ++i) {
+ BankList bl = md->getBanksByMSB
+ (m_selectedInstrument->isPercussion(), bytes[i]);
+ RG_DEBUG << "MIDIInstrumentParameterPanel::populateBankList: have " << bl.size() << " variations for msb " << bytes[i] << endl;
+
+ if (bl.size() == 0)
+ continue;
+ if (m_selectedInstrument->getMSB() == bytes[i]) {
+ currentBank = banks.size();
+ }
+ banks.push_back(bl[0]);
+ }
+ } else {
+ for (unsigned int i = 0; i < bytes.size(); ++i) {
+ BankList bl = md->getBanksByLSB
+ (m_selectedInstrument->isPercussion(), bytes[i]);
+ RG_DEBUG << "MIDIInstrumentParameterPanel::populateBankList: have " << bl.size() << " variations for lsb " << bytes[i] << endl;
+ if (bl.size() == 0)
+ continue;
+ if (m_selectedInstrument->getLSB() == bytes[i]) {
+ currentBank = banks.size();
+ }
+ banks.push_back(bl[0]);
+ }
+ }
+ }
+
+ for (BankList::const_iterator i = banks.begin();
+ i != banks.end(); ++i) {
+ m_banks.push_back(*i);
+ m_bankValue->insertItem(strtoqstr(i->getName()));
+ }
+
+ m_bankValue->setEnabled(m_selectedInstrument->sendsBankSelect());
+
+ if (currentBank < 0 && !banks.empty()) {
+ m_bankValue->setCurrentItem(0);
+ slotSelectBank(0);
+ } else {
+ m_bankValue->setCurrentItem(currentBank);
+ }
+}
+
+void
+MIDIInstrumentParameterPanel::populateProgramList()
+{
+ if (m_selectedInstrument == 0)
+ return ;
+
+ m_programValue->clear();
+ m_programs.clear();
+
+ MidiDevice *md = dynamic_cast<MidiDevice*>
+ (m_selectedInstrument->getDevice());
+ if (!md) {
+ RG_DEBUG << "WARNING: MIDIInstrumentParameterPanel::populateProgramList: No MidiDevice for Instrument "
+ << m_selectedInstrument->getId() << endl;
+ return ;
+ }
+
+ /*
+ RG_DEBUG << "MIDIInstrumentParameterPanel::populateProgramList:"
+ << " variation type is " << md->getVariationType() << endl;
+ */
+
+ MidiBank bank( m_selectedInstrument->isPercussion(),
+ m_selectedInstrument->getMSB(),
+ m_selectedInstrument->getLSB());
+
+ if (m_selectedInstrument->sendsBankSelect()) {
+ bank = m_selectedInstrument->getProgram().getBank();
+ }
+
+ int currentProgram = -1;
+
+ ProgramList programs = md->getPrograms(bank);
+
+ if (!programs.empty()) {
+ if (m_programLabel->isHidden()) {
+ m_programLabel->show();
+ m_programCheckBox->show();
+ m_programValue->show();
+ }
+ } else {
+ m_programLabel->hide();
+ m_programCheckBox->hide();
+ m_programValue->hide();
+ }
+
+ for (unsigned int i = 0; i < programs.size(); ++i) {
+ std::string programName = programs[i].getName();
+ if (programName != "") {
+ m_programValue->insertItem(QString("%1. %2")
+ .arg(programs[i].getProgram() + 1)
+ .arg(strtoqstr(programName)));
+ if (m_selectedInstrument->getProgram() == programs[i]) {
+ currentProgram = m_programs.size();
+ }
+ m_programs.push_back(programs[i]);
+ }
+ }
+
+ m_programValue->setEnabled(m_selectedInstrument->sendsProgramChange());
+
+ if (currentProgram < 0 && !m_programs.empty()) {
+ m_programValue->setCurrentItem(0);
+ slotSelectProgram(0);
+ } else {
+ m_programValue->setCurrentItem(currentProgram);
+
+ // Ensure that stored program change value is same as the one
+ // we're now showing (BUG 937371)
+ //
+ if (!m_programs.empty()) {
+ m_selectedInstrument->setProgramChange
+ ((m_programs[m_programValue->currentItem()]).getProgram());
+ }
+ }
+}
+
+void
+MIDIInstrumentParameterPanel::populateVariationList()
+{
+ if (m_selectedInstrument == 0)
+ return ;
+
+ m_variationValue->clear();
+ m_variations.clear();
+
+ MidiDevice *md = dynamic_cast<MidiDevice*>
+ (m_selectedInstrument->getDevice());
+ if (!md) {
+ RG_DEBUG << "WARNING: MIDIInstrumentParameterPanel::populateVariationList: No MidiDevice for Instrument "
+ << m_selectedInstrument->getId() << endl;
+ return ;
+ }
+
+ /*
+ RG_DEBUG << "MIDIInstrumentParameterPanel::populateVariationList:"
+ << " variation type is " << md->getVariationType() << endl;
+ */
+
+ if (md->getVariationType() == MidiDevice::NoVariations) {
+ if (!m_variationLabel->isHidden()) {
+ m_variationLabel->hide();
+ m_variationCheckBox->hide();
+ m_variationValue->hide();
+ }
+ return ;
+ }
+
+ bool useMSB = (md->getVariationType() == MidiDevice::VariationFromMSB);
+ MidiByteList variations;
+
+ if (useMSB) {
+ MidiByte lsb = m_selectedInstrument->getLSB();
+ variations = md->getDistinctMSBs(m_selectedInstrument->isPercussion(),
+ lsb);
+ RG_DEBUG << "MIDIInstrumentParameterPanel::populateVariationList: have " << variations.size() << " variations for lsb " << lsb << endl;
+
+ } else {
+ MidiByte msb = m_selectedInstrument->getMSB();
+ variations = md->getDistinctLSBs(m_selectedInstrument->isPercussion(),
+ msb);
+ RG_DEBUG << "MIDIInstrumentParameterPanel::populateVariationList: have " << variations.size() << " variations for msb " << msb << endl;
+ }
+
+ m_variationValue->setCurrentItem( -1);
+
+ MidiProgram defaultProgram;
+
+ if (useMSB) {
+ defaultProgram = MidiProgram
+ (MidiBank(m_selectedInstrument->isPercussion(),
+ 0,
+ m_selectedInstrument->getLSB()),
+ m_selectedInstrument->getProgramChange());
+ } else {
+ defaultProgram = MidiProgram
+ (MidiBank(m_selectedInstrument->isPercussion(),
+ m_selectedInstrument->getMSB(),
+ 0),
+ m_selectedInstrument->getProgramChange());
+ }
+ std::string defaultProgramName = md->getProgramName(defaultProgram);
+
+ int currentVariation = -1;
+
+ for (unsigned int i = 0; i < variations.size(); ++i) {
+
+ MidiProgram program;
+
+ if (useMSB) {
+ program = MidiProgram
+ (MidiBank(m_selectedInstrument->isPercussion(),
+ variations[i],
+ m_selectedInstrument->getLSB()),
+ m_selectedInstrument->getProgramChange());
+ } else {
+ program = MidiProgram
+ (MidiBank(m_selectedInstrument->isPercussion(),
+ m_selectedInstrument->getMSB(),
+ variations[i]),
+ m_selectedInstrument->getProgramChange());
+ }
+
+ std::string programName = md->getProgramName(program);
+
+ if (programName != "") { // yes, that is how you know whether it exists
+ /*
+ m_variationValue->insertItem(programName == defaultProgramName ?
+ i18n("(default)") :
+ strtoqstr(programName));
+ */
+ m_variationValue->insertItem(QString("%1. %2")
+ .arg(variations[i] + 1)
+ .arg(strtoqstr(programName)));
+ if (m_selectedInstrument->getProgram() == program) {
+ currentVariation = m_variations.size();
+ }
+ m_variations.push_back(variations[i]);
+ }
+ }
+
+ if (currentVariation < 0 && !m_variations.empty()) {
+ m_variationValue->setCurrentItem(0);
+ slotSelectVariation(0);
+ } else {
+ m_variationValue->setCurrentItem(currentVariation);
+ }
+
+ if (m_variations.size() < 2) {
+ if (!m_variationLabel->isHidden()) {
+ m_variationLabel->hide();
+ m_variationCheckBox->hide();
+ m_variationValue->hide();
+ }
+
+ } else {
+ //!!! seem to have problems here -- the grid layout doesn't
+ //like us adding stuff in the middle so if we go from 1
+ //visible row (say program) to 2 (program + variation) the
+ //second one overlaps the control knobs
+
+ if (m_variationLabel->isHidden()) {
+ m_variationLabel->show();
+ m_variationCheckBox->show();
+ m_variationValue->show();
+ }
+
+ if (m_programValue->width() > m_variationValue->width()) {
+ m_variationValue->setMinimumWidth(m_programValue->width());
+ } else {
+ m_programValue->setMinimumWidth(m_variationValue->width());
+ }
+ }
+
+ m_variationValue->setEnabled(m_selectedInstrument->sendsBankSelect());
+}
+
+void
+MIDIInstrumentParameterPanel::slotTogglePercussion(bool value)
+{
+ if (m_selectedInstrument == 0) {
+ m_percussionCheckBox->setChecked(false);
+ emit updateAllBoxes();
+ return ;
+ }
+
+ m_selectedInstrument->setPercussion(value);
+
+ populateBankList();
+ populateProgramList();
+ populateVariationList();
+
+ sendBankAndProgram();
+
+ emit changeInstrumentLabel(m_selectedInstrument->getId(),
+ strtoqstr(m_selectedInstrument->
+ getProgramName()));
+ emit updateAllBoxes();
+
+ emit instrumentParametersChanged(m_selectedInstrument->getId());
+}
+
+void
+MIDIInstrumentParameterPanel::slotToggleBank(bool value)
+{
+ if (m_selectedInstrument == 0) {
+ m_bankCheckBox->setChecked(false);
+ emit updateAllBoxes();
+ return ;
+ }
+
+ m_variationCheckBox->setChecked(value);
+ m_selectedInstrument->setSendBankSelect(value);
+
+ m_bankValue->setDisabled(!value);
+ populateBankList();
+ populateProgramList();
+ populateVariationList();
+
+ sendBankAndProgram();
+
+ emit changeInstrumentLabel(m_selectedInstrument->getId(),
+ strtoqstr(m_selectedInstrument->
+ getProgramName()));
+ emit updateAllBoxes();
+
+ emit instrumentParametersChanged(m_selectedInstrument->getId());
+}
+
+void
+MIDIInstrumentParameterPanel::slotToggleProgramChange(bool value)
+{
+ if (m_selectedInstrument == 0) {
+ m_programCheckBox->setChecked(false);
+ emit updateAllBoxes();
+ return ;
+ }
+
+ m_selectedInstrument->setSendProgramChange(value);
+
+ m_programValue->setDisabled(!value);
+ populateProgramList();
+ populateVariationList();
+
+ if (value)
+ sendBankAndProgram();
+
+ emit changeInstrumentLabel(m_selectedInstrument->getId(),
+ strtoqstr(m_selectedInstrument->
+ getProgramName()));
+ emit updateAllBoxes();
+
+ emit instrumentParametersChanged(m_selectedInstrument->getId());
+}
+
+void
+MIDIInstrumentParameterPanel::slotToggleVariation(bool value)
+{
+ if (m_selectedInstrument == 0) {
+ m_variationCheckBox->setChecked(false);
+ emit updateAllBoxes();
+ return ;
+ }
+
+ m_bankCheckBox->setChecked(value);
+ m_selectedInstrument->setSendBankSelect(value);
+
+ m_variationValue->setDisabled(!value);
+ populateVariationList();
+
+ sendBankAndProgram();
+
+ emit changeInstrumentLabel(m_selectedInstrument->getId(),
+ strtoqstr(m_selectedInstrument->
+ getProgramName()));
+ emit updateAllBoxes();
+
+ emit instrumentParametersChanged(m_selectedInstrument->getId());
+}
+
+void
+MIDIInstrumentParameterPanel::slotSelectBank(int index)
+{
+ if (m_selectedInstrument == 0)
+ return ;
+
+ MidiDevice *md = dynamic_cast<MidiDevice*>
+ (m_selectedInstrument->getDevice());
+ if (!md) {
+ RG_DEBUG << "WARNING: MIDIInstrumentParameterPanel::slotSelectBank: No MidiDevice for Instrument "
+ << m_selectedInstrument->getId() << endl;
+ return ;
+ }
+
+ const MidiBank *bank = &m_banks[index];
+
+ bool change = false;
+
+ if (md->getVariationType() != MidiDevice::VariationFromLSB) {
+ if (m_selectedInstrument->getLSB() != bank->getLSB()) {
+ m_selectedInstrument->setLSB(bank->getLSB());
+ change = true;
+ }
+ }
+ if (md->getVariationType() != MidiDevice::VariationFromMSB) {
+ if (m_selectedInstrument->getMSB() != bank->getMSB()) {
+ m_selectedInstrument->setMSB(bank->getMSB());
+ change = true;
+ }
+ }
+
+ populateProgramList();
+
+ if (change) {
+ sendBankAndProgram();
+ emit updateAllBoxes();
+ }
+
+ emit instrumentParametersChanged(m_selectedInstrument->getId());
+}
+
+void
+MIDIInstrumentParameterPanel::slotSelectProgram(int index)
+{
+ const MidiProgram *prg = &m_programs[index];
+ if (prg == 0) {
+ RG_DEBUG << "program change not found in bank" << endl;
+ return ;
+ }
+
+ bool change = false;
+ if (m_selectedInstrument->getProgramChange() != prg->getProgram()) {
+ m_selectedInstrument->setProgramChange(prg->getProgram());
+ change = true;
+ }
+
+ populateVariationList();
+
+ if (change) {
+ sendBankAndProgram();
+ emit changeInstrumentLabel(m_selectedInstrument->getId(),
+ strtoqstr(m_selectedInstrument->
+ getProgramName()));
+ emit updateAllBoxes();
+ }
+
+ emit instrumentParametersChanged(m_selectedInstrument->getId());
+}
+
+void
+MIDIInstrumentParameterPanel::slotSelectVariation(int index)
+{
+ MidiDevice *md = dynamic_cast<MidiDevice*>
+ (m_selectedInstrument->getDevice());
+ if (!md) {
+ RG_DEBUG << "WARNING: MIDIInstrumentParameterPanel::slotSelectVariation: No MidiDevice for Instrument "
+ << m_selectedInstrument->getId() << endl;
+ return ;
+ }
+
+ if (index < 0 || index > int(m_variations.size())) {
+ RG_DEBUG << "WARNING: MIDIInstrumentParameterPanel::slotSelectVariation: index " << index << " out of range" << endl;
+ return ;
+ }
+
+ MidiByte v = m_variations[index];
+
+ bool change = false;
+
+ if (md->getVariationType() == MidiDevice::VariationFromLSB) {
+ if (m_selectedInstrument->getLSB() != v) {
+ m_selectedInstrument->setLSB(v);
+ change = true;
+ }
+ } else if (md->getVariationType() == MidiDevice::VariationFromMSB) {
+ if (m_selectedInstrument->getMSB() != v) {
+ m_selectedInstrument->setMSB(v);
+ change = true;
+ }
+ }
+
+ if (change) {
+ sendBankAndProgram();
+ }
+
+ emit instrumentParametersChanged(m_selectedInstrument->getId());
+}
+
+void
+MIDIInstrumentParameterPanel::sendBankAndProgram()
+{
+ if (m_selectedInstrument == 0)
+ return ;
+
+ MidiDevice *md = dynamic_cast<MidiDevice*>
+ (m_selectedInstrument->getDevice());
+ if (!md) {
+ RG_DEBUG << "WARNING: MIDIInstrumentParameterPanel::sendBankAndProgram: No MidiDevice for Instrument "
+ << m_selectedInstrument->getId() << endl;
+ return ;
+ }
+
+ if (m_selectedInstrument->sendsBankSelect()) {
+
+ // Send the bank select message before any PC message
+ //
+ MappedEvent mEMSB(m_selectedInstrument->getId(),
+ MappedEvent::MidiController,
+ MIDI_CONTROLLER_BANK_MSB,
+ m_selectedInstrument->getMSB());
+
+ RG_DEBUG << "MIDIInstrumentParameterPanel::sendBankAndProgram - "
+ << "sending MSB = "
+ << int(m_selectedInstrument->getMSB())
+ << endl;
+
+ StudioControl::sendMappedEvent(mEMSB);
+
+ MappedEvent mELSB(m_selectedInstrument->getId(),
+ MappedEvent::MidiController,
+ MIDI_CONTROLLER_BANK_LSB,
+ m_selectedInstrument->getLSB());
+
+ RG_DEBUG << "MIDIInstrumentParameterPanel::sendBankAndProgram - "
+ << "sending LSB = "
+ << int(m_selectedInstrument->getLSB())
+ << endl;
+
+ StudioControl::sendMappedEvent(mELSB);
+ }
+
+ MappedEvent mE(m_selectedInstrument->getId(),
+ MappedEvent::MidiProgramChange,
+ m_selectedInstrument->getProgramChange(),
+ (MidiByte)0);
+
+ RG_DEBUG << "MIDIInstrumentParameterPanel::sendBankAndProgram - "
+ << "sending program change = "
+ << int(m_selectedInstrument->getProgramChange())
+ << endl;
+
+
+ // Send the controller change
+ //
+ StudioControl::sendMappedEvent(mE);
+}
+
+void
+MIDIInstrumentParameterPanel::slotControllerChanged(int controllerNumber)
+{
+
+ RG_DEBUG << "MIDIInstrumentParameterPanel::slotControllerChanged - "
+ << "controller = " << controllerNumber << "\n";
+
+
+ if (m_selectedInstrument == 0)
+ return ;
+
+ MidiDevice *md = dynamic_cast<MidiDevice*>
+ (m_selectedInstrument->getDevice());
+ if (!md)
+ return ;
+
+ /*
+ ControlParameter *controller =
+ md->getControlParameter(MidiByte(controllerNumber));
+ */
+
+ int value = getValueFromRotary(controllerNumber);
+
+ if (value == -1) {
+ RG_DEBUG << "MIDIInstrumentParameterPanel::slotControllerChanged - "
+ << "couldn't get value of rotary for controller "
+ << controllerNumber << endl;
+ return ;
+ }
+
+
+ // two special cases
+ if (controllerNumber == int(MIDI_CONTROLLER_PAN)) {
+ float adjValue = value;
+ if (m_selectedInstrument->getType() == Instrument::Audio ||
+ m_selectedInstrument->getType() == Instrument::SoftSynth)
+ value += 100;
+
+ m_selectedInstrument->setPan(MidiByte(adjValue));
+ } else if (controllerNumber == int(MIDI_CONTROLLER_VOLUME)) {
+ m_selectedInstrument->setVolume(MidiByte(value));
+ } else // just set the controller (this will create it on the instrument if
+ // it doesn't exist)
+ {
+ m_selectedInstrument->setControllerValue(MidiByte(controllerNumber),
+ MidiByte(value));
+
+ RG_DEBUG << "SET CONTROLLER VALUE (" << controllerNumber << ") = " << value << endl;
+ }
+ /*
+ else
+ {
+ RG_DEBUG << "MIDIInstrumentParameterPanel::slotControllerChanged - "
+ << "no controller retrieved\n";
+ return;
+ }
+ */
+
+ MappedEvent mE(m_selectedInstrument->getId(),
+ MappedEvent::MidiController,
+ (MidiByte)controllerNumber,
+ (MidiByte)value);
+ StudioControl::sendMappedEvent(mE);
+
+ emit updateAllBoxes();
+ emit instrumentParametersChanged(m_selectedInstrument->getId());
+
+}
+
+int
+MIDIInstrumentParameterPanel::getValueFromRotary(int rotary)
+{
+ for (RotaryMap::iterator it = m_rotaries.begin(); it != m_rotaries.end(); ++it) {
+ if (it->first == rotary)
+ return int(it->second.first->getPosition());
+ }
+
+ return -1;
+}
+
+void
+MIDIInstrumentParameterPanel::showAdditionalControls(bool showThem)
+{
+ m_instrumentLabel->setShown(showThem);
+ int index = 0;
+ for (RotaryMap::iterator it = m_rotaries.begin(); it != m_rotaries.end(); ++it) {
+ it->second.first->parentWidget()->setShown(showThem || (index < 8));
+ //it->second.first->setShown(showThem || (index < 8));
+ //it->second.second->setShown(showThem || (index < 8));
+ index++;
+ }
+}
+
+}
+#include "MIDIInstrumentParameterPanel.moc"
diff --git a/src/gui/editors/parameters/MIDIInstrumentParameterPanel.h b/src/gui/editors/parameters/MIDIInstrumentParameterPanel.h
new file mode 100644
index 0000000..7f1a1c5
--- /dev/null
+++ b/src/gui/editors/parameters/MIDIInstrumentParameterPanel.h
@@ -0,0 +1,137 @@
+
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
+
+/*
+ Rosegarden
+ A MIDI and audio sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_MIDIINSTRUMENTPARAMETERPANEL_H_
+#define _RG_MIDIINSTRUMENTPARAMETERPANEL_H_
+
+#include "base/MidiProgram.h"
+#include "base/MidiDevice.h"
+#include "InstrumentParameterPanel.h"
+#include <qstring.h>
+
+
+class QWidget;
+class QSignalMapper;
+class QLabel;
+class QGridLayout;
+class QFrame;
+class QCheckBox;
+class KComboBox;
+
+
+namespace Rosegarden
+{
+
+class RosegardenGUIDoc;
+class MidiDevice;
+class Instrument;
+
+
+class MIDIInstrumentParameterPanel : public InstrumentParameterPanel
+{
+ Q_OBJECT
+public:
+
+ MIDIInstrumentParameterPanel(RosegardenGUIDoc *doc, QWidget* parent);
+
+ void setupControllers(MidiDevice *); // setup ControlParameters on box
+
+ virtual void setupForInstrument(Instrument*);
+
+ void showAdditionalControls(bool showThem);
+
+signals:
+ void changeInstrumentLabel(InstrumentId id, QString label);
+ void instrumentParametersChanged(InstrumentId);
+
+public slots:
+ void slotSelectProgram(int index);
+ void slotSelectBank(int index);
+ void slotSelectVariation(int index);
+ void slotSelectChannel(int index);
+ //void slotSelectInputChannel(int index);
+
+ void slotControllerChanged(int index);
+
+ void slotTogglePercussion(bool value);
+ void slotToggleProgramChange(bool value);
+ void slotToggleBank(bool value);
+ void slotToggleVariation(bool value);
+
+protected:
+
+ // fill (or hide) bank combo based on whether the instrument is percussion
+ void populateBankList();
+
+ // fill program combo based on current bank
+ void populateProgramList();
+
+ // fill (or hide) variation combo based on current bank and program
+ void populateVariationList();
+
+ // send the bank and program events relevant to this instrument
+ void sendBankAndProgram();
+
+ // get value of a specific rotary (keyed by controller value)
+ int getValueFromRotary(int rotary);
+
+ // set rotary to value
+ void setRotaryToValue(int controller, int value);
+
+ //--------------- Data members ---------------------------------
+
+ QLabel *m_connectionLabel;
+
+ KComboBox *m_bankValue;
+ KComboBox *m_variationValue;
+ KComboBox *m_channelValue;
+ KComboBox *m_programValue;
+ //KComboBox *m_channelInValue;
+
+ QCheckBox *m_percussionCheckBox;
+ QCheckBox *m_bankCheckBox;
+ QCheckBox *m_variationCheckBox;
+ QCheckBox *m_programCheckBox;
+
+ QLabel *m_bankLabel;
+ QLabel *m_variationLabel;
+ QLabel *m_programLabel;
+
+ QGridLayout *m_mainGrid;
+ QFrame *m_rotaryFrame;
+ QGridLayout *m_rotaryGrid;
+ RotaryMap m_rotaries;
+ QSignalMapper *m_rotaryMapper;
+
+ BankList m_banks;
+ ProgramList m_programs;
+ MidiByteList m_variations;
+};
+
+
+
+}
+
+#endif
diff --git a/src/gui/editors/parameters/RosegardenParameterArea.cpp b/src/gui/editors/parameters/RosegardenParameterArea.cpp
new file mode 100644
index 0000000..968c737
--- /dev/null
+++ b/src/gui/editors/parameters/RosegardenParameterArea.cpp
@@ -0,0 +1,227 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
+
+/*
+ Rosegarden
+ A MIDI and audio sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ This file Copyright 2006 Martin Shepherd <mcs@astro.caltech.edu>.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "RosegardenParameterArea.h"
+
+#include "RosegardenParameterBox.h"
+#include <ktabwidget.h>
+#include <qfont.h>
+#include <qframe.h>
+#include <qpoint.h>
+#include <qscrollview.h>
+#include <qstring.h>
+#include <qvbox.h>
+#include <qlayout.h>
+#include <qvgroupbox.h>
+#include <qwidget.h>
+#include <qwidgetstack.h>
+#include <iostream>
+#include <set>
+
+
+namespace Rosegarden
+{
+
+RosegardenParameterArea::RosegardenParameterArea(QWidget *parent,
+ const char *name, WFlags f)
+ : QWidgetStack(parent, name, f),
+ m_style(RosegardenParameterArea::CLASSIC_STYLE),
+ m_scrollView(new QScrollView(this, 0, Qt::WStaticContents)),
+ m_classic(new QVBox(m_scrollView->viewport())),
+ m_tabBox(new KTabWidget(this)),
+ m_active(0),
+ m_spacing(0)
+{
+ m_scrollView->addChild(m_classic);
+ m_scrollView->setHScrollBarMode(QScrollView::AlwaysOff);
+ m_scrollView->setVScrollBarMode(QScrollView::Auto);
+ m_scrollView->setResizePolicy(QScrollView::AutoOneFit);
+
+ // Install the classic-style VBox widget in the widget-stack.
+
+ addWidget(m_scrollView, CLASSIC_STYLE);
+
+ // Install the widget that implements the tab-style to the widget-stack.
+
+ addWidget(m_tabBox, TAB_BOX_STYLE);
+
+}
+
+void RosegardenParameterArea::addRosegardenParameterBox(
+ RosegardenParameterBox *b)
+{
+ // Check that the box hasn't been added before.
+
+ for (unsigned int i = 0; i < m_parameterBoxes.size(); i++) {
+ if (m_parameterBoxes[i] == b)
+ return ;
+ }
+
+ // Append the parameter box to the list to be displayed.
+
+ m_parameterBoxes.push_back(b);
+
+ m_scrollView->setMinimumWidth(std::max(m_scrollView->minimumWidth(),
+ b->sizeHint().width()) + 8);
+
+ // Create a titled group box for the parameter box, parented by the
+ // classic layout widget, so that it can be used to provide a title
+ // and outline, in classic mode. Add this container to an array that
+ // parallels the above array of parameter boxes.
+
+ QVGroupBox *box = new QVGroupBox(b->getLongLabel(), m_classic);
+ box->layout()->setMargin( 4 ); // about half the default value
+ QFont f;
+ f.setBold( true );
+ box->setFont( f );
+ m_groupBoxes.push_back(box);
+
+ if (m_spacing)
+ delete m_spacing;
+ m_spacing = new QFrame(m_classic);
+ m_classic->setStretchFactor(m_spacing, 100);
+
+ // Add the parameter box to the current container of the displayed
+ // widgets, unless the current container has been set up yet.
+
+ if (m_active)
+ moveWidget(0, m_active, b);
+
+ // Queue a redisplay of the parameter area, to incorporate the new box.
+
+ update();
+}
+
+void RosegardenParameterArea::setArrangement(Arrangement style)
+{
+ // Lookup the container of the specified style.
+
+ QWidget *container;
+ switch (style) {
+ case CLASSIC_STYLE:
+ container = m_classic;
+ break;
+ case TAB_BOX_STYLE:
+ container = m_tabBox;
+ break;
+ default:
+ std::cerr << "setArrangement() was passed an unknown arrangement style."
+ << std::endl;
+ return ;
+ }
+
+ // Does the current container of the parameter-box widgets differ
+ // from the one that is associated with the currently configured
+ // style?
+
+ if (container != m_active) {
+
+ // Move the parameter boxes from the old container to the new one.
+
+ std::vector<RosegardenParameterBox *> sorted;
+ std::set<RosegardenParameterBox *> unsorted;
+
+ for (unsigned int i = 0; i < m_parameterBoxes.size(); i++) {
+ unsorted.insert(m_parameterBoxes[i]);
+ }
+
+ QString previous = "";
+
+ while (!unsorted.empty()) {
+ std::set<RosegardenParameterBox *>::iterator i = unsorted.begin();
+ bool have = false;
+ while (i != unsorted.end()) {
+ if ((*i)->getPreviousBox(style) == previous) {
+ sorted.push_back(*i);
+ previous = (*i)->getShortLabel();
+ unsorted.erase(i);
+ have = true;
+ break;
+ }
+ ++i;
+ }
+ if (!have) {
+ while (!unsorted.empty()) {
+ sorted.push_back(*unsorted.begin());
+ unsorted.erase(unsorted.begin());
+ }
+ break;
+ }
+ }
+
+ for (std::vector<RosegardenParameterBox *>::iterator i = sorted.begin();
+ i != sorted.end(); ++i) {
+ moveWidget(m_active, container, *i);
+ (*i)->showAdditionalControls(style == TAB_BOX_STYLE);
+ }
+
+ // Switch the widget stack to displaying the new container.
+
+ raiseWidget(style);
+ }
+
+ // Record the identity of the active container, and the associated
+ // arrangement style.
+
+ m_active = container;
+ m_style = style;
+}
+
+void RosegardenParameterArea::moveWidget(QWidget *old_container,
+ QWidget *new_container,
+ RosegardenParameterBox *box)
+{
+ // Remove any state that is associated with the parameter boxes,
+ // from the active container.
+
+ if (old_container == m_classic) {
+ ;
+ } else if (old_container == m_tabBox) {
+ m_tabBox->removePage(box);
+ }
+
+ // Reparent the parameter box, and perform any container-specific
+ // configuration.
+
+ if (new_container == m_classic) {
+ int index = 0;
+ while (index < m_parameterBoxes.size()) {
+ if (box == m_parameterBoxes[index])
+ break;
+ ++index;
+ }
+ if (index < m_parameterBoxes.size()) {
+ box->reparent(m_groupBoxes[index], 0, QPoint(0, 0), FALSE);
+ }
+ } else if (new_container == m_tabBox) {
+ box->reparent(new_container, 0, QPoint(0, 0), FALSE);
+ m_tabBox->insertTab(box, box->getShortLabel());
+ }
+}
+
+}
+#include "RosegardenParameterArea.moc"
diff --git a/src/gui/editors/parameters/RosegardenParameterArea.h b/src/gui/editors/parameters/RosegardenParameterArea.h
new file mode 100644
index 0000000..1236a43
--- /dev/null
+++ b/src/gui/editors/parameters/RosegardenParameterArea.h
@@ -0,0 +1,108 @@
+
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
+
+/*
+ Rosegarden
+ A MIDI and audio sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ This file Copyright 2006 Martin Shepherd <mcs@astro.caltech.edu>.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_ROSEGARDENPARAMETERAREA_H_
+#define _RG_ROSEGARDENPARAMETERAREA_H_
+
+#include <qwidgetstack.h>
+#include <vector>
+
+
+class QWidget;
+class QVGroupBox;
+class QVBox;
+class QScrollView;
+class KTabWidget;
+
+
+namespace Rosegarden
+{
+
+class RosegardenParameterBox;
+
+
+/**
+ * A widget that arranges a set of Rosegarden parameter-box widgets
+ * within a frame, in a dynamically configurable manner.
+ */
+class RosegardenParameterArea : public QWidgetStack
+{
+ Q_OBJECT
+public:
+
+ // Create the parameter display area.
+
+ RosegardenParameterArea(QWidget *parent=0, const char *name=0, WFlags f=0);
+
+ // Add a rosegarden parameter box to the list that are to be displayed.
+
+ void addRosegardenParameterBox(RosegardenParameterBox *b);
+
+
+ // List the supported methods of arranging the various parameter-box
+ // widgets within the parameter area.
+
+ enum Arrangement {
+ CLASSIC_STYLE, // A simple vertical tiling of parameter-box widgets.
+ TAB_BOX_STYLE // A horizontal list of tabs, displaying one box at a time.
+ };
+
+ // Redisplay the widgets with a different layout style.
+
+ void setArrangement(Arrangement style);
+
+protected:
+private:
+ Arrangement m_style; // The current layout style.
+
+ // The list of parameter box widgets that are being displayed by this
+ // widget.
+
+ std::vector<RosegardenParameterBox *> m_parameterBoxes;
+
+ // Create a parallel array of group boxes, to be used when the
+ // corresponding parameter box widget needs to be enclosed by a
+ // titled outline.
+
+ std::vector<QVGroupBox *> m_groupBoxes;
+
+ // Move a RosegardenParameterBox widget from one container to another.
+
+ void moveWidget(QWidget *old_container, QWidget *new_container,
+ RosegardenParameterBox *box);
+
+ QScrollView *m_scrollView; // Holds the m_classic container
+ QVBox *m_classic; // The container widget for m_style==CLASSIC_STYLE.
+ KTabWidget *m_tabBox; // The container widget for m_style==TAB_BOX_STYLE.
+ QWidget *m_active; // The current container widget.
+ QWidget *m_spacing;
+};
+
+
+}
+
+#endif
diff --git a/src/gui/editors/parameters/RosegardenParameterBox.cpp b/src/gui/editors/parameters/RosegardenParameterBox.cpp
new file mode 100644
index 0000000..7d9100c
--- /dev/null
+++ b/src/gui/editors/parameters/RosegardenParameterBox.cpp
@@ -0,0 +1,89 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
+
+/*
+ Rosegarden
+ A MIDI and audio sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "RosegardenParameterBox.h"
+
+#include "RosegardenParameterArea.h"
+#include <ktabwidget.h>
+#include <qfont.h>
+#include <qframe.h>
+#include <qscrollview.h>
+#include <qstring.h>
+#include <qvbox.h>
+#include <qwidget.h>
+#include <qwidgetstack.h>
+
+
+namespace Rosegarden
+{
+
+RosegardenParameterBox::RosegardenParameterBox(const QString &shortLabel,
+ const QString &longLabel,
+ QWidget *parent,
+ const char *name) :
+ QFrame(parent, name),
+ m_shortLabel(shortLabel),
+ m_longLabel(longLabel),
+ m_mode(LANDSCAPE_MODE)
+{
+ init();
+}
+
+void RosegardenParameterBox::init()
+{
+ QFont plainFont;
+ plainFont.setPointSize(plainFont.pointSize() * 95 / 100);
+ if (plainFont.pixelSize() > 14)
+ plainFont.setPixelSize(14);
+ plainFont.setBold(false);
+ m_font = plainFont;
+
+ QFont boldFont;
+ boldFont.setPointSize(int(boldFont.pointSize() * 9.5 / 10.0 + 0.5));
+ if (boldFont.pixelSize() > 14)
+ boldFont.setPixelSize(14);
+ boldFont.setBold(true);
+
+ setFont(boldFont);
+}
+
+QString RosegardenParameterBox::getShortLabel() const
+{
+ return m_shortLabel;
+}
+
+QString RosegardenParameterBox::getLongLabel() const
+{
+ return m_longLabel;
+}
+
+QString RosegardenParameterBox::getPreviousBox(RosegardenParameterArea::Arrangement) const
+{
+ // No ordering known -- depends on subclasses
+ return "";
+}
+
+}
+#include "RosegardenParameterBox.moc"
diff --git a/src/gui/editors/parameters/RosegardenParameterBox.h b/src/gui/editors/parameters/RosegardenParameterBox.h
new file mode 100644
index 0000000..6f17358
--- /dev/null
+++ b/src/gui/editors/parameters/RosegardenParameterBox.h
@@ -0,0 +1,92 @@
+
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
+
+/*
+ Rosegarden
+ A MIDI and audio sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_ROSEGARDENPARAMETERBOX_H_
+#define _RG_ROSEGARDENPARAMETERBOX_H_
+
+#include "RosegardenParameterArea.h"
+#include <qfont.h>
+#include <qframe.h>
+#include <qstring.h>
+#include <klocale.h>
+
+
+class QWidget;
+
+
+namespace Rosegarden
+{
+
+
+
+/**
+ * A flat QFrame, in which a group of parameters can be laid out.
+ * Virtual method functions are defined for for requesting a layout
+ * style, and returning the single-word to use for labelling the
+ * box.
+ */
+
+class RosegardenParameterBox : public QFrame
+{
+ Q_OBJECT
+public:
+ RosegardenParameterBox(const QString &shortLabel, // e.g. i18n("Track")
+ const QString &longLabel, // e.g. i18n("Track Parameters")
+ QWidget *parent = 0,
+ const char *name = 0);
+
+ // Ask for a one-word string that can be used to label the widget.
+ QString getShortLabel() const;
+
+ // Ask for the full label (e.g. short-label "Parameters")
+ QString getLongLabel() const;
+
+ // Get the short label of the prior parameter box (to establish an ordering)
+ virtual QString getPreviousBox(RosegardenParameterArea::Arrangement) const;
+
+ virtual void showAdditionalControls(bool) = 0;
+
+protected:
+ void init();
+
+ // List the layout styles that may be requested via a call to setStyle().
+
+ enum LayoutMode {
+ LANDSCAPE_MODE, // Optimize the layout for a tall and narrow parent.
+ PORTRAIT_MODE // Optimize the layout for a short and wide parent.
+ };
+
+ void setLayoutMode(LayoutMode mode);
+
+ QFont m_font;
+ QString m_shortLabel; // The string that containers can use for labelling and identification
+ QString m_longLabel; // The full title
+ LayoutMode m_mode; // The current layout mode.
+};
+
+
+}
+
+#endif
diff --git a/src/gui/editors/parameters/SegmentParameterBox.cpp b/src/gui/editors/parameters/SegmentParameterBox.cpp
new file mode 100644
index 0000000..c17cbe2
--- /dev/null
+++ b/src/gui/editors/parameters/SegmentParameterBox.cpp
@@ -0,0 +1,1214 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
+
+/*
+ Rosegarden
+ A MIDI and audio sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "SegmentParameterBox.h"
+#include <qlayout.h>
+#include <kapplication.h>
+
+#include <klocale.h>
+#include "misc/Debug.h"
+#include "misc/Strings.h"
+#include "document/ConfigGroups.h"
+#include "base/Colour.h"
+#include "base/ColourMap.h"
+#include "base/Composition.h"
+#include "base/MidiProgram.h"
+#include "base/NotationTypes.h"
+#include "base/BasicQuantizer.h"
+#include "base/RealTime.h"
+#include "base/Segment.h"
+#include "base/Selection.h"
+#include "commands/segment/SegmentChangeQuantizationCommand.h"
+#include "commands/segment/SegmentColourCommand.h"
+#include "commands/segment/SegmentColourMapCommand.h"
+#include "commands/segment/SegmentCommandRepeat.h"
+#include "commands/segment/SegmentLabelCommand.h"
+#include "document/MultiViewCommandHistory.h"
+#include "document/RosegardenGUIDoc.h"
+#include "gui/dialogs/PitchPickerDialog.h"
+#include "gui/editors/notation/NotationStrings.h"
+#include "gui/editors/notation/NotePixmapFactory.h"
+#include "gui/general/GUIPalette.h"
+#include "gui/widgets/ColourTable.h"
+#include "gui/widgets/TristateCheckBox.h"
+#include "RosegardenParameterArea.h"
+#include "RosegardenParameterBox.h"
+#include <kcolordialog.h>
+#include <kcombobox.h>
+#include <kcommand.h>
+#include <kconfig.h>
+#include <klineeditdlg.h>
+#include <ktabwidget.h>
+#include <qbutton.h>
+#include <qcheckbox.h>
+#include <qcolor.h>
+#include <qdialog.h>
+#include <qfont.h>
+#include <qfontmetrics.h>
+#include <qframe.h>
+#include <qlabel.h>
+#include <qpixmap.h>
+#include <qpushbutton.h>
+#include <qscrollview.h>
+#include <qspinbox.h>
+#include <qstring.h>
+#include <qtooltip.h>
+#include <qvbox.h>
+#include <qwidget.h>
+#include <qwidgetstack.h>
+
+
+namespace Rosegarden
+{
+
+SegmentParameterBox::SegmentParameterBox(RosegardenGUIDoc* doc,
+ QWidget *parent)
+ : RosegardenParameterBox(i18n("Segment"),
+ i18n("Segment Parameters"),
+ parent),
+ m_highestPlayable(127),
+ m_lowestPlayable(0),
+ m_standardQuantizations(BasicQuantizer::getStandardQuantizations()),
+ m_doc(doc),
+ m_transposeRange(48)
+{
+ initBox();
+
+ m_doc->getComposition().addObserver(this);
+
+ connect(getCommandHistory(), SIGNAL(commandExecuted()),
+ this, SLOT(update()));
+}
+
+SegmentParameterBox::~SegmentParameterBox()
+{
+ if (!isCompositionDeleted()) {
+ m_doc->getComposition().removeObserver(this);
+ }
+}
+
+void
+SegmentParameterBox::initBox()
+{
+ QFont font(m_font);
+
+ QFontMetrics fontMetrics(font);
+ // magic numbers: 13 is the height of the menu pixmaps, 10 is just 10
+ //int comboHeight = std::max(fontMetrics.height(), 13) + 10;
+ int width = fontMetrics.width("12345678901234567890");
+
+ // QFrame *frame = new QFrame(this);
+ QGridLayout *gridLayout = new QGridLayout(this, 8, 6, 4, 2);
+
+ QLabel *label = new QLabel(i18n("Label"), this);
+ QLabel *repeatLabel = new QLabel(i18n("Repeat"), this);
+ QLabel *quantizeLabel = new QLabel(i18n("Quantize"), this);
+ QLabel *transposeLabel = new QLabel(i18n("Transpose"), this);
+ QLabel *delayLabel = new QLabel(i18n("Delay"), this);
+ QLabel *colourLabel = new QLabel(i18n("Color"), this);
+// m_autoFadeLabel = new QLabel(i18n("Audio auto-fade"), this);
+// m_fadeInLabel = new QLabel(i18n("Fade in"), this);
+// m_fadeOutLabel = new QLabel(i18n("Fade out"), this);
+// m_rangeLabel = new QLabel(i18n("Range"), this);
+
+ // Label ..
+ m_label = new QLabel(this);
+ m_label->setFont(font);
+ m_label->setFixedWidth(width);
+ //m_label->setFixedHeight(comboHeight);
+ m_label->setFrameStyle(QFrame::Panel | QFrame::Sunken);
+
+ // .. and edit button
+ m_labelButton = new QPushButton(i18n("Edit"), this);
+ m_labelButton->setFont(font);
+ // m_labelButton->setFixedWidth(50);
+
+ connect(m_labelButton, SIGNAL(released()),
+ SLOT(slotEditSegmentLabel()));
+
+ m_repeatValue = new TristateCheckBox(this);
+ m_repeatValue->setFont(font);
+ //m_repeatValue->setFixedHeight(comboHeight);
+
+ // handle state changes
+ connect(m_repeatValue, SIGNAL(pressed()), SLOT(slotRepeatPressed()));
+
+ // non-reversing motif style read-only combo
+ m_quantizeValue = new KComboBox(this);
+ m_quantizeValue->setFont(font);
+ //m_quantizeValue->setFixedHeight(comboHeight);
+
+ // handle quantize changes from drop down
+ connect(m_quantizeValue, SIGNAL(activated(int)),
+ SLOT(slotQuantizeSelected(int)));
+
+ // reversing motif style read-write combo
+ m_transposeValue = new KComboBox(this);
+ m_transposeValue->setFont(font);
+ //m_transposeValue->setFixedHeight(comboHeight);
+
+ // handle transpose combo changes
+ connect(m_transposeValue, SIGNAL(activated(int)),
+ SLOT(slotTransposeSelected(int)));
+
+ // and text changes
+ connect(m_transposeValue, SIGNAL(textChanged(const QString&)),
+ SLOT(slotTransposeTextChanged(const QString&)));
+
+ // reversing motif style read-write combo
+ m_delayValue = new KComboBox(this);
+ m_delayValue->setFont(font);
+ //m_delayValue->setFixedHeight(comboHeight);
+
+ // handle delay combo changes
+ connect(m_delayValue, SIGNAL(activated(int)),
+ SLOT(slotDelaySelected(int)));
+
+ // Detect when the document colours are updated
+ connect(m_doc, SIGNAL(docColoursChanged()),
+ this, SLOT(slotDocColoursChanged()));
+
+ // handle text changes for delay
+ connect(m_delayValue, SIGNAL(textChanged(const QString&)),
+ SLOT(slotDelayTextChanged(const QString &)));
+
+ // set up combo box for colours
+ m_colourValue = new KComboBox(false, this);
+ m_colourValue->setFont(font);
+ //m_colourValue->setFixedHeight(comboHeight);
+ // m_colourValue->setMaximumWidth(width);
+ m_colourValue->setSizeLimit(20);
+
+ // handle colour combo changes
+ connect(m_colourValue, SIGNAL(activated(int)),
+ SLOT(slotColourSelected(int)));
+
+ // pre-set width of buttons so they don't grow later
+// width = fontMetrics.width(i18n("used internally for spacing", "High: ----"));
+
+ // highest playable note
+ //
+// m_highButton = new QPushButton(i18n("High: ---"), this);
+// QToolTip::add
+// (m_highButton, i18n("Choose the highest suggested playable note, using a staff"));
+// m_highButton->setFont(font);
+// m_highButton->setMinimumWidth(width);
+
+// connect(m_highButton, SIGNAL(released()),
+// SLOT(slotHighestPressed()));
+
+ // lowest playable note
+ //
+// m_lowButton = new QPushButton(i18n("Low: ----"), this);
+// QToolTip::add
+// (m_lowButton, i18n("Choose the lowest suggested playable note, using a staff"));
+// m_lowButton->setFont(font);
+// m_lowButton->setMinimumWidth(width);
+
+// connect(m_lowButton, SIGNAL(released()),
+// SLOT(slotLowestPressed()));
+
+ // Audio autofade enabled
+ //
+// m_autoFadeBox = new QCheckBox(this);
+// connect(m_autoFadeBox, SIGNAL(stateChanged(int)),
+// this, SLOT(slotAudioFadeChanged(int)));
+
+ // Fade in and out times
+ //
+// m_fadeInSpin = new QSpinBox(this);
+// m_fadeInSpin->setMinValue(0);
+// m_fadeInSpin->setMaxValue(5000);
+// m_fadeInSpin->setSuffix(i18n(" ms"));
+// connect(m_fadeInSpin, SIGNAL(valueChanged(int)),
+// this, SLOT(slotFadeInChanged(int)));
+
+// m_fadeOutSpin = new QSpinBox(this);
+// m_fadeOutSpin->setMinValue(0);
+// m_fadeOutSpin->setMaxValue(5000);
+// m_fadeOutSpin->setSuffix(i18n(" ms"));
+// connect(m_fadeOutSpin, SIGNAL(valueChanged(int)),
+// this, SLOT(slotFadeOutChanged(int)));
+
+ label->setFont(font);
+ repeatLabel->setFont(font);
+ quantizeLabel->setFont(font);
+ transposeLabel->setFont(font);
+ delayLabel->setFont(font);
+ colourLabel->setFont(font);
+// m_autoFadeLabel->setFont(font);
+// m_fadeInLabel->setFont(font);
+// m_fadeOutLabel->setFont(font);
+// m_rangeLabel->setFont(font);
+
+ int row = 0;
+
+// gridLayout->addRowSpacing(0, 12); // why??
+
+ gridLayout->addWidget(label, row, 0); //, AlignRight);
+ gridLayout->addMultiCellWidget(m_label, row, row, 1, 4); //, AlignLeft);
+ gridLayout->addWidget(m_labelButton, row, 5); //, AlignLeft);
+ ++row;
+
+ gridLayout->addWidget(repeatLabel, row, 0); //, AlignRight);
+ gridLayout->addWidget(m_repeatValue, row, 1); //, AlignLeft);
+
+ gridLayout->addMultiCellWidget(transposeLabel, row, row, 2, 3, AlignRight);
+ gridLayout->addMultiCellWidget(m_transposeValue, row, row, 4, 5);
+ ++row;
+
+ gridLayout->addWidget(quantizeLabel, row, 0); //, AlignRight);
+ gridLayout->addMultiCellWidget(m_quantizeValue, row, row, 1, 2); //, AlignLeft);
+
+ gridLayout->addWidget(delayLabel, row, 3, AlignRight);
+ gridLayout->addMultiCellWidget(m_delayValue, row, row, 4, 5);
+ ++row;
+
+ gridLayout->addWidget(colourLabel, row, 0); //, AlignRight);
+ gridLayout->addMultiCellWidget(m_colourValue, row, row, 1, 5);
+ ++row;
+
+// gridLayout->addWidget(m_rangeLabel, row, 0); //, AlignRight);
+// gridLayout->addMultiCellWidget(m_lowButton, row, row, 1, 2);
+// gridLayout->addMultiCellWidget(m_highButton, row, row, 3, 4);
+// ++row;
+
+// m_autoFadeLabel->hide();
+// m_autoFadeBox->hide();
+ /*
+ gridLayout->addWidget(m_fadeInLabel, 5, 0, AlignRight);
+ gridLayout->addWidget(m_fadeInSpin, 5, 1);
+
+ gridLayout->addWidget(m_fadeOutLabel, 5, 2, AlignRight);
+ gridLayout->addWidget(m_fadeOutSpin, 5, 3);
+ */
+ // Configure the empty final row to accomodate any extra vertical space.
+
+ gridLayout->setRowStretch(gridLayout->numRows() - 1, 1);
+
+ // Configure the empty final column to accomodate any extra horizontal
+ // space.
+
+// gridLayout->setColStretch(gridLayout->numCols() - 1, 1);
+
+ // populate the quantize combo
+ //
+ QPixmap noMap = NotePixmapFactory::toQPixmap(NotePixmapFactory::makeToolbarPixmap("menu-no-note"));
+
+ for (unsigned int i = 0; i < m_standardQuantizations.size(); ++i) {
+
+ timeT time = m_standardQuantizations[i];
+ timeT error = 0;
+ QString label = NotationStrings::makeNoteMenuLabel(time, true, error);
+ QPixmap pmap = NotePixmapFactory::toQPixmap(NotePixmapFactory::makeNoteMenuPixmap(time, error));
+ m_quantizeValue->insertItem(error ? noMap : pmap, label);
+ }
+ m_quantizeValue->insertItem(noMap, i18n("Off"));
+
+ // default to last item
+ m_quantizeValue->setCurrentItem(m_quantizeValue->count() - 1);
+
+ // populate the transpose combo
+ //
+ for (int i = -m_transposeRange; i < m_transposeRange + 1; i++) {
+ m_transposeValue->insertItem(noMap, QString("%1").arg(i));
+ if (i == 0)
+ m_transposeValue->setCurrentItem(m_transposeValue->count() - 1);
+ }
+
+ m_delays.clear();
+
+ for (int i = 0; i < 6; i++) {
+ timeT time = 0;
+ if (i > 0 && i < 6) {
+ time = Note(Note::Hemidemisemiquaver).getDuration() << (i - 1);
+ } else if (i > 5) {
+ time = Note(Note::Crotchet).getDuration() * (i - 4);
+ }
+
+ m_delays.push_back(time);
+
+ // check if it's a valid note duration (it will be for the
+ // time defn above, but if we were basing it on the sequencer
+ // resolution it might not be) & include a note pixmap if so
+ //
+ timeT error = 0;
+ QString label = NotationStrings::makeNoteMenuLabel(time, true, error);
+ QPixmap pmap = NotePixmapFactory::toQPixmap(NotePixmapFactory::makeNoteMenuPixmap(time, error));
+ m_delayValue->insertItem((error ? noMap : pmap), label);
+ }
+
+ for (int i = 0; i < 10; i++) {
+ int rtd = (i < 5 ? ((i + 1) * 10) : ((i - 3) * 50));
+ m_realTimeDelays.push_back(rtd);
+ m_delayValue->insertItem(i18n("%1 ms").arg(rtd));
+ }
+
+ // set delay blank initially
+ m_delayValue->setCurrentItem( -1);
+
+ // populate m_colourValue
+ slotDocColoursChanged();
+
+ //!!! disabled until after 1.3
+// m_highButton->hide();
+// m_lowButton->hide();
+// m_rangeLabel->hide();
+ //////////////////////////////
+
+}
+
+void
+SegmentParameterBox::setDocument(RosegardenGUIDoc* doc)
+{
+ if (m_doc != 0)
+ disconnect(m_doc, SIGNAL(docColoursChanged()),
+ this, SLOT(slotDocColoursChanged()));
+
+ m_doc = doc;
+
+ // Detect when the document colours are updated
+ connect (m_doc, SIGNAL(docColoursChanged()),
+ this, SLOT(slotDocColoursChanged()));
+
+ slotDocColoursChanged(); // repopulate combo
+}
+
+void
+SegmentParameterBox::useSegment(Segment *segment)
+{
+ m_segments.clear();
+ m_segments.push_back(segment);
+ populateBoxFromSegments();
+}
+
+void
+SegmentParameterBox::useSegments(const SegmentSelection &segments)
+{
+ m_segments.clear();
+
+ m_segments.resize(segments.size());
+ std::copy(segments.begin(), segments.end(), m_segments.begin());
+
+ populateBoxFromSegments();
+}
+
+void
+SegmentParameterBox::slotDocColoursChanged()
+{
+ RG_DEBUG << "SegmentParameterBox::slotDocColoursChanged()" << endl;
+
+ m_colourValue->clear();
+ m_colourList.clear();
+ // Populate it from composition.m_segmentColourMap
+ ColourMap temp = m_doc->getComposition().getSegmentColourMap();
+
+ unsigned int i = 0;
+
+ for (RCMap::const_iterator it = temp.begin(); it != temp.end(); ++it) {
+ QString qtrunc(strtoqstr(it->second.second));
+ QPixmap colour(15, 15);
+ colour.fill(GUIPalette::convertColour(it->second.first));
+ if (qtrunc == "") {
+ m_colourValue->insertItem(colour, i18n("Default"), i);
+ } else {
+ // truncate name to 15 characters to avoid the combo forcing the
+ // whole kit and kaboodle too wide
+ if (qtrunc.length() > 15)
+ qtrunc = qtrunc.left(12) + "...";
+ m_colourValue->insertItem(colour, qtrunc, i);
+ }
+ m_colourList[it->first] = i; // maps colour number to menu index
+ ++i;
+ }
+
+ m_addColourPos = i;
+ m_colourValue->insertItem(i18n("Add New Color"), m_addColourPos);
+
+ m_colourValue->setCurrentItem(0);
+}
+
+void SegmentParameterBox::update()
+{
+ RG_DEBUG << "SegmentParameterBox::update()" << endl;
+
+ populateBoxFromSegments();
+}
+
+void
+SegmentParameterBox::segmentRemoved(const Composition *composition,
+ Segment *segment)
+{
+ if (composition == &m_doc->getComposition()) {
+
+ for (std::vector<Segment*>::iterator it =
+ m_segments.begin(); it != m_segments.end(); ++it) {
+
+ if (*it == segment) {
+ m_segments.erase(it);
+ return ;
+ }
+ }
+ }
+}
+
+void
+SegmentParameterBox::populateBoxFromSegments()
+{
+ std::vector<Segment*>::iterator it;
+ Tristate repeated = NotApplicable;
+ Tristate quantized = NotApplicable;
+ Tristate transposed = NotApplicable;
+ Tristate delayed = NotApplicable;
+ Tristate diffcolours = NotApplicable;
+ Tristate highlow = NotApplicable;
+ unsigned int myCol = 0;
+ unsigned int myHigh = 127;
+ unsigned int myLow = 0;
+
+ timeT qntzLevel = 0;
+ // At the moment we have no negative delay, so we use negative
+ // values to represent real-time delay in ms
+ timeT delayLevel = 0;
+ int transposeLevel = 0;
+
+ if (m_segments.size() == 0)
+ m_label->setText("");
+ else
+ m_label->setText(strtoqstr(m_segments[0]->getLabel()));
+
+ for (it = m_segments.begin(); it != m_segments.end(); it++) {
+ // ok, first thing is we know we have at least one segment
+ if (repeated == NotApplicable)
+ repeated = None;
+ if (quantized == NotApplicable)
+ quantized = None;
+ if (transposed == NotApplicable)
+ transposed = None;
+ if (delayed == NotApplicable)
+ delayed = None;
+ if (diffcolours == NotApplicable)
+ diffcolours = None;
+ if (highlow == NotApplicable)
+ highlow = None;
+
+ // Set label to "*" when multiple labels don't match
+ //
+ if (strtoqstr((*it)->getLabel()) != m_label->text())
+ m_label->setText("*");
+
+ // Are all, some or none of the Segments repeating?
+ if ((*it)->isRepeating()) {
+ if (it == m_segments.begin())
+ repeated = All;
+ else {
+ if (repeated == None)
+ repeated = Some;
+ }
+ } else {
+ if (repeated == All)
+ repeated = Some;
+ }
+
+ // Quantization
+ //
+ if ((*it)->hasQuantization()) {
+ if (it == m_segments.begin()) {
+ quantized = All;
+ qntzLevel = (*it)->getQuantizer()->getUnit();
+ } else {
+ // If quantize levels don't match
+ if (quantized == None ||
+ (quantized == All &&
+ qntzLevel !=
+ (*it)->getQuantizer()->getUnit()))
+ quantized = Some;
+ }
+ } else {
+ if (quantized == All)
+ quantized = Some;
+ }
+
+ // Transpose
+ //
+ if ((*it)->getTranspose() != 0) {
+ if (it == m_segments.begin()) {
+ transposed = All;
+ transposeLevel = (*it)->getTranspose();
+ } else {
+ if (transposed == None ||
+ (transposed == All &&
+ transposeLevel != (*it)->getTranspose()))
+ transposed = Some;
+ }
+
+ } else {
+ if (transposed == All)
+ transposed = Some;
+ }
+
+ // Delay
+ //
+ timeT myDelay = (*it)->getDelay();
+ if (myDelay == 0) {
+ myDelay = -((*it)->getRealTimeDelay().sec * 1000 +
+ (*it)->getRealTimeDelay().msec());
+ }
+
+ if (myDelay != 0) {
+ if (it == m_segments.begin()) {
+ delayed = All;
+ delayLevel = myDelay;
+ } else {
+ if (delayed == None ||
+ (delayed == All &&
+ delayLevel != myDelay))
+ delayed = Some;
+ }
+ } else {
+ if (delayed == All)
+ delayed = Some;
+ }
+
+ // Colour
+
+ if (it == m_segments.begin()) {
+ myCol = (*it)->getColourIndex();
+ } else {
+ if (myCol != (*it)->getColourIndex())
+ ;
+ diffcolours = All;
+ }
+
+ // Highest/Lowest playable
+ //
+ if (it == m_segments.begin()) {
+ myHigh = (*it)->getHighestPlayable();
+ myLow = (*it)->getLowestPlayable();
+ } else {
+ if (myHigh != (*it)->getHighestPlayable() ||
+ myLow != (*it)->getLowestPlayable()) {
+ highlow = All;
+ }
+ }
+
+ }
+
+ switch (repeated) {
+ case All:
+ m_repeatValue->setChecked(true);
+ break;
+
+ case Some:
+ m_repeatValue->setNoChange();
+ break;
+
+ case None:
+ case NotApplicable:
+ default:
+ m_repeatValue->setChecked(false);
+ break;
+ }
+
+ m_repeatValue->setEnabled(repeated != NotApplicable);
+
+ switch (quantized) {
+ case All: {
+ for (unsigned int i = 0;
+ i < m_standardQuantizations.size(); ++i) {
+ if (m_standardQuantizations[i] == qntzLevel) {
+ m_quantizeValue->setCurrentItem(i);
+ break;
+ }
+ }
+ }
+ break;
+
+ case Some:
+ // Set the edit text to an unfeasible blank value meaning "Some"
+ //
+ m_quantizeValue->setCurrentItem( -1);
+ break;
+
+ // Assuming "Off" is always the last field
+ case None:
+ default:
+ m_quantizeValue->setCurrentItem(m_quantizeValue->count() - 1);
+ break;
+ }
+
+ m_quantizeValue->setEnabled(quantized != NotApplicable);
+
+ switch (transposed) {
+ // setCurrentItem works with QStrings
+ // 2nd arg of "true" means "add if necessary"
+ case All:
+ m_transposeValue->
+ setCurrentItem(QString("%1").arg(transposeLevel), true);
+ break;
+
+ case Some:
+ m_transposeValue->setCurrentItem(QString(""), true);
+ break;
+
+ case None:
+ default:
+ m_transposeValue->setCurrentItem("0");
+ break;
+ }
+
+ m_transposeValue->setEnabled(transposed != NotApplicable);
+
+ m_delayValue->blockSignals(true);
+
+ switch (delayed) {
+ case All:
+ if (delayLevel >= 0) {
+ timeT error = 0;
+ QString label = NotationStrings::makeNoteMenuLabel(delayLevel,
+ true,
+ error);
+ m_delayValue->setCurrentItem(label, true);
+
+ } else if (delayLevel < 0) {
+
+ m_delayValue->setCurrentItem(i18n("%1 ms").arg( -delayLevel),
+ true);
+ }
+
+ break;
+
+ case Some:
+ m_delayValue->setCurrentItem("", true);
+ break;
+
+ case None:
+ default:
+ m_delayValue->setCurrentItem(0);
+ break;
+ }
+
+ m_delayValue->setEnabled(delayed != NotApplicable);
+
+ m_delayValue->blockSignals(false);
+
+ switch (diffcolours) {
+ case None:
+ if (m_colourList.find(myCol) != m_colourList.end())
+ m_colourValue->setCurrentItem(m_colourList[myCol]);
+ else
+ m_colourValue->setCurrentItem(0);
+ break;
+
+
+ case All:
+ case NotApplicable:
+ default:
+ m_colourValue->setCurrentItem(0);
+ break;
+
+ }
+
+ m_colourValue->setEnabled(diffcolours != NotApplicable);
+
+ //!!! this is all borked up and useless; sort out after 1.3
+/*
+ switch (highlow) {
+ case All:
+ updateHighLow();
+ break;
+
+ case Some:
+ case None:
+ default:
+ m_highButton->setText(i18n("High: ---"));
+ m_lowButton->setText(i18n("Low: ----"));
+ highlow = NotApplicable;
+ break;
+ }
+
+ m_highButton->setEnabled(highlow != NotApplicable);
+ m_lowButton->setEnabled(highlow != NotApplicable);
+*/
+
+ // Enable or disable the fade in/out params
+/*
+ if (m_segments.size() == 1 &&
+ (*(m_segments.begin()))->getType() == Segment::Audio) {
+ m_autoFadeBox->blockSignals(true);
+ m_fadeInSpin->blockSignals(true);
+ m_fadeOutSpin->blockSignals(true);
+
+ ... !!! No, not setting up autofade widgets. The implementation's too
+ incomplete to finish for this release.
+
+ (Or for the next one after the one the previous comment referred to.)
+
+ (Or for the one after the one after that. Will we ever get those
+ working, or should Rich's final legacy simply be quietly disappeared?)
+
+ m_fadeInLabel->show();
+ m_fadeInSpin->show();
+ m_fadeOutLabel->show();
+ m_fadeOutSpin->show();
+
+ instead:
+
+ m_fadeInLabel->hide();
+ m_fadeInSpin->hide();
+ m_fadeOutLabel->hide();
+ m_fadeOutSpin->hide();
+
+ m_autoFadeLabel->setEnabled(true);
+ m_autoFadeBox->setEnabled(true);
+ m_fadeInLabel->setEnabled(true);
+ m_fadeInSpin->setEnabled(true);
+ m_fadeOutLabel->setEnabled(true);
+ m_fadeOutSpin->setEnabled(true);
+
+ Segment *seg = *(m_segments.begin());
+
+ int fadeInTime = seg->getFadeInTime().sec * 1000 +
+ seg->getFadeInTime().msec();
+ m_fadeInSpin->setValue(fadeInTime);
+
+ int fadeOutTime = seg->getFadeOutTime().sec * 1000 +
+ seg->getFadeOutTime().msec();
+ m_fadeOutSpin->setValue(fadeOutTime);
+
+ m_autoFadeBox->setChecked(seg->isAutoFading());
+
+ m_autoFadeBox->blockSignals(false);
+ m_fadeInSpin->blockSignals(false);
+ m_fadeOutSpin->blockSignals(false);
+ } else {
+ m_autoFadeLabel->setEnabled(false);
+ m_autoFadeBox->setEnabled(false);
+ m_fadeInLabel->setEnabled(false);
+ m_fadeInSpin->setEnabled(false);
+ m_fadeOutLabel->setEnabled(false);
+ m_fadeOutSpin->setEnabled(false);
+
+ m_autoFadeLabel->hide();
+ m_autoFadeBox->hide();
+ m_fadeInLabel->hide();
+ m_fadeInSpin->hide();
+ m_fadeOutLabel->hide();
+ m_fadeOutSpin->hide();
+
+ m_autoFadeBox->setChecked(false);
+ m_fadeInSpin->setValue(0);
+ m_fadeOutSpin->setValue(0);
+ }
+*/
+
+}
+
+void SegmentParameterBox::slotRepeatPressed()
+{
+ if (m_segments.size() == 0)
+ return ;
+
+ bool state = false;
+
+ switch (m_repeatValue->state()) {
+ case QButton::Off:
+ state = true;
+ break;
+
+ case QButton::NoChange:
+ case QButton::On:
+ default:
+ state = false;
+ break;
+ }
+
+ // update the check box and all current Segments
+ m_repeatValue->setChecked(state);
+
+ addCommandToHistory(new SegmentCommandRepeat(m_segments, state));
+
+ // std::vector<Segment*>::iterator it;
+
+ // for (it = m_segments.begin(); it != m_segments.end(); it++)
+ // (*it)->setRepeating(state);
+}
+
+void
+SegmentParameterBox::slotQuantizeSelected(int qLevel)
+{
+ bool off = (qLevel == m_quantizeValue->count() - 1);
+
+ SegmentChangeQuantizationCommand *command =
+ new SegmentChangeQuantizationCommand
+ (off ? 0 : m_standardQuantizations[qLevel]);
+
+ std::vector<Segment*>::iterator it;
+ for (it = m_segments.begin(); it != m_segments.end(); it++) {
+ command->addSegment(*it);
+ }
+
+ addCommandToHistory(command);
+}
+
+void
+SegmentParameterBox::slotTransposeTextChanged(const QString &text)
+{
+ if (text.isEmpty() || m_segments.size() == 0)
+ return ;
+
+ int transposeValue = text.toInt();
+
+ // addCommandToHistory(new SegmentCommandChangeTransposeValue(m_segments,
+ // transposeValue));
+
+ std::vector<Segment*>::iterator it;
+ for (it = m_segments.begin(); it != m_segments.end(); it++) {
+ (*it)->setTranspose(transposeValue);
+ }
+
+ emit documentModified();
+}
+
+void
+SegmentParameterBox::slotTransposeSelected(int value)
+{
+ slotTransposeTextChanged(m_transposeValue->text(value));
+}
+
+void
+SegmentParameterBox::slotDelayTimeChanged(timeT delayValue)
+{
+ // by convention and as a nasty hack, we use negative timeT here
+ // to represent positive RealTime in ms
+
+ if (delayValue > 0) {
+
+ std::vector<Segment*>::iterator it;
+ for (it = m_segments.begin(); it != m_segments.end(); it++) {
+ (*it)->setDelay(delayValue);
+ (*it)->setRealTimeDelay(RealTime::zeroTime);
+ }
+
+ } else if (delayValue < 0) {
+
+ std::vector<Segment*>::iterator it;
+ for (it = m_segments.begin(); it != m_segments.end(); it++) {
+ (*it)->setDelay(0);
+ int sec = ( -delayValue) / 1000;
+ int nsec = (( -delayValue) - 1000 * sec) * 1000000;
+ (*it)->setRealTimeDelay(RealTime(sec, nsec));
+ }
+ } else {
+
+ std::vector<Segment*>::iterator it;
+ for (it = m_segments.begin(); it != m_segments.end(); it++) {
+ (*it)->setDelay(0);
+ (*it)->setRealTimeDelay(RealTime::zeroTime);
+ }
+ }
+
+ emit documentModified();
+}
+
+void
+SegmentParameterBox::slotDelayTextChanged(const QString &text)
+{
+ if (text.isEmpty() || m_segments.size() == 0)
+ return ;
+
+ slotDelayTimeChanged( -(text.toInt()));
+}
+
+void
+SegmentParameterBox::slotDelaySelected(int value)
+{
+ if (value < int(m_delays.size())) {
+ slotDelayTimeChanged(m_delays[value]);
+ } else {
+ slotDelayTimeChanged( -(m_realTimeDelays[value - m_delays.size()]));
+ }
+}
+
+void
+SegmentParameterBox::slotColourSelected(int value)
+{
+ if (value != m_addColourPos) {
+ unsigned int temp = 0;
+
+ ColourTable::ColourList::const_iterator pos;
+ for (pos = m_colourList.begin(); pos != m_colourList.end(); ++pos) {
+ if (pos->second == value) {
+ temp = pos->first;
+ break;
+ }
+ }
+
+ SegmentSelection segments;
+ std::vector<Segment*>::iterator it;
+
+ for (it = m_segments.begin(); it != m_segments.end(); ++it) {
+ segments.insert(*it);
+ }
+
+ SegmentColourCommand *command = new SegmentColourCommand(segments, temp);
+
+ addCommandToHistory(command);
+ } else {
+ ColourMap newMap = m_doc->getComposition().getSegmentColourMap();
+ QColor newColour;
+ bool ok = false;
+ QString newName = KLineEditDlg::getText(i18n("New Color Name"), i18n("Enter new name"),
+ i18n("New"), &ok);
+ if ((ok == true) && (!newName.isEmpty())) {
+ KColorDialog box(this, "", true);
+
+ int result = box.getColor(newColour);
+
+ if (result == KColorDialog::Accepted) {
+ Colour newRColour = GUIPalette::convertColour(newColour);
+ newMap.addItem(newRColour, qstrtostr(newName));
+ SegmentColourMapCommand *command = new SegmentColourMapCommand(m_doc, newMap);
+ addCommandToHistory(command);
+ slotDocColoursChanged();
+ }
+ }
+ // Else we don't do anything as they either didn't give a name·
+ // or didn't give a colour
+ }
+
+
+}
+
+void
+SegmentParameterBox::updateHighLow()
+{
+ // Key of C major and NoAccidental means any "black key" notes will be
+ // written as sharps.
+ Accidental accidental = Accidentals::NoAccidental;
+ Rosegarden::Key key = Rosegarden::Key("C major");
+
+ Pitch highest(m_highestPlayable, accidental);
+ Pitch lowest(m_lowestPlayable, accidental);
+
+ KConfig *config = kapp->config();
+ config->setGroup(GeneralOptionsConfigGroup);
+ int base = config->readNumEntry("midipitchoctave", -2);
+ //!!! FIXME this code is broken, and needs to be fixed after the fashion of
+ //the TPB, but I'm not bothering with that at this time, because they are
+ //going to be hidden for 1.3 anyway
+// m_highButton->setText(QString("&High: %1%2").arg(highest.getNoteName(key)).arg(highest.getOctave(base)));
+// m_lowButton->setText(QString("&Low: %1%2").arg(lowest.getNoteName(key)).arg(lowest.getOctave(base)));
+}
+
+void
+SegmentParameterBox::slotHighestPressed()
+{
+ RG_DEBUG << "SegmentParameterBox::slotHighestPressed()" << endl;
+
+ PitchPickerDialog dialog(0, m_highestPlayable, i18n("Highest playable note"));
+ std::vector<Segment*>::iterator it;
+
+ if (dialog.exec() == QDialog::Accepted) {
+ m_highestPlayable = dialog.getPitch();
+ updateHighLow();
+
+ for (it = m_segments.begin(); it != m_segments.end(); it++) {
+ (*it)->setHighestPlayable(m_highestPlayable);
+ }
+
+ emit documentModified();
+ }
+}
+
+void
+SegmentParameterBox::slotLowestPressed()
+{
+ RG_DEBUG << "SegmentParameterBox::slotLowestPressed()" << endl;
+
+ PitchPickerDialog dialog(0, m_lowestPlayable, i18n("Lowest playable note"));
+ std::vector<Segment*>::iterator it;
+
+ if (dialog.exec() == QDialog::Accepted) {
+ m_lowestPlayable = dialog.getPitch();
+ updateHighLow();
+
+ for (it = m_segments.begin(); it != m_segments.end(); it++) {
+ (*it)->setLowestPlayable(m_lowestPlayable);
+ }
+
+ emit documentModified();
+ }
+}
+
+MultiViewCommandHistory*
+SegmentParameterBox::getCommandHistory()
+{
+ return m_doc->getCommandHistory();
+}
+
+void
+SegmentParameterBox::addCommandToHistory(KCommand *command)
+{
+ m_doc->getCommandHistory()->addCommand(command);
+}
+
+void
+SegmentParameterBox::slotEditSegmentLabel()
+{
+ QString editLabel;
+
+ if (m_segments.size() == 0)
+ return ;
+ else if (m_segments.size() == 1)
+ editLabel = i18n("Modify Segment label");
+ else
+ editLabel = i18n("Modify Segments label");
+
+ bool ok = false;
+
+ // Remove the asterisk if we're using it
+ //
+ QString label = m_label->text();
+ if (label == "*")
+ label = "";
+
+ QString newLabel = KLineEditDlg::getText(editLabel,
+ i18n("Enter new label"),
+ m_label->text(),
+ &ok,
+ this);
+
+ if (ok) {
+ SegmentSelection segments;
+ std::vector<Segment*>::iterator it;
+ for (it = m_segments.begin(); it != m_segments.end(); ++it)
+ segments.insert(*it);
+
+ SegmentLabelCommand *command = new
+ SegmentLabelCommand(segments, newLabel);
+
+ addCommandToHistory(command);
+
+ // fix #1776915, maybe?
+ update();
+ }
+}
+
+void
+SegmentParameterBox::slotAudioFadeChanged(int value)
+{
+ RG_DEBUG << "SegmentParameterBox::slotAudioFadeChanged - value = "
+ << value << endl;
+/*
+ if (m_segments.size() == 0)
+ return ;
+
+ bool state = false;
+ if (value == QButton::On)
+ state = true;
+
+ std::vector<Segment*>::iterator it;
+ for (it = m_segments.begin(); it != m_segments.end(); it++) {
+ (*it)->setAutoFade(state);
+ }
+*/
+}
+
+void
+SegmentParameterBox::slotFadeInChanged(int value)
+{
+ RG_DEBUG << "SegmentParameterBox::slotFadeInChanged - value = "
+ << value << endl;
+/*
+ if (m_segments.size() == 0)
+ return ;
+
+ if (value == 0 && m_fadeOutSpin->value() == 0)
+ slotAudioFadeChanged(QButton::Off);
+ else
+ slotAudioFadeChanged(QButton::On);
+
+ // Convert from ms
+ //
+ RealTime fadeInTime(value / 1000, (value % 1000) * 1000000);
+
+ std::vector<Segment*>::iterator it;
+ for (it = m_segments.begin(); it != m_segments.end(); it++) {
+ (*it)->setFadeInTime(fadeInTime);
+ }
+
+ emit documentModified();
+*/
+}
+
+void
+SegmentParameterBox::slotFadeOutChanged(int value)
+{
+ RG_DEBUG << "SegmentParameterBox::slotFadeOutChanged - value = "
+ << value << endl;
+/*
+ if (m_segments.size() == 0)
+ return ;
+
+ if (value == 0 && m_fadeInSpin->value() == 0)
+ slotAudioFadeChanged(QButton::Off);
+ else
+ slotAudioFadeChanged(QButton::On);
+
+ // Convert from ms
+ //
+ RealTime fadeOutTime(value / 1000000, (value % 1000) * 10000000);
+
+ std::vector<Segment*>::iterator it;
+ for (it = m_segments.begin(); it != m_segments.end(); it++) {
+ (*it)->setFadeOutTime(fadeOutTime);
+ }
+
+ emit documentModified();
+*/
+}
+
+void
+SegmentParameterBox::showAdditionalControls(bool showThem)
+{
+ //!!! disabled until after 1.3
+ /* m_highButton->setShown(showThem);
+ m_lowButton->setShown(showThem);
+ m_rangeLabel->setShown(showThem); */
+}
+
+QString
+SegmentParameterBox::getPreviousBox(RosegardenParameterArea::Arrangement arrangement) const
+{
+ if (arrangement == RosegardenParameterArea::CLASSIC_STYLE) {
+ return "";
+ } else {
+ return i18n("Instrument");
+ }
+}
+
+}
+#include "SegmentParameterBox.moc"
diff --git a/src/gui/editors/parameters/SegmentParameterBox.h b/src/gui/editors/parameters/SegmentParameterBox.h
new file mode 100644
index 0000000..a8b0353
--- /dev/null
+++ b/src/gui/editors/parameters/SegmentParameterBox.h
@@ -0,0 +1,174 @@
+
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
+
+/*
+ Rosegarden
+ A MIDI and audio sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_SEGMENTPARAMETERBOX_H_
+#define _RG_SEGMENTPARAMETERBOX_H_
+
+#include "base/Composition.h"
+#include "base/MidiProgram.h"
+#include "gui/widgets/ColourTable.h"
+#include "RosegardenParameterArea.h"
+#include "RosegardenParameterBox.h"
+#include <qstring.h>
+#include <vector>
+#include "base/Event.h"
+
+
+class QWidget;
+class QSpinBox;
+class QPushButton;
+class QLabel;
+class QCheckBox;
+class KCommand;
+class KComboBox;
+
+
+namespace Rosegarden
+{
+
+class TristateCheckBox;
+class SegmentSelection;
+class Segment;
+class RosegardenGUIDoc;
+class MultiViewCommandHistory;
+class Composition;
+
+
+class SegmentParameterBox : public RosegardenParameterBox,
+ public CompositionObserver
+{
+Q_OBJECT
+
+public:
+
+ typedef enum
+ {
+ None,
+ Some,
+ All,
+ NotApplicable // no applicable segments selected
+ } Tristate;
+
+ SegmentParameterBox(RosegardenGUIDoc *doc,
+ QWidget *parent=0);
+ ~SegmentParameterBox();
+
+ // Use Segments to update GUI parameters
+ //
+ void useSegment(Segment *segment);
+ void useSegments(const SegmentSelection &segments);
+
+ // Command history stuff
+ MultiViewCommandHistory* getCommandHistory();
+ void addCommandToHistory(KCommand *command);
+
+ void setDocument(RosegardenGUIDoc*);
+
+ // CompositionObserver interface
+ //
+ virtual void segmentRemoved(const Composition *,
+ Segment *);
+
+ virtual void showAdditionalControls(bool showThem);
+
+ virtual QString getPreviousBox(RosegardenParameterArea::Arrangement) const;
+
+public slots:
+ void slotRepeatPressed();
+ void slotQuantizeSelected(int);
+
+ void slotTransposeSelected(int);
+ void slotTransposeTextChanged(const QString &);
+
+ void slotDelaySelected(int);
+ void slotDelayTimeChanged(timeT delayValue);
+ void slotDelayTextChanged(const QString &);
+
+ void slotEditSegmentLabel();
+
+ void slotColourSelected(int);
+ void slotDocColoursChanged();
+
+ void slotAudioFadeChanged(int);
+ void slotFadeInChanged(int);
+ void slotFadeOutChanged(int);
+
+ void slotHighestPressed();
+ void slotLowestPressed();
+
+ virtual void update();
+
+signals:
+ void documentModified();
+ void canvasModified();
+
+protected:
+ void initBox();
+ void populateBoxFromSegments();
+ void updateHighLow();
+
+ QLabel *m_label;
+// QLabel *m_rangeLabel;
+ QPushButton *m_labelButton;
+// QPushButton *m_highButton;
+// QPushButton *m_lowButton;
+ TristateCheckBox *m_repeatValue;
+ KComboBox *m_quantizeValue;
+ KComboBox *m_transposeValue;
+ KComboBox *m_delayValue;
+ KComboBox *m_colourValue;
+
+ // Audio autofade
+ //
+// QLabel *m_autoFadeLabel;
+// QCheckBox *m_autoFadeBox;
+// QLabel *m_fadeInLabel;
+// QSpinBox *m_fadeInSpin;
+// QLabel *m_fadeOutLabel;
+// QSpinBox *m_fadeOutSpin;
+
+ int m_addColourPos;
+
+ // used to keep track of highest/lowest as there is no associated spinbox
+ // to query for its value
+ int m_highestPlayable;
+ int m_lowestPlayable;
+
+ std::vector<Segment*> m_segments;
+ std::vector<timeT> m_standardQuantizations;
+ std::vector<timeT> m_delays;
+ std::vector<int> m_realTimeDelays;
+ ColourTable::ColourList m_colourList;
+
+ RosegardenGUIDoc *m_doc;
+
+ MidiByte m_transposeRange;
+};
+
+
+
+}
+
+#endif
diff --git a/src/gui/editors/parameters/TrackParameterBox.cpp b/src/gui/editors/parameters/TrackParameterBox.cpp
new file mode 100644
index 0000000..fc85346
--- /dev/null
+++ b/src/gui/editors/parameters/TrackParameterBox.cpp
@@ -0,0 +1,1022 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
+
+/*
+ Rosegarden
+ A MIDI and audio sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ This file is Copyright 2006
+ Pedro Lopez-Cabanillas <plcl@users.sourceforge.net>
+ D. Michael McIntyre <dmmcintyr@users.sourceforge.net>
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "TrackParameterBox.h"
+#include <qlayout.h>
+#include <kapplication.h>
+
+#include <klocale.h>
+#include "misc/Debug.h"
+#include "misc/Strings.h"
+#include "gui/general/ClefIndex.h"
+#include "document/ConfigGroups.h"
+#include "base/AudioPluginInstance.h"
+#include "base/Colour.h"
+#include "base/ColourMap.h"
+#include "base/Composition.h"
+#include "base/Device.h"
+#include "base/Exception.h"
+#include "base/Instrument.h"
+#include "base/MidiDevice.h"
+#include "base/MidiProgram.h"
+#include "base/NotationTypes.h"
+#include "base/Studio.h"
+#include "base/Track.h"
+#include "base/StaffExportTypes.h"
+#include "commands/segment/SegmentSyncCommand.h"
+#include "document/RosegardenGUIDoc.h"
+#include "gui/dialogs/PitchPickerDialog.h"
+#include "gui/general/GUIPalette.h"
+#include "gui/general/PresetHandlerDialog.h"
+#include "gui/widgets/CollapsingFrame.h"
+#include "gui/widgets/ColourTable.h"
+#include "RosegardenParameterArea.h"
+#include "RosegardenParameterBox.h"
+#include "sound/PluginIdentifier.h"
+#include <kcolordialog.h>
+#include <kcombobox.h>
+#include <kconfig.h>
+#include <klineeditdlg.h>
+#include <kmessagebox.h>
+#include <ksqueezedtextlabel.h>
+#include <ktabwidget.h>
+#include <qcolor.h>
+#include <qdialog.h>
+#include <qfont.h>
+#include <qfontmetrics.h>
+#include <qframe.h>
+#include <qlabel.h>
+#include <qpixmap.h>
+#include <qpushbutton.h>
+#include <qregexp.h>
+#include <qscrollview.h>
+#include <qstring.h>
+#include <qtooltip.h>
+#include <qvbox.h>
+#include <qwidget.h>
+#include <qwidgetstack.h>
+#include <qcheckbox.h>
+
+
+namespace Rosegarden
+{
+
+TrackParameterBox::TrackParameterBox( RosegardenGUIDoc *doc,
+ QWidget *parent)
+ : RosegardenParameterBox(i18n("Track"),
+ i18n("Track Parameters"),
+ parent),
+ m_doc(doc),
+ m_highestPlayable(127),
+ m_lowestPlayable(0),
+ m_selectedTrackId( -1)
+{
+ QFont font(m_font);
+ QFont title_font(m_font);
+ QFontMetrics metrics(font);
+ int width11 = metrics.width("12345678901");
+ int width20 = metrics.width("12345678901234567890");
+ int width22 = metrics.width("1234567890123456789012");
+ int width25 = metrics.width("1234567890123456789012345");
+ setFont(m_font);
+ title_font.setBold(true);
+
+ // Set up default expansions for the collapsing elements
+ KConfig *config = kapp->config();
+ QString groupTemp = config->group();
+ config->setGroup("CollapsingFrame");
+ bool expanded = config->readBoolEntry("trackparametersplayback", true);
+ config->writeEntry("trackparametersplayback", expanded);
+ expanded = config->readBoolEntry("trackparametersrecord", false);
+ config->writeEntry("trackparametersrecord", expanded);
+ expanded = config->readBoolEntry("trackparametersdefaults", false);
+ config->writeEntry("trackparametersdefaults", expanded);
+ expanded = config->readBoolEntry("trackstaffgroup", false);
+ config->writeEntry("trackstaffgroup", expanded);
+ config->setGroup(groupTemp);
+
+ QGridLayout *mainLayout = new QGridLayout(this, 5, 1, 2, 1);
+
+ int row = 0;
+
+ // track label
+ //
+ m_trackLabel = new KSqueezedTextLabel(i18n("<untitled>"), this);
+ m_trackLabel->setAlignment(Qt::AlignCenter);
+ //mainLayout->addMultiCellWidget(m_trackLabel, 0, 0, 0, 5, AlignCenter);
+ mainLayout->addWidget(m_trackLabel, 0, 0);
+
+ // playback group
+ //
+ CollapsingFrame *cframe = new CollapsingFrame(i18n("Playback parameters"),
+ this, "trackparametersplayback");
+ m_playbackGroup = new QFrame(cframe);
+ cframe->setWidget(m_playbackGroup);
+ QGridLayout *groupLayout = new QGridLayout(m_playbackGroup, 3, 3, 3, 2);
+
+ // playback group title
+ //
+ row = 0;
+
+ // playback device
+ //
+ // row++;
+ QLabel *devLabel = new QLabel(i18n("Device"), m_playbackGroup);
+ groupLayout->addWidget(devLabel, row, 0);
+ m_playDevice = new KComboBox(m_playbackGroup);
+ m_playDevice->setMinimumWidth(width25);
+ groupLayout->addMultiCellWidget(m_playDevice, row, row, 1, 2);
+
+ // playback instrument
+ //
+ row++;
+ QLabel *insLabel = new QLabel(i18n("Instrument"), m_playbackGroup);
+ groupLayout->addMultiCellWidget(insLabel, row, row, 0, 1);
+ m_instrument = new KComboBox(m_playbackGroup);
+ m_instrument->setSizeLimit( 16 );
+ m_instrument->setMinimumWidth(width22);
+ groupLayout->addWidget(m_instrument, row, 2);
+
+ groupLayout->setColStretch(groupLayout->numCols() - 1, 1);
+
+ mainLayout->addWidget(cframe, 1, 0);
+
+ // record group
+ //
+ cframe = new CollapsingFrame(i18n("Recording filters"), this,
+ "trackparametersrecord");
+ m_recordGroup = new QFrame(cframe);
+ cframe->setWidget(m_recordGroup);
+ groupLayout = new QGridLayout(m_recordGroup, 3, 3, 3, 2);
+
+ // recording group title
+ //
+ row = 0;
+
+ // recording device
+ groupLayout->addWidget(new QLabel(i18n("Device"), m_recordGroup), row, 0);
+ m_recDevice = new KComboBox(m_recordGroup);
+ m_recDevice->setMinimumWidth(width25);
+ groupLayout->addMultiCellWidget(m_recDevice, row, row, 1, 2);
+
+ // recording channel
+ //
+ row++;
+ groupLayout->addMultiCellWidget(new QLabel(i18n("Channel"), m_recordGroup), row, row, 0, 1);
+ m_recChannel = new KComboBox(m_recordGroup);
+ m_recChannel->setSizeLimit( 17 );
+ m_recChannel->setMinimumWidth(width11);
+ groupLayout->addWidget(m_recChannel, row, 2);
+
+ groupLayout->setColStretch(groupLayout->numCols() - 1, 1);
+
+ mainLayout->addWidget(cframe, 2, 0);
+
+ // staff group
+ //
+ cframe = new CollapsingFrame(i18n("Staff export options"), this,
+ "staffoptions");
+ m_staffGroup = new QFrame(cframe);
+ cframe->setWidget(m_staffGroup);
+ groupLayout = new QGridLayout(m_staffGroup, 2, 2, 2, 2);
+
+ groupLayout->setColStretch(1, 1);
+
+ row = 0;
+
+ // Notation size (export only)
+ //
+ // NOTE: This is the only way to get a \small or \tiny inserted before the
+ // first note in LilyPond export. Setting the actual staff size on a
+ // per-staff (rather than per-score) basis is something the author of the
+ // LilyPond documentation has no idea how to do, so we settle for this,
+ // which is not as nice, but actually a lot easier to implement.
+ m_staffGrpLbl = new QLabel(i18n("Notation size:"), m_staffGroup);
+ groupLayout->addWidget(m_staffGrpLbl, row, 0, AlignLeft);
+ m_staffSizeCombo = new KComboBox(m_staffGroup);
+ m_staffSizeCombo->setMinimumWidth(width11);
+ m_staffSizeCombo->insertItem(i18n("Normal"), StaffTypes::Normal);
+ m_staffSizeCombo->insertItem(i18n("Small"), StaffTypes::Small);
+ m_staffSizeCombo->insertItem(i18n("Tiny"), StaffTypes::Tiny);
+
+ groupLayout->addMultiCellWidget(m_staffSizeCombo, row, row, 1, 2);
+
+ // Staff bracketing (export only at the moment, but using this for GUI
+ // rendering would be nice in the future!) //!!!
+ row++;
+ m_grandStaffLbl = new QLabel(i18n("Bracket type:"), m_staffGroup);
+ groupLayout->addWidget(m_grandStaffLbl, row, 0, AlignLeft);
+ m_staffBracketCombo = new KComboBox(m_staffGroup);
+ m_staffBracketCombo->setMinimumWidth(width11);
+ m_staffBracketCombo->insertItem(i18n("-----"), Brackets::None);
+ m_staffBracketCombo->insertItem(i18n("[----"), Brackets::SquareOn);
+ m_staffBracketCombo->insertItem(i18n("----]"), Brackets::SquareOff);
+ m_staffBracketCombo->insertItem(i18n("[---]"), Brackets::SquareOnOff);
+ m_staffBracketCombo->insertItem(i18n("{----"), Brackets::CurlyOn);
+ m_staffBracketCombo->insertItem(i18n("----}"), Brackets::CurlyOff);
+ m_staffBracketCombo->insertItem(i18n("{[---"), Brackets::CurlySquareOn);
+ m_staffBracketCombo->insertItem(i18n("---]}"), Brackets::CurlySquareOff);
+
+ groupLayout->addMultiCellWidget(m_staffBracketCombo, row, row, 1, 2);
+
+ mainLayout->addWidget(cframe, 3, 0);
+
+
+ // default segment group
+ //
+ cframe = new CollapsingFrame(i18n("Create segments with"), this,
+ "trackparametersdefaults");
+ m_defaultsGroup = new QFrame(cframe);
+ cframe->setWidget(m_defaultsGroup);
+ groupLayout = new QGridLayout(m_defaultsGroup, 6, 6, 3, 2);
+
+ groupLayout->setColStretch(1, 1);
+
+ row = 0;
+
+ // preset picker
+ m_psetLbl = new QLabel(i18n("Preset"), m_defaultsGroup);
+ groupLayout->addWidget(m_psetLbl, row, 0, AlignLeft);
+
+ m_presetLbl = new QLabel(i18n("<none>"), m_defaultsGroup);
+ m_presetLbl->setFrameStyle(QFrame::Panel | QFrame::Sunken);
+ m_presetLbl->setFixedWidth(width20);
+ groupLayout->addMultiCellWidget(m_presetLbl, row, row, 1, 3);
+
+ m_presetButton = new QPushButton(i18n("Load"), m_defaultsGroup);
+ groupLayout->addMultiCellWidget(m_presetButton, row, row, 4, 5);
+
+ // default clef
+ //
+ row++;
+ m_clefLbl = new QLabel(i18n("Clef"), m_defaultsGroup);
+ groupLayout->addWidget(m_clefLbl, row, 0, AlignLeft);
+ m_defClef = new KComboBox(m_defaultsGroup);
+ m_defClef->setMinimumWidth(width11);
+ m_defClef->insertItem(i18n("treble"), TrebleClef);
+ m_defClef->insertItem(i18n("bass"), BassClef);
+ m_defClef->insertItem(i18n("crotales"), CrotalesClef);
+ m_defClef->insertItem(i18n("xylophone"), XylophoneClef);
+ m_defClef->insertItem(i18n("guitar"), GuitarClef);
+ m_defClef->insertItem(i18n("contrabass"), ContrabassClef);
+ m_defClef->insertItem(i18n("celesta"), CelestaClef);
+ m_defClef->insertItem(i18n("old celesta"), OldCelestaClef);
+ m_defClef->insertItem(i18n("french"), FrenchClef);
+ m_defClef->insertItem(i18n("soprano"), SopranoClef);
+ m_defClef->insertItem(i18n("mezzosoprano"), MezzosopranoClef);
+ m_defClef->insertItem(i18n("alto"), AltoClef);
+ m_defClef->insertItem(i18n("tenor"), TenorClef);
+ m_defClef->insertItem(i18n("baritone"), BaritoneClef);
+ m_defClef->insertItem(i18n("varbaritone"), VarbaritoneClef);
+ m_defClef->insertItem(i18n("subbass"), SubbassClef);
+ /* clef types in the datbase that are not yet supported must be ignored for
+ * now:
+ m_defClef->insertItem(i18n("two bar"), TwoBarClef); */
+ groupLayout->addMultiCellWidget(m_defClef, row, row, 1, 2);
+
+ // default transpose
+ //
+ m_transpLbl = new QLabel(i18n("Transpose"), m_defaultsGroup);
+ groupLayout->addMultiCellWidget(m_transpLbl, row, row, 3, 4, AlignRight);
+ m_defTranspose = new KComboBox(m_defaultsGroup);
+
+ connect(m_defTranspose, SIGNAL(activated(int)),
+ SLOT(slotTransposeIndexChanged(int)));
+
+ int transposeRange = 48;
+ for (int i = -transposeRange; i < transposeRange + 1; i++) {
+ m_defTranspose->insertItem(QString("%1").arg(i));
+ if (i == 0)
+ m_defTranspose->setCurrentItem(m_defTranspose->count() - 1);
+ }
+
+ groupLayout->addMultiCellWidget(m_defTranspose, row, row, 5, 5);
+
+ // highest/lowest playable note
+ //
+ row++;
+ m_rangeLbl = new QLabel(i18n("Pitch"), m_defaultsGroup);
+ groupLayout->addMultiCellWidget(m_rangeLbl, row, row, 0, 0);
+
+ groupLayout->addWidget(new QLabel(i18n("Lowest"), m_defaultsGroup), row, 1, AlignRight);
+
+ m_lowButton = new QPushButton(i18n("---"), m_defaultsGroup);
+ QToolTip::add
+ (m_lowButton, i18n("Choose the lowest suggested playable note, using a staff"));
+ groupLayout->addMultiCellWidget(m_lowButton, row, row, 2, 2);
+
+ groupLayout->addWidget(new QLabel(i18n("Highest"), m_defaultsGroup), row, 3, AlignRight);
+
+ m_highButton = new QPushButton(i18n("---"), m_defaultsGroup);
+ QToolTip::add
+ (m_highButton, i18n("Choose the highest suggested playable note, using a staff"));
+ groupLayout->addMultiCellWidget(m_highButton, row, row, 4, 5);
+
+ updateHighLow();
+
+ // default color
+ //
+ row++;
+ m_colorLbl = new QLabel(i18n("Color"), m_defaultsGroup);
+ groupLayout->addWidget(m_colorLbl, row, 0, AlignLeft);
+ m_defColor = new KComboBox(false, m_defaultsGroup);
+ m_defColor->setSizeLimit(20);
+ groupLayout->addMultiCellWidget(m_defColor, row, row, 1, 5);
+
+ // populate combo from doc colors
+ slotDocColoursChanged();
+
+ mainLayout->addWidget(cframe, 4, 0);
+
+
+ // Configure the empty final row to accomodate any extra vertical space.
+ //
+// mainLayout->setColStretch(mainLayout->numCols() - 1, 1);
+ mainLayout->setRowStretch(mainLayout->numRows() - 1, 1);
+
+ // Connections
+ connect( m_playDevice, SIGNAL(activated(int)),
+ this, SLOT(slotPlaybackDeviceChanged(int)));
+
+ connect( m_instrument, SIGNAL(activated(int)),
+ this, SLOT(slotInstrumentChanged(int)));
+
+ connect( m_recDevice, SIGNAL(activated(int)),
+ this, SLOT(slotRecordingDeviceChanged(int)));
+
+ connect( m_recChannel, SIGNAL(activated(int)),
+ this, SLOT(slotRecordingChannelChanged(int)));
+
+ connect( m_defClef, SIGNAL(activated(int)),
+ this, SLOT(slotClefChanged(int)));
+
+ // Detect when the document colours are updated
+ connect(m_doc, SIGNAL(docColoursChanged()),
+ this, SLOT(slotDocColoursChanged()));
+
+ // handle colour combo changes
+ connect(m_defColor, SIGNAL(activated(int)),
+ SLOT(slotColorChanged(int)));
+
+ connect(m_highButton, SIGNAL(released()),
+ SLOT(slotHighestPressed()));
+
+ connect(m_lowButton, SIGNAL(released()),
+ SLOT(slotLowestPressed()));
+
+ connect(m_presetButton, SIGNAL(released()),
+ SLOT(slotPresetPressed()));
+
+ connect(m_staffSizeCombo, SIGNAL(activated(int)),
+ this, SLOT(slotStaffSizeChanged(int)));
+
+ connect(m_staffBracketCombo, SIGNAL(activated(int)),
+ this, SLOT(slotStaffBracketChanged(int)));
+}
+
+TrackParameterBox::~TrackParameterBox()
+{}
+
+void
+
+TrackParameterBox::setDocument( RosegardenGUIDoc *doc )
+{
+ if (m_doc != doc) {
+ RG_DEBUG << "TrackParameterBox::setDocument\n";
+ m_doc = doc;
+ populateDeviceLists();
+ }
+}
+
+void
+TrackParameterBox::populateDeviceLists()
+{
+ RG_DEBUG << "TrackParameterBox::populateDeviceLists()\n";
+ populatePlaybackDeviceList();
+ //populateRecordingDeviceList();
+ slotUpdateControls( -1);
+ m_lastInstrumentType = -1;
+}
+
+void
+TrackParameterBox::populatePlaybackDeviceList()
+{
+ RG_DEBUG << "TrackParameterBox::populatePlaybackDeviceList()\n";
+ m_playDevice->clear();
+ m_playDeviceIds.clear();
+ m_instrument->clear();
+ m_instrumentIds.clear();
+ m_instrumentNames.clear();
+
+ Studio &studio = m_doc->getStudio();
+
+ // Get the list
+ InstrumentList list = studio.getPresentationInstruments();
+ InstrumentList::iterator it;
+ int currentDevId = -1;
+
+ for (it = list.begin(); it != list.end(); it++) {
+
+ if (! (*it))
+ continue; // sanity check
+
+ //QString iname(strtoqstr((*it)->getPresentationName()));
+ QString iname(strtoqstr((*it)->getName()));
+ QString pname(strtoqstr((*it)->getProgramName()));
+ Device *device = (*it)->getDevice();
+ DeviceId devId = device->getId();
+
+ if ((*it)->getType() == Instrument::SoftSynth) {
+ iname.replace("Synth plugin ", "");
+ pname = "";
+ AudioPluginInstance *plugin = (*it)->getPlugin
+ (Instrument::SYNTH_PLUGIN_POSITION);
+ if (plugin) {
+ pname = strtoqstr(plugin->getProgram());
+ QString identifier = strtoqstr(plugin->getIdentifier());
+ if (identifier != "") {
+ QString type, soName, label;
+ PluginIdentifier::parseIdentifier
+ (identifier, type, soName, label);
+ if (pname == "") {
+ pname = strtoqstr(plugin->getDistinctiveConfigurationText());
+ }
+ if (pname != "") {
+ pname = QString("%1: %2").arg(label).arg(pname);
+ } else {
+ pname = label;
+ }
+ }
+ }
+ }
+
+ if (devId != (DeviceId)(currentDevId)) {
+ currentDevId = int(devId);
+ QString deviceName = strtoqstr(device->getName());
+ m_playDevice->insertItem(deviceName);
+ m_playDeviceIds.push_back(currentDevId);
+ }
+
+ if (pname != "")
+ iname += " (" + pname + ")";
+ m_instrumentIds[currentDevId].push_back((*it)->getId());
+ m_instrumentNames[currentDevId].append(iname);
+
+ }
+
+ m_playDevice->setCurrentItem( -1);
+ m_instrument->setCurrentItem( -1);
+}
+
+void
+TrackParameterBox::populateRecordingDeviceList()
+{
+ RG_DEBUG << "TrackParameterBox::populateRecordingDeviceList()\n";
+
+ if (m_selectedTrackId < 0)
+ return ;
+ Composition &comp = m_doc->getComposition();
+ Track *trk = comp.getTrackById(m_selectedTrackId);
+ if (!trk)
+ return ;
+
+ Instrument *inst = m_doc->getStudio().getInstrumentById(trk->getInstrument());
+ if (!inst)
+ return ;
+
+ if (m_lastInstrumentType != (char)inst->getInstrumentType()) {
+ m_lastInstrumentType = (char)inst->getInstrumentType();
+
+ m_recDevice->clear();
+ m_recDeviceIds.clear();
+ m_recChannel->clear();
+
+ if (inst->getInstrumentType() == Instrument::Audio) {
+
+ m_recDeviceIds.push_back(Device::NO_DEVICE);
+ m_recDevice->insertItem(i18n("Audio"));
+ m_recChannel->insertItem(i18n("Audio"));
+
+ m_recDevice->setEnabled(false);
+ m_recChannel->setEnabled(false);
+
+ // hide these for audio instruments
+ m_defaultsGroup->parentWidget()->setShown(false);
+
+ } else { // InstrumentType::Midi and InstrumentType::SoftSynth
+
+ // show these if not audio instrument
+ m_defaultsGroup->parentWidget()->setShown(true);
+
+ m_recDeviceIds.push_back(Device::ALL_DEVICES);
+ m_recDevice->insertItem(i18n("All"));
+
+ DeviceList *devices = m_doc->getStudio().getDevices();
+ DeviceListConstIterator it;
+ for (it = devices->begin(); it != devices->end(); it++) {
+ MidiDevice *dev =
+ dynamic_cast<MidiDevice*>(*it);
+ if (dev) {
+ if (dev->getDirection() == MidiDevice::Record
+ && dev->isRecording()) {
+ QString connection = strtoqstr(dev->getConnection());
+ // remove trailing "(duplex)", "(read only)", "(write only)" etc
+ connection.replace(QRegExp("\\s*\\([^)0-9]+\\)\\s*$"), "");
+ m_recDevice->insertItem(connection);
+ m_recDeviceIds.push_back(dev->getId());
+ }
+ }
+ }
+
+ m_recChannel->insertItem(i18n("All"));
+ for (int i = 1; i < 17; ++i) {
+ m_recChannel->insertItem(QString::number(i));
+ }
+
+ m_recDevice->setEnabled(true);
+ m_recChannel->setEnabled(true);
+ }
+ }
+
+ if (inst->getInstrumentType() == Instrument::Audio) {
+ m_recDevice->setCurrentItem(0);
+ m_recChannel->setCurrentItem(0);
+ } else {
+ m_recDevice->setCurrentItem(0);
+ m_recChannel->setCurrentItem((int)trk->getMidiInputChannel() + 1);
+ for (unsigned int i = 0; i < m_recDeviceIds.size(); ++i) {
+ if (m_recDeviceIds[i] == trk->getMidiInputDevice()) {
+ m_recDevice->setCurrentItem(i);
+ break;
+ }
+ }
+ }
+}
+
+void
+TrackParameterBox::updateHighLow()
+{
+ Composition &comp = m_doc->getComposition();
+ Track *trk = comp.getTrackById(comp.getSelectedTrack());
+ if (!trk)
+ return ;
+
+ trk->setHighestPlayable(m_highestPlayable);
+ trk->setLowestPlayable(m_lowestPlayable);
+
+ Accidental accidental = Accidentals::NoAccidental;
+
+ Pitch highest(m_highestPlayable, accidental);
+ Pitch lowest(m_lowestPlayable, accidental);
+
+ KConfig *config = kapp->config();
+ config->setGroup(GeneralOptionsConfigGroup);
+ int base = config->readNumEntry("midipitchoctave", -2);
+ bool useSharps = true;
+ bool includeOctave = true;
+
+// m_highButton->setText(i18n("High: %1").arg(highest.getAsString(useSharps, includeOctave, base)));
+// m_lowButton->setText(i18n("Low: %1").arg(lowest.getAsString(useSharps, includeOctave, base)));
+ m_highButton->setText(QString("%1").arg(highest.getAsString(useSharps, includeOctave, base)));
+ m_lowButton->setText(QString("%1").arg(lowest.getAsString(useSharps, includeOctave, base)));
+
+ m_presetLbl->setEnabled(false);
+}
+
+void
+TrackParameterBox::slotUpdateControls(int /*dummy*/)
+{
+ RG_DEBUG << "TrackParameterBox::slotUpdateControls()\n";
+ slotPlaybackDeviceChanged( -1);
+ slotInstrumentChanged( -1);
+
+ if (m_selectedTrackId < 0)
+ return ;
+ Composition &comp = m_doc->getComposition();
+ Track *trk = comp.getTrackById(m_selectedTrackId);
+ if (!trk)
+ return ;
+
+ m_defClef->setCurrentItem(trk->getClef());
+ m_defTranspose->setCurrentItem(QString("%1").arg(trk->getTranspose()), true);
+ m_defColor->setCurrentItem(trk->getColor());
+ m_highestPlayable = trk->getHighestPlayable();
+ m_lowestPlayable = trk->getLowestPlayable();
+ updateHighLow();
+ // set this down here because updateHighLow just disabled the label
+ m_presetLbl->setText(trk->getPresetLabel());
+ m_presetLbl->setEnabled(true);
+
+ m_staffSizeCombo->setCurrentItem(trk->getStaffSize());
+ m_staffBracketCombo->setCurrentItem(trk->getStaffBracket());
+}
+
+void
+TrackParameterBox::slotSelectedTrackChanged()
+{
+ RG_DEBUG << "TrackParameterBox::slotSelectedTrackChanged()\n";
+ Composition &comp = m_doc->getComposition();
+ TrackId newTrack = comp.getSelectedTrack();
+ if ( newTrack != m_selectedTrackId ) {
+ m_presetLbl->setEnabled(true);
+ m_selectedTrackId = newTrack;
+ slotSelectedTrackNameChanged();
+ slotUpdateControls( -1);
+ }
+}
+
+void
+TrackParameterBox::slotSelectedTrackNameChanged()
+{
+ RG_DEBUG << "TrackParameterBox::sotSelectedTrackNameChanged()\n";
+ Composition &comp = m_doc->getComposition();
+ Track *trk = comp.getTrackById(m_selectedTrackId);
+ QString m_trackName = trk->getLabel();
+ if (m_trackName.isEmpty())
+ m_trackName = i18n("<untitled>");
+ else
+ m_trackName.truncate(20);
+ int trackNum = trk->getPosition() + 1;
+ m_trackLabel->setText(i18n("[ Track %1 - %2 ]").arg(trackNum).arg(m_trackName));
+}
+
+void
+TrackParameterBox::slotPlaybackDeviceChanged(int index)
+{
+ RG_DEBUG << "TrackParameterBox::slotPlaybackDeviceChanged(" << index << ")\n";
+ DeviceId devId;
+ if (index == -1) {
+ if (m_selectedTrackId < 0)
+ return ;
+ Composition &comp = m_doc->getComposition();
+ Track *trk = comp.getTrackById(m_selectedTrackId);
+ if (!trk)
+ return ;
+ Instrument *inst = m_doc->getStudio().getInstrumentById(trk->getInstrument());
+ if (!inst)
+ return ;
+ devId = inst->getDevice()->getId();
+ int pos = -1;
+ IdsVector::const_iterator it;
+ for ( it = m_playDeviceIds.begin(); it != m_playDeviceIds.end(); ++it) {
+ pos++;
+ if ((*it) == devId)
+ break;
+ }
+ m_playDevice->setCurrentItem(pos);
+ } else {
+ devId = m_playDeviceIds[index];
+ }
+
+ m_instrument->clear();
+ m_instrument->insertStringList(m_instrumentNames[devId]);
+
+ populateRecordingDeviceList();
+
+ if (index != -1) {
+ m_instrument->setCurrentItem(0);
+ slotInstrumentChanged(0);
+ }
+}
+
+void
+TrackParameterBox::slotInstrumentChanged(int index)
+{
+ RG_DEBUG << "TrackParameterBox::slotInstrumentChanged(" << index << ")\n";
+ DeviceId devId;
+ Instrument *inst;
+ if (index == -1) {
+ Composition &comp = m_doc->getComposition();
+ Track *trk = comp.getTrackById(comp.getSelectedTrack());
+ if (!trk)
+ return ;
+ inst = m_doc->getStudio().getInstrumentById(trk->getInstrument());
+ if (!inst)
+ return ;
+ devId = inst->getDevice()->getId();
+
+ int pos = -1;
+ IdsVector::const_iterator it;
+ for ( it = m_instrumentIds[devId].begin(); it != m_instrumentIds[devId].end(); ++it ) {
+ pos++;
+ if ((*it) == trk->getInstrument())
+ break;
+ }
+ m_instrument->setCurrentItem(pos);
+ } else {
+ devId = m_playDeviceIds[m_playDevice->currentItem()];
+ // set the new selected instrument for the track
+ int item = 0;
+ std::map<DeviceId, IdsVector>::const_iterator it;
+ for ( it = m_instrumentIds.begin(); it != m_instrumentIds.end(); ++it) {
+ if ( (*it).first == devId )
+ break;
+ item += (*it).second.size();
+ }
+ item += index;
+ RG_DEBUG << "TrackParameterBox::slotInstrumentChanged() item = " << item << "\n";
+ emit instrumentSelected( m_selectedTrackId, item );
+ }
+}
+
+void
+TrackParameterBox::slotRecordingDeviceChanged(int index)
+{
+ RG_DEBUG << "TrackParameterBox::slotRecordingDeviceChanged(" << index << ")" << endl;
+ Composition &comp = m_doc->getComposition();
+ Track *trk = comp.getTrackById(comp.getSelectedTrack());
+ if (!trk)
+ return ;
+ Instrument *inst = m_doc->getStudio().getInstrumentById(trk->getInstrument());
+ if (!inst)
+ return ;
+ if (inst->getInstrumentType() == Instrument::Audio) {
+ //Not implemented yet
+ } else {
+ trk->setMidiInputDevice(m_recDeviceIds[index]);
+ }
+}
+
+void
+TrackParameterBox::slotRecordingChannelChanged(int index)
+{
+ RG_DEBUG << "TrackParameterBox::slotRecordingChannelChanged(" << index << ")" << endl;
+ Composition &comp = m_doc->getComposition();
+ Track *trk = comp.getTrackById(comp.getSelectedTrack());
+ if (!trk)
+ return ;
+ Instrument *inst = m_doc->getStudio().getInstrumentById(trk->getInstrument());
+ if (!inst)
+ return ;
+ if (inst->getInstrumentType() == Instrument::Audio) {
+ //Not implemented yet
+ } else {
+ trk->setMidiInputChannel(index - 1);
+ }
+}
+
+void
+TrackParameterBox::slotInstrumentLabelChanged(InstrumentId id, QString label)
+{
+ RG_DEBUG << "TrackParameterBox::slotInstrumentLabelChanged(" << id << ") = " << label << "\n";
+ populatePlaybackDeviceList();
+ slotUpdateControls( -1);
+}
+
+void
+TrackParameterBox::showAdditionalControls(bool showThem)
+{
+ // m_defaultsGroup->parentWidget()->setShown(showThem);
+}
+
+void
+TrackParameterBox::slotClefChanged(int clef)
+{
+ RG_DEBUG << "TrackParameterBox::slotClefChanged(" << clef << ")" << endl;
+ Composition &comp = m_doc->getComposition();
+ Track *trk = comp.getTrackById(comp.getSelectedTrack());
+ trk->setClef(clef);
+ m_presetLbl->setEnabled(false);
+}
+
+void
+TrackParameterBox::slotTransposeChanged(int transpose)
+{
+ RG_DEBUG << "TrackParameterBox::slotTransposeChanged(" << transpose << ")" << endl;
+ Composition &comp = m_doc->getComposition();
+ Track *trk = comp.getTrackById(comp.getSelectedTrack());
+ trk->setTranspose(transpose);
+ m_presetLbl->setEnabled(false);
+}
+
+void
+TrackParameterBox::slotTransposeIndexChanged(int index)
+{
+ slotTransposeTextChanged(m_defTranspose->text(index));
+}
+
+void
+TrackParameterBox::slotTransposeTextChanged(QString text)
+{
+ if (text.isEmpty())
+ return ;
+ int value = text.toInt();
+ slotTransposeChanged(value);
+}
+
+void
+TrackParameterBox::slotDocColoursChanged()
+{
+ RG_DEBUG << "TrackParameterBox::slotDocColoursChanged()" << endl;
+
+ m_defColor->clear();
+ m_colourList.clear();
+ // Populate it from composition.m_segmentColourMap
+ ColourMap temp = m_doc->getComposition().getSegmentColourMap();
+
+ unsigned int i = 0;
+
+ for (RCMap::const_iterator it = temp.begin(); it != temp.end(); ++it) {
+ QString qtrunc(strtoqstr(it->second.second));
+ QPixmap colour(15, 15);
+ colour.fill(GUIPalette::convertColour(it->second.first));
+ if (qtrunc == "") {
+ m_defColor->insertItem(colour, i18n("Default"), i);
+ } else {
+ // truncate name to 15 characters to avoid the combo forcing the
+ // whole kit and kaboodle too wide
+ if (qtrunc.length() > 15)
+ qtrunc = qtrunc.left(12) + "...";
+ m_defColor->insertItem(colour, qtrunc, i);
+ }
+ m_colourList[it->first] = i; // maps colour number to menu index
+ ++i;
+ }
+
+ m_addColourPos = i;
+ m_defColor->insertItem(i18n("Add New Color"), m_addColourPos);
+
+ m_defColor->setCurrentItem(0);
+}
+
+void
+TrackParameterBox::slotColorChanged(int index)
+{
+ RG_DEBUG << "TrackParameterBox::slotColorChanged(" << index << ")" << endl;
+
+ Composition &comp = m_doc->getComposition();
+ Track *trk = comp.getTrackById(comp.getSelectedTrack());
+
+ trk->setColor(index);
+
+ if (index == m_addColourPos) {
+ ColourMap newMap = m_doc->getComposition().getSegmentColourMap();
+ QColor newColour;
+ bool ok = false;
+ QString newName = KLineEditDlg::getText(i18n("New Color Name"), i18n("Enter new name"),
+ i18n("New"), &ok);
+ if ((ok == true) && (!newName.isEmpty())) {
+ KColorDialog box(this, "", true);
+
+ int result = box.getColor(newColour);
+
+ if (result == KColorDialog::Accepted) {
+ Colour newRColour = GUIPalette::convertColour(newColour);
+ newMap.addItem(newRColour, qstrtostr(newName));
+ slotDocColoursChanged();
+ }
+ }
+ // Else we don't do anything as they either didn't give a name�
+ // or didn't give a colour
+ }
+}
+
+void
+TrackParameterBox::slotHighestPressed()
+{
+ RG_DEBUG << "TrackParameterBox::slotHighestPressed()" << endl;
+
+ Composition &comp = m_doc->getComposition();
+ Track *trk = comp.getTrackById(comp.getSelectedTrack());
+ if (!trk)
+ return ;
+
+ PitchPickerDialog dialog(0, m_highestPlayable, i18n("Highest playable note"));
+
+ if (dialog.exec() == QDialog::Accepted) {
+ m_highestPlayable = dialog.getPitch();
+ updateHighLow();
+ }
+
+ m_presetLbl->setEnabled(false);
+}
+
+void
+TrackParameterBox::slotLowestPressed()
+{
+ RG_DEBUG << "TrackParameterBox::slotLowestPressed()" << endl;
+
+ Composition &comp = m_doc->getComposition();
+ Track *trk = comp.getTrackById(comp.getSelectedTrack());
+ if (!trk)
+ return ;
+
+ PitchPickerDialog dialog(0, m_lowestPlayable, i18n("Lowest playable note"));
+
+ if (dialog.exec() == QDialog::Accepted) {
+ m_lowestPlayable = dialog.getPitch();
+ updateHighLow();
+ }
+
+ m_presetLbl->setEnabled(false);
+}
+
+void
+TrackParameterBox::slotPresetPressed()
+{
+ RG_DEBUG << "TrackParameterBox::slotPresetPressed()" << endl;
+
+ Composition &comp = m_doc->getComposition();
+ Track *trk = comp.getTrackById(comp.getSelectedTrack());
+ if (!trk)
+ return ;
+
+ PresetHandlerDialog dialog(this);
+
+ try {
+ if (dialog.exec() == QDialog::Accepted) {
+ m_presetLbl->setText(dialog.getName());
+ trk->setPresetLabel(dialog.getName());
+ if (dialog.getConvertAllSegments()) {
+ SegmentSyncCommand* command = new SegmentSyncCommand(
+ comp.getSegments(), comp.getSelectedTrack(),
+ dialog.getTranspose(), dialog.getLowRange(),
+ dialog.getHighRange(),
+ clefIndexToClef(dialog.getClef()));
+ m_doc->getCommandHistory()->addCommand(command);
+ }
+ m_defClef->setCurrentItem(dialog.getClef());
+ m_defTranspose->setCurrentItem(QString("%1").arg
+ (dialog.getTranspose()), true);
+
+ m_highestPlayable = dialog.getHighRange();
+ m_lowestPlayable = dialog.getLowRange();
+ updateHighLow();
+ slotClefChanged(dialog.getClef());
+ slotTransposeChanged(dialog.getTranspose());
+
+ // the preceding slots will have set this disabled, so we
+ // re-enable it until it is subsequently re-disabled by the
+ // user overriding the preset, calling one of the above slots
+ // in the normal course
+ m_presetLbl->setEnabled(true);
+ }
+ } catch (Exception e) {
+ //!!! This should be a more verbose error to pass along the
+ // row/column of the corruption, but I can't be bothered to work
+ // that out just at the moment. Hopefully this code will never
+ // execute anyway.
+ KMessageBox::sorry(0, i18n("The instrument preset database is corrupt. Check your installation."));
+ }
+
+}
+
+void
+TrackParameterBox::slotStaffSizeChanged(int index)
+{
+ RG_DEBUG << "TrackParameterBox::sotStaffSizeChanged()" << endl;
+ Composition &comp = m_doc->getComposition();
+ Track *trk = comp.getTrackById(m_selectedTrackId);
+
+ trk->setStaffSize(index);
+}
+
+
+void
+TrackParameterBox::slotStaffBracketChanged(int index)
+{
+ RG_DEBUG << "TrackParameterBox::sotStaffBracketChanged()" << endl;
+ Composition &comp = m_doc->getComposition();
+ Track *trk = comp.getTrackById(m_selectedTrackId);
+
+ trk->setStaffBracket(index);
+}
+
+QString
+TrackParameterBox::getPreviousBox(RosegardenParameterArea::Arrangement arrangement) const
+{
+ if (arrangement == RosegardenParameterArea::CLASSIC_STYLE) {
+ return i18n("Segment");
+ } else {
+ return "";
+ }
+}
+
+}
+#include "TrackParameterBox.moc"
diff --git a/src/gui/editors/parameters/TrackParameterBox.h b/src/gui/editors/parameters/TrackParameterBox.h
new file mode 100644
index 0000000..c5fa0f9
--- /dev/null
+++ b/src/gui/editors/parameters/TrackParameterBox.h
@@ -0,0 +1,161 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
+
+/*
+ Rosegarden
+ A MIDI and audio sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ This file is Copyright 2006
+ Pedro Lopez-Cabanillas <plcl@users.sourceforge.net>
+ D. Michael McIntyre <dmmcintyr@users.sourceforge.net>
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_TRACKPARAMETERBOX_H_
+#define _RG_TRACKPARAMETERBOX_H_
+
+#include "base/MidiProgram.h"
+#include "base/Track.h"
+#include "gui/widgets/ColourTable.h"
+#include <map>
+#include "RosegardenParameterArea.h"
+#include "RosegardenParameterBox.h"
+#include <qstring.h>
+#include <qcheckbox.h> // #include <QCheckBox> in QT4, thinking ahead
+#include <vector>
+
+
+class QWidget;
+class QPushButton;
+class QLabel;
+class QFrame;
+class KComboBox;
+class QCheckBox;
+
+
+namespace Rosegarden
+{
+
+class RosegardenGUIDoc;
+
+
+class TrackParameterBox : public RosegardenParameterBox
+{
+Q_OBJECT
+
+public:
+ TrackParameterBox( RosegardenGUIDoc *doc,
+ QWidget *parent=0);
+ ~TrackParameterBox();
+
+ void setDocument( RosegardenGUIDoc *doc );
+ void populateDeviceLists();
+ virtual void showAdditionalControls(bool showThem);
+
+ virtual QString getPreviousBox(RosegardenParameterArea::Arrangement) const;
+
+public slots:
+ void slotSelectedTrackChanged();
+ void slotSelectedTrackNameChanged();
+ void slotPlaybackDeviceChanged(int index);
+ void slotInstrumentChanged(int index);
+ void slotRecordingDeviceChanged(int index);
+ void slotRecordingChannelChanged(int index);
+ void slotUpdateControls(int);
+ void slotInstrumentLabelChanged(InstrumentId id, QString label);
+
+ void slotClefChanged(int clef);
+ void slotTransposeChanged(int transpose);
+ void slotTransposeIndexChanged(int index);
+ void slotTransposeTextChanged(QString text);
+ void slotDocColoursChanged();
+ void slotColorChanged(int index);
+ void slotHighestPressed();
+ void slotLowestPressed();
+ void slotPresetPressed();
+
+ void slotStaffSizeChanged(int index);
+ void slotStaffBracketChanged(int index);
+
+signals:
+ void instrumentSelected(TrackId, int);
+
+protected:
+ void populatePlaybackDeviceList();
+ void populateRecordingDeviceList();
+ void updateHighLow();
+
+private:
+ RosegardenGUIDoc *m_doc;
+
+ KComboBox *m_playDevice;
+ KComboBox *m_instrument;
+ KComboBox *m_recDevice;
+ KComboBox *m_recChannel;
+
+ QPushButton *m_presetButton;
+ QPushButton *m_highButton;
+ QPushButton *m_lowButton;
+
+ KComboBox *m_defClef;
+ KComboBox *m_defColor;
+ KComboBox *m_defTranspose;
+ KComboBox *m_staffSizeCombo;
+ KComboBox *m_staffBracketCombo;
+
+
+ int m_addColourPos;
+ int m_highestPlayable;
+ int m_lowestPlayable;
+ ColourTable::ColourList m_colourList;
+
+ QLabel *m_trackLabel;
+
+ typedef std::vector<DeviceId> IdsVector;
+
+ IdsVector m_playDeviceIds;
+ IdsVector m_recDeviceIds;
+
+ std::map<DeviceId, IdsVector> m_instrumentIds;
+ std::map<DeviceId, QStringList> m_instrumentNames;
+
+ int m_selectedTrackId;
+
+ char m_lastInstrumentType;
+
+ // Additional elements that may be hidden in vertical stacked mode
+ //QFrame *m_separator2;
+ QFrame *m_playbackGroup;
+ QFrame *m_recordGroup;
+ QFrame *m_defaultsGroup;
+ QFrame *m_staffGroup;
+ QLabel *m_segHeader;
+ QLabel *m_presetLbl;
+ QLabel *m_staffGrpLbl;
+ QLabel *m_grandStaffLbl;
+ QLabel *m_clefLbl;
+ QLabel *m_transpLbl;
+ QLabel *m_colorLbl;
+ QLabel *m_rangeLbl;
+ QLabel *m_psetLbl;
+};
+
+
+}
+
+#endif
diff --git a/src/gui/editors/segment/ControlEditorDialog.cpp b/src/gui/editors/segment/ControlEditorDialog.cpp
new file mode 100644
index 0000000..3c4cc47
--- /dev/null
+++ b/src/gui/editors/segment/ControlEditorDialog.cpp
@@ -0,0 +1,446 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
+
+/*
+ Rosegarden
+ A MIDI and audio sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "ControlEditorDialog.h"
+#include <qlayout.h>
+#include <kapplication.h>
+
+#include <klocale.h>
+#include "misc/Debug.h"
+#include "misc/Strings.h"
+#include "base/Colour.h"
+#include "base/Composition.h"
+#include "base/ControlParameter.h"
+#include "base/Device.h"
+#include "base/Event.h"
+#include "base/MidiDevice.h"
+#include "base/MidiTypes.h"
+#include "base/Studio.h"
+#include "commands/studio/AddControlParameterCommand.h"
+#include "commands/studio/ModifyControlParameterCommand.h"
+#include "commands/studio/RemoveControlParameterCommand.h"
+#include "ControlParameterEditDialog.h"
+#include "ControlParameterItem.h"
+#include "document/MultiViewCommandHistory.h"
+#include "document/RosegardenGUIDoc.h"
+#include "document/ConfigGroups.h"
+#include <kaction.h>
+#include <kcommand.h>
+#include <klistview.h>
+#include <kmainwindow.h>
+#include <kstdaccel.h>
+#include <kstdaction.h>
+#include <qcolor.h>
+#include <qdialog.h>
+#include <qframe.h>
+#include <qlabel.h>
+#include <qlistview.h>
+#include <qpixmap.h>
+#include <qptrlist.h>
+#include <qpushbutton.h>
+#include <qsizepolicy.h>
+#include <qstring.h>
+#include <qtooltip.h>
+#include <qvbox.h>
+#include <qwidget.h>
+
+
+namespace Rosegarden
+{
+
+const QString notShowing(i18n("<not showing>"));
+
+ControlEditorDialog::ControlEditorDialog(QWidget *parent,
+ RosegardenGUIDoc *doc,
+ DeviceId device):
+ KMainWindow(parent, "controleditordialog"),
+ m_studio(&doc->getStudio()),
+ m_doc(doc),
+ m_device(device),
+ m_modified(false)
+{
+ RG_DEBUG << "ControlEditorDialog::ControlEditorDialog: device is " << m_device << endl;
+
+ QVBox* mainFrame = new QVBox(this);
+ setCentralWidget(mainFrame);
+
+ setCaption(i18n("Manage Control Events"));
+
+ QString deviceName(i18n("<no device>"));
+ MidiDevice *md =
+ dynamic_cast<MidiDevice *>(m_studio->getDevice(m_device));
+ if (md)
+ deviceName = strtoqstr(md->getName());
+
+ // spacing hack!
+ new QLabel("", mainFrame);
+ new QLabel(i18n(" Control Events for %1 (device %2)").arg(deviceName).
+ arg(device), mainFrame);
+ new QLabel("", mainFrame);
+
+ m_listView = new KListView(mainFrame);
+ m_listView->addColumn(i18n("Control Event name "));
+ m_listView->addColumn(i18n("Control Event type "));
+ m_listView->addColumn(i18n("Control Event value "));
+ m_listView->addColumn(i18n("Description "));
+ m_listView->addColumn(i18n("Min "));
+ m_listView->addColumn(i18n("Max "));
+ m_listView->addColumn(i18n("Default "));
+ m_listView->addColumn(i18n("Color "));
+ m_listView->addColumn(i18n("Position on instrument panel"));
+
+ m_listView->setColumnAlignment(0, Qt::AlignLeft);
+
+ // Align remaining columns centrally
+ for (int i = 1; i < 9; ++i)
+ m_listView->setColumnAlignment(i, Qt::AlignHCenter);
+
+ m_listView->restoreLayout(kapp->config(), ControlEditorConfigGroup);
+
+ QFrame* btnBox = new QFrame(mainFrame);
+
+ btnBox->setSizePolicy(
+ QSizePolicy(QSizePolicy::Minimum, QSizePolicy::Fixed));
+
+ QHBoxLayout* layout = new QHBoxLayout(btnBox, 4, 10);
+
+ m_addButton = new QPushButton(i18n("Add"), btnBox);
+ m_deleteButton = new QPushButton(i18n("Delete"), btnBox);
+
+ m_closeButton = new QPushButton(i18n("Close"), btnBox);
+
+ QToolTip::add
+ (m_addButton,
+ i18n("Add a Control Parameter to the Studio"));
+
+ QToolTip::add
+ (m_deleteButton,
+ i18n("Delete a Control Parameter from the Studio"));
+
+ QToolTip::add
+ (m_closeButton,
+ i18n("Close the Control Parameter editor"));
+
+ layout->addStretch(10);
+ layout->addWidget(m_addButton);
+ layout->addWidget(m_deleteButton);
+ layout->addSpacing(30);
+
+ layout->addWidget(m_closeButton);
+ layout->addSpacing(5);
+
+ connect(m_addButton, SIGNAL(released()),
+ SLOT(slotAdd()));
+
+ connect(m_deleteButton, SIGNAL(released()),
+ SLOT(slotDelete()));
+
+ setupActions();
+
+ m_doc->getCommandHistory()->attachView(actionCollection());
+ connect(m_doc->getCommandHistory(), SIGNAL(commandExecuted()),
+ this, SLOT(slotUpdate()));
+
+ connect(m_listView, SIGNAL(doubleClicked(QListViewItem *)),
+ SLOT(slotEdit(QListViewItem *)));
+
+ // Highlight all columns - enable extended selection mode
+ //
+ m_listView->setAllColumnsShowFocus(true);
+ m_listView->setSelectionMode(QListView::Extended);
+
+ initDialog();
+
+ setAutoSaveSettings(ControlEditorConfigGroup, true);
+}
+
+ControlEditorDialog::~ControlEditorDialog()
+{
+ RG_DEBUG << "\n*** ControlEditorDialog::~ControlEditorDialog\n" << endl;
+
+ m_listView->saveLayout(kapp->config(), ControlEditorConfigGroup);
+
+ if (m_doc)
+ m_doc->getCommandHistory()->detachView(actionCollection());
+}
+
+void
+ControlEditorDialog::initDialog()
+{
+ RG_DEBUG << "ControlEditorDialog::initDialog" << endl;
+ slotUpdate();
+}
+
+void
+ControlEditorDialog::slotUpdate()
+{
+ RG_DEBUG << "ControlEditorDialog::slotUpdate" << endl;
+
+ //QPtrList<QListViewItem> selection = m_listView->selectedItems();
+
+ MidiDevice *md =
+ dynamic_cast<MidiDevice *>(m_studio->getDevice(m_device));
+ if (!md)
+ return ;
+
+ ControlList::const_iterator it = md->beginControllers();
+ QListViewItem *item;
+ int i = 0;
+
+ m_listView->clear();
+
+ for (; it != md->endControllers(); ++it) {
+ Composition &comp = m_doc->getComposition();
+
+ QString colour =
+ strtoqstr(comp.getGeneralColourMap().getNameByIndex(it->getColourIndex()));
+
+ if (colour == "")
+ colour = i18n("<default>");
+
+ QString position = QString("%1").arg(it->getIPBPosition());
+ if (position.toInt() == -1)
+ position = notShowing;
+
+ QString value;
+ value.sprintf("%d (0x%x)", it->getControllerValue(),
+ it->getControllerValue());
+
+ if (it->getType() == PitchBend::EventType) {
+ item = new ControlParameterItem(i++,
+ m_listView,
+ strtoqstr(it->getName()),
+ strtoqstr(it->getType()),
+ QString("-"),
+ strtoqstr(it->getDescription()),
+ QString("%1").arg(it->getMin()),
+ QString("%1").arg(it->getMax()),
+ QString("%1").arg(it->getDefault()),
+ colour,
+ position);
+ } else {
+ item = new ControlParameterItem(i++,
+ m_listView,
+ strtoqstr(it->getName()),
+ strtoqstr(it->getType()),
+ value,
+ strtoqstr(it->getDescription()),
+ QString("%1").arg(it->getMin()),
+ QString("%1").arg(it->getMax()),
+ QString("%1").arg(it->getDefault()),
+ colour,
+ position);
+ }
+
+
+ // create and set a colour pixmap
+ //
+ QPixmap colourPixmap(16, 16);
+ Colour c = comp.getGeneralColourMap().getColourByIndex(it->getColourIndex());
+ colourPixmap.fill(QColor(c.getRed(), c.getGreen(), c.getBlue()));
+ item->setPixmap(7, colourPixmap);
+
+ m_listView->insertItem(item);
+ }
+
+ if (m_listView->childCount() == 0) {
+ QListViewItem *item = new QListViewItem(m_listView, i18n("<none>"));
+ m_listView->insertItem(item);
+
+ m_listView->setSelectionMode(QListView::NoSelection);
+ } else {
+ m_listView->setSelectionMode(QListView::Extended);
+ }
+
+
+}
+
+/*
+void
+ControlEditorDialog::slotEditCopy()
+{
+ RG_DEBUG << "ControlEditorDialog::slotEditCopy" << endl;
+}
+
+void
+ControlEditorDialog::slotEditPaste()
+{
+ RG_DEBUG << "ControlEditorDialog::slotEditPaste" << endl;
+}
+*/
+
+void
+ControlEditorDialog::slotAdd()
+{
+ RG_DEBUG << "ControlEditorDialog::slotAdd to device " << m_device << endl;
+
+ AddControlParameterCommand *command =
+ new AddControlParameterCommand(m_studio, m_device,
+ ControlParameter());
+
+ addCommandToHistory(command);
+}
+
+void
+ControlEditorDialog::slotDelete()
+{
+ RG_DEBUG << "ControlEditorDialog::slotDelete" << endl;
+
+ if (!m_listView->currentItem())
+ return ;
+
+ ControlParameterItem *item =
+ dynamic_cast<ControlParameterItem*>(m_listView->currentItem());
+
+ if (item) {
+ RemoveControlParameterCommand *command =
+ new RemoveControlParameterCommand(m_studio, m_device, item->getId());
+
+ addCommandToHistory(command);
+ }
+}
+
+void
+ControlEditorDialog::slotClose()
+{
+ RG_DEBUG << "ControlEditorDialog::slotClose" << endl;
+
+ if (m_doc)
+ m_doc->getCommandHistory()->detachView(actionCollection());
+ m_doc = 0;
+
+ close();
+}
+
+void
+ControlEditorDialog::setupActions()
+{
+ KAction* close = KStdAction::close(this,
+ SLOT(slotClose()),
+ actionCollection());
+
+ m_closeButton->setText(close->text());
+ connect(m_closeButton, SIGNAL(released()), this, SLOT(slotClose()));
+
+ // some adjustments
+ new KToolBarPopupAction(i18n("Und&o"),
+ "undo",
+ KStdAccel::key(KStdAccel::Undo),
+ actionCollection(),
+ KStdAction::stdName(KStdAction::Undo));
+
+ new KToolBarPopupAction(i18n("Re&do"),
+ "redo",
+ KStdAccel::key(KStdAccel::Redo),
+ actionCollection(),
+ KStdAction::stdName(KStdAction::Redo));
+
+ createGUI("controleditor.rc");
+}
+
+void
+ControlEditorDialog::addCommandToHistory(KCommand *command)
+{
+ getCommandHistory()->addCommand(command);
+ setModified(false);
+}
+
+MultiViewCommandHistory*
+ControlEditorDialog::getCommandHistory()
+{
+ return m_doc->getCommandHistory();
+}
+
+void
+ControlEditorDialog::setModified(bool modified)
+{
+ RG_DEBUG << "ControlEditorDialog::setModified(" << modified << ")" << endl;
+
+ if (modified) {}
+ else {}
+
+ m_modified = modified;
+}
+
+void
+ControlEditorDialog::checkModified()
+{
+ RG_DEBUG << "ControlEditorDialog::checkModified(" << m_modified << ")"
+ << endl;
+
+}
+
+void
+ControlEditorDialog::slotEdit()
+{}
+
+void
+ControlEditorDialog::slotEdit(QListViewItem *i)
+{
+ RG_DEBUG << "ControlEditorDialog::slotEdit" << endl;
+
+ ControlParameterItem *item =
+ dynamic_cast<ControlParameterItem*>(i);
+
+ MidiDevice *md =
+ dynamic_cast<MidiDevice *>(m_studio->getDevice(m_device));
+
+ if (item && md) {
+ ControlParameterEditDialog dialog
+ (this,
+ md->getControlParameter(item->getId()), m_doc);
+
+ if (dialog.exec() == QDialog::Accepted) {
+ ModifyControlParameterCommand *command =
+ new ModifyControlParameterCommand(m_studio,
+ m_device,
+ dialog.getControl(),
+ item->getId());
+
+ addCommandToHistory(command);
+ }
+ }
+}
+
+void
+ControlEditorDialog::closeEvent(QCloseEvent *e)
+{
+ emit closing();
+ KMainWindow::closeEvent(e);
+}
+
+void
+ControlEditorDialog::setDocument(RosegardenGUIDoc *doc)
+{
+ // reset our pointers
+ m_doc = doc;
+ m_studio = &doc->getStudio();
+ m_modified = false;
+
+ slotUpdate();
+}
+
+}
+#include "ControlEditorDialog.moc"
diff --git a/src/gui/editors/segment/ControlEditorDialog.h b/src/gui/editors/segment/ControlEditorDialog.h
new file mode 100644
index 0000000..9270d2c
--- /dev/null
+++ b/src/gui/editors/segment/ControlEditorDialog.h
@@ -0,0 +1,122 @@
+
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
+
+/*
+ Rosegarden
+ A MIDI and audio sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_CONTROLEDITORDIALOG_H_
+#define _RG_CONTROLEDITORDIALOG_H_
+
+#include "base/Device.h"
+#include "base/MidiDevice.h"
+#include <kmainwindow.h>
+
+
+class QWidget;
+class QPushButton;
+class QListViewItem;
+class QCloseEvent;
+class KListView;
+class KCommand;
+
+
+namespace Rosegarden
+{
+
+class Studio;
+class RosegardenGUIDoc;
+class MultiViewCommandHistory;
+
+
+class ControlEditorDialog : public KMainWindow
+{
+ Q_OBJECT
+
+public:
+ ControlEditorDialog(QWidget *parent,
+ RosegardenGUIDoc *doc,
+ DeviceId device);
+
+ ~ControlEditorDialog();
+
+ void initDialog();
+
+ void addCommandToHistory(KCommand *command);
+ MultiViewCommandHistory* getCommandHistory();
+
+ void setModified(bool value);
+ void checkModified();
+
+ // reset the document
+ void setDocument(RosegardenGUIDoc *doc);
+
+ DeviceId getDevice() { return m_device; }
+
+public slots:
+ void slotUpdate();
+
+/*
+ void slotEditCopy();
+ void slotEditPaste();
+*/
+
+ void slotAdd();
+ void slotDelete();
+ void slotClose();
+
+ void slotEdit();
+ void slotEdit(QListViewItem *);
+
+signals:
+ void closing();
+
+
+protected:
+ virtual void closeEvent(QCloseEvent *);
+
+ void setupActions();
+
+ //--------------- Data members ---------------------------------
+ Studio *m_studio;
+ RosegardenGUIDoc *m_doc;
+ DeviceId m_device;
+
+ QPushButton *m_closeButton;
+
+ QPushButton *m_copyButton;
+ QPushButton *m_pasteButton;
+
+ QPushButton *m_addButton;
+ QPushButton *m_deleteButton;
+
+ KListView *m_listView;
+
+ bool m_modified;
+
+ ControlList m_clipboard; // local clipboard only
+
+};
+
+
+}
+
+#endif
diff --git a/src/gui/editors/segment/ControlParameterEditDialog.cpp b/src/gui/editors/segment/ControlParameterEditDialog.cpp
new file mode 100644
index 0000000..bc779f5
--- /dev/null
+++ b/src/gui/editors/segment/ControlParameterEditDialog.cpp
@@ -0,0 +1,325 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
+
+/*
+ Rosegarden
+ A MIDI and audio sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "ControlParameterEditDialog.h"
+#include <qlayout.h>
+
+#include <klocale.h>
+#include "misc/Debug.h"
+#include "misc/Strings.h"
+#include "base/Colour.h"
+#include "base/ColourMap.h"
+#include "base/ControlParameter.h"
+#include "base/Event.h"
+#include "base/MidiTypes.h"
+#include "document/RosegardenGUIDoc.h"
+#include <kcombobox.h>
+#include <kdialogbase.h>
+#include <qcolor.h>
+#include <qframe.h>
+#include <qgroupbox.h>
+#include <qlabel.h>
+#include <qlineedit.h>
+#include <qpixmap.h>
+#include <qspinbox.h>
+#include <qstring.h>
+#include <qvbox.h>
+#include <qwidget.h>
+
+
+namespace Rosegarden
+{
+
+const QString notShowing(i18n("<not showing>"));
+
+ControlParameterEditDialog::ControlParameterEditDialog(
+ QWidget *parent,
+ ControlParameter *control,
+ RosegardenGUIDoc *doc):
+ KDialogBase(parent, 0, true,
+ i18n("Edit Control Parameter"), Ok | Cancel),
+ m_doc(doc),
+ m_control(control)
+{
+ m_dialogControl = *control; // copy in the ControlParameter
+
+ QVBox *vbox = makeVBoxMainWidget();
+
+ QGroupBox *groupBox = new QGroupBox
+ (1, Horizontal, i18n("Control Event Properties"), vbox);
+
+ QFrame *frame = new QFrame(groupBox);
+
+ QGridLayout *layout = new QGridLayout(frame, 4, 3, 10, 5);
+
+ layout->addWidget(new QLabel(i18n("Name:"), frame), 0, 0);
+ m_nameEdit = new QLineEdit(frame);
+ layout->addWidget(m_nameEdit, 0, 1);
+
+ layout->addWidget(new QLabel(i18n("Type:"), frame), 1, 0);
+ m_typeCombo = new KComboBox(frame);
+ layout->addMultiCellWidget(m_typeCombo, 1, 1, 1, 2);
+
+ layout->addWidget(new QLabel(i18n("Description:"), frame), 2, 0);
+ m_description = new QLineEdit(frame);
+ layout->addMultiCellWidget(m_description, 2, 2, 1, 2);
+
+ // hex value alongside decimal value
+ m_hexValue = new QLabel(frame);
+ layout->addWidget(m_hexValue, 3, 1);
+
+ layout->addWidget(new QLabel(i18n("Control Event value:"), frame), 3, 0);
+ m_controllerBox = new QSpinBox(frame);
+ layout->addWidget(m_controllerBox, 3, 2);
+
+ layout->addWidget(new QLabel(i18n("Minimum value:"), frame), 4, 0);
+ m_minBox = new QSpinBox(frame);
+ layout->addMultiCellWidget(m_minBox, 4, 4, 1, 2);
+
+ layout->addWidget(new QLabel(i18n("Maximum value:"), frame), 5, 0);
+ m_maxBox = new QSpinBox(frame);
+ layout->addMultiCellWidget(m_maxBox, 5, 5, 1, 2);
+
+ layout->addWidget(new QLabel(i18n("Default value:"), frame), 6, 0);
+ m_defaultBox = new QSpinBox(frame);
+ layout->addMultiCellWidget(m_defaultBox, 6, 6, 1, 2);
+
+ layout->addWidget(new QLabel(i18n("Color:"), frame), 7, 0);
+ m_colourCombo = new KComboBox(frame);
+ layout->addMultiCellWidget(m_colourCombo, 7, 7, 1, 2);
+
+ layout->addWidget(new QLabel(i18n("Instrument Parameter Box position:"), frame), 8, 0);
+ m_ipbPosition = new KComboBox(frame);
+ layout->addMultiCellWidget(m_ipbPosition, 8, 8, 1, 2);
+
+ connect(m_nameEdit, SIGNAL(textChanged(const QString&)),
+ SLOT(slotNameChanged(const QString&)));
+
+ connect(m_typeCombo, SIGNAL(activated(int)),
+ SLOT(slotTypeChanged(int)));
+
+ connect(m_description, SIGNAL(textChanged(const QString&)),
+ SLOT(slotDescriptionChanged(const QString &)));
+
+ connect(m_controllerBox, SIGNAL(valueChanged(int)),
+ SLOT(slotControllerChanged(int)));
+
+ connect(m_minBox, SIGNAL(valueChanged(int)),
+ SLOT(slotMinChanged(int)));
+
+ connect(m_maxBox, SIGNAL(valueChanged(int)),
+ SLOT(slotMaxChanged(int)));
+
+ connect(m_defaultBox, SIGNAL(valueChanged(int)),
+ SLOT(slotDefaultChanged(int)));
+
+ connect(m_colourCombo, SIGNAL(activated(int)),
+ SLOT(slotColourChanged(int)));
+
+ connect(m_ipbPosition, SIGNAL(activated(int)),
+ SLOT(slotIPBPositionChanged(int)));
+
+ //m_nameEdit->selectAll();
+ //m_description->selectAll();
+
+ // set limits
+ m_controllerBox->setMinValue(0);
+ m_controllerBox->setMaxValue(127);
+
+ m_minBox->setMinValue(INT_MIN);
+ m_minBox->setMaxValue(INT_MAX);
+
+ m_maxBox->setMinValue(INT_MIN);
+ m_maxBox->setMaxValue(INT_MAX);
+
+ m_defaultBox->setMinValue(INT_MIN);
+ m_defaultBox->setMaxValue(INT_MAX);
+
+ // populate combos
+ m_typeCombo->insertItem(strtoqstr(Controller::EventType));
+ m_typeCombo->insertItem(strtoqstr(PitchBend::EventType));
+ /*
+ m_typeCombo->insertItem(strtoqstr(KeyPressure::EventType));
+ m_typeCombo->insertItem(strtoqstr(ChannelPressure::EventType));
+ */
+
+ // Populate colour combo
+ //
+ //
+ ColourMap &colourMap = m_doc->getComposition().getGeneralColourMap();
+ RCMap::const_iterator it;
+ QPixmap colourPixmap(16, 16);
+
+ for (it = colourMap.begin(); it != colourMap.end(); ++it) {
+ Colour c = it->second.first;
+ colourPixmap.fill(QColor(c.getRed(), c.getGreen(), c.getBlue()));
+ m_colourCombo->insertItem(colourPixmap, strtoqstr(it->second.second));
+ }
+
+ // Populate IPB position combo
+ //
+ m_ipbPosition->insertItem(notShowing);
+ for (unsigned int i = 0; i < 32; i++)
+ m_ipbPosition->insertItem(QString("%1").arg(i));
+
+ if (m_control->getType() == Controller::EventType)
+ m_typeCombo->setCurrentItem(0);
+ else if (m_control->getType() == PitchBend::EventType)
+ m_typeCombo->setCurrentItem(1);
+ /*
+ else if (m_control->getType() == KeyPressure::EventType)
+ m_typeCombo->setCurrentItem(2);
+ else if (m_control->getType() == ChannelPressure::EventType)
+ m_typeCombo->setCurrentItem(3);
+ */
+
+ populate();
+}
+
+void
+ControlParameterEditDialog::populate()
+{
+ m_nameEdit->setText(strtoqstr(m_control->getName()));
+
+ m_description->setText(strtoqstr(m_control->getDescription()));
+ m_controllerBox->setValue(int(m_control->getControllerValue()));
+
+ QString hexValue;
+ hexValue.sprintf("(0x%x)", m_control->getControllerValue());
+ m_hexValue->setText(hexValue);
+
+ m_minBox->setValue(m_control->getMin());
+ m_maxBox->setValue(m_control->getMax());
+ m_defaultBox->setValue(m_control->getDefault());
+
+ int pos = 0, setItem = 0;
+ ColourMap &colourMap = m_doc->getComposition().getGeneralColourMap();
+ RCMap::const_iterator it;
+ for (it = colourMap.begin(); it != colourMap.end(); ++it)
+ if (m_control->getColourIndex() == it->first)
+ setItem = pos++;
+
+ m_colourCombo->setCurrentItem(setItem);
+
+ // set combo position
+ m_ipbPosition->setCurrentItem(m_control->getIPBPosition() + 1);
+
+ // If the type has changed and there are no defaults then we have to
+ // supply some.
+ //
+ if (qstrtostr(m_typeCombo->currentText()) == PitchBend::EventType ||
+ qstrtostr(m_typeCombo->currentText()) == KeyPressure::EventType ||
+ qstrtostr(m_typeCombo->currentText()) == ChannelPressure::EventType) {
+ m_controllerBox->setEnabled(false);
+ m_ipbPosition->setEnabled(false);
+ m_colourCombo->setEnabled(false);
+ m_hexValue->setEnabled(false);
+ m_minBox->setEnabled(false);
+ m_maxBox->setEnabled(false);
+ m_defaultBox->setEnabled(false);
+ } else if (qstrtostr(m_typeCombo->currentText()) == Controller::EventType) {
+ m_controllerBox->setEnabled(true);
+ m_ipbPosition->setEnabled(true);
+ m_colourCombo->setEnabled(true);
+ m_hexValue->setEnabled(true);
+ m_minBox->setEnabled(true);
+ m_maxBox->setEnabled(true);
+ m_defaultBox->setEnabled(true);
+ }
+
+}
+
+void
+ControlParameterEditDialog::slotNameChanged(const QString &str)
+{
+ RG_DEBUG << "ControlParameterEditDialog::slotNameChanged" << endl;
+ m_dialogControl.setName(qstrtostr(str));
+}
+
+void
+ControlParameterEditDialog::slotTypeChanged(int value)
+{
+ RG_DEBUG << "ControlParameterEditDialog::slotTypeChanged" << endl;
+ m_dialogControl.setType(qstrtostr(m_typeCombo->text(value)));
+
+ populate();
+}
+
+void
+ControlParameterEditDialog::slotDescriptionChanged(const QString &str)
+{
+ RG_DEBUG << "ControlParameterEditDialog::slotDescriptionChanged" << endl;
+ m_dialogControl.setDescription(qstrtostr(str));
+}
+
+void
+ControlParameterEditDialog::slotControllerChanged(int value)
+{
+ RG_DEBUG << "ControlParameterEditDialog::slotControllerChanged" << endl;
+ m_dialogControl.setControllerValue(value);
+
+ // set hex value
+ QString hexValue;
+ hexValue.sprintf("(0x%x)", value);
+ m_hexValue->setText(hexValue);
+}
+
+void
+ControlParameterEditDialog::slotMinChanged(int value)
+{
+ RG_DEBUG << "ControlParameterEditDialog::slotMinChanged" << endl;
+ m_dialogControl.setMin(value);
+}
+
+void
+ControlParameterEditDialog::slotMaxChanged(int value)
+{
+ RG_DEBUG << "ControlParameterEditDialog::slotMaxChanged" << endl;
+ m_dialogControl.setMax(value);
+}
+
+void
+ControlParameterEditDialog::slotDefaultChanged(int value)
+{
+ RG_DEBUG << "ControlParameterEditDialog::slotDefaultChanged" << endl;
+ m_dialogControl.setDefault(value);
+}
+
+void
+ControlParameterEditDialog::slotColourChanged(int value)
+{
+ RG_DEBUG << "ControlParameterEditDialog::slotColourChanged" << endl;
+ m_dialogControl.setColourIndex(value);
+}
+
+void
+ControlParameterEditDialog::slotIPBPositionChanged(int value)
+{
+ RG_DEBUG << "ControlParameterEditDialog::slotIPBPositionChanged" << endl;
+ m_dialogControl.setIPBPosition(value - 1);
+}
+
+}
+#include "ControlParameterEditDialog.moc"
diff --git a/src/gui/editors/segment/ControlParameterEditDialog.h b/src/gui/editors/segment/ControlParameterEditDialog.h
new file mode 100644
index 0000000..b9f4606
--- /dev/null
+++ b/src/gui/editors/segment/ControlParameterEditDialog.h
@@ -0,0 +1,92 @@
+
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
+
+/*
+ Rosegarden
+ A MIDI and audio sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_CONTROLPARAMETEREDITDIALOG_H_
+#define _RG_CONTROLPARAMETEREDITDIALOG_H_
+
+#include "base/ControlParameter.h"
+#include <kdialogbase.h>
+
+
+class QWidget;
+class QString;
+class QSpinBox;
+class QLineEdit;
+class QLabel;
+class KComboBox;
+
+
+namespace Rosegarden
+{
+
+class RosegardenGUIDoc;
+
+
+class ControlParameterEditDialog : public KDialogBase
+{
+ Q_OBJECT
+public:
+ ControlParameterEditDialog(QWidget *parent,
+ ControlParameter *control,
+ RosegardenGUIDoc *doc);
+
+ ControlParameter& getControl() { return m_dialogControl; }
+
+public slots:
+
+ void slotNameChanged(const QString &);
+ void slotTypeChanged(int);
+ void slotDescriptionChanged(const QString &);
+ void slotControllerChanged(int);
+ void slotMinChanged(int);
+ void slotMaxChanged(int);
+ void slotDefaultChanged(int);
+ void slotColourChanged(int);
+ void slotIPBPositionChanged(int);
+
+protected:
+ void populate(); // populate the dialog
+
+ RosegardenGUIDoc *m_doc;
+ ControlParameter *m_control;
+ ControlParameter m_dialogControl;
+
+ QLineEdit *m_nameEdit;
+ KComboBox *m_typeCombo;
+ QLineEdit *m_description;
+ QSpinBox *m_controllerBox;
+ QSpinBox *m_minBox;
+ QSpinBox *m_maxBox;
+ QSpinBox *m_defaultBox;
+ KComboBox *m_colourCombo;
+ KComboBox *m_ipbPosition;
+ QLabel *m_hexValue;
+};
+
+
+
+}
+
+#endif
diff --git a/src/gui/editors/segment/ControlParameterItem.cpp b/src/gui/editors/segment/ControlParameterItem.cpp
new file mode 100644
index 0000000..beb0922
--- /dev/null
+++ b/src/gui/editors/segment/ControlParameterItem.cpp
@@ -0,0 +1,34 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
+
+/*
+ Rosegarden
+ A MIDI and audio sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "ControlParameterItem.h"
+
+#include <qlistview.h>
+#include <qstring.h>
+
+
+namespace Rosegarden
+{
+}
diff --git a/src/gui/editors/segment/ControlParameterItem.h b/src/gui/editors/segment/ControlParameterItem.h
new file mode 100644
index 0000000..6746ca2
--- /dev/null
+++ b/src/gui/editors/segment/ControlParameterItem.h
@@ -0,0 +1,65 @@
+
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
+
+/*
+ Rosegarden
+ A MIDI and audio sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_CONTROLPARAMETERITEM_H_
+#define _RG_CONTROLPARAMETERITEM_H_
+
+#include <qstring.h>
+#include <klistview.h>
+
+
+namespace Rosegarden
+{
+
+
+class ControlParameterItem : public KListViewItem
+{
+public:
+ ControlParameterItem(int id,
+ QListView *parent,
+ QString str1,
+ QString str2,
+ QString str3,
+ QString str4,
+ QString str5,
+ QString str6,
+ QString str7,
+ QString str8,
+ QString str9):
+ KListViewItem(parent, str1, str2, str3, str4, str5, str6, str7, str8),
+ m_id(id) { setText(8, str9); }
+
+ int getId() const { return m_id; }
+
+protected:
+
+ int m_id;
+ QString m_string9;
+};
+
+
+}
+
+#endif
diff --git a/src/gui/editors/segment/MarkerEditor.cpp b/src/gui/editors/segment/MarkerEditor.cpp
new file mode 100644
index 0000000..61caaa7
--- /dev/null
+++ b/src/gui/editors/segment/MarkerEditor.cpp
@@ -0,0 +1,594 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
+
+/*
+ Rosegarden
+ A MIDI and audio sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "MarkerEditor.h"
+#include "MarkerEditorViewItem.h"
+#include <qlayout.h>
+#include <kapplication.h>
+
+#include <klocale.h>
+#include <kstddirs.h>
+#include <kstdaccel.h>
+#include <kconfig.h>
+#include "misc/Debug.h"
+#include "misc/Strings.h"
+#include "base/Composition.h"
+#include "base/Marker.h"
+#include "base/RealTime.h"
+#include "commands/edit/AddMarkerCommand.h"
+#include "commands/edit/ModifyMarkerCommand.h"
+#include "commands/edit/RemoveMarkerCommand.h"
+#include "document/MultiViewCommandHistory.h"
+#include "document/RosegardenGUIDoc.h"
+#include "document/ConfigGroups.h"
+#include "gui/dialogs/MarkerModifyDialog.h"
+#include <kaction.h>
+#include <kcommand.h>
+#include <kglobal.h>
+#include <klistview.h>
+#include <kmainwindow.h>
+#include <kstdaccel.h>
+#include <kstdaction.h>
+#include <qaccel.h>
+#include <qdialog.h>
+#include <qframe.h>
+#include <qgroupbox.h>
+#include <qiconset.h>
+#include <qlabel.h>
+#include <qlistview.h>
+#include <qptrlist.h>
+#include <qpushbutton.h>
+#include <qsizepolicy.h>
+#include <qstring.h>
+#include <qtooltip.h>
+#include <qvbox.h>
+#include <qwidget.h>
+#include <qcanvas.h>
+
+
+namespace Rosegarden
+{
+
+MarkerEditor::MarkerEditor(QWidget *parent,
+ RosegardenGUIDoc *doc):
+ KMainWindow(parent, "markereditordialog"),
+ m_doc(doc),
+ m_modified(false)
+{
+ QVBox* mainFrame = new QVBox(this);
+ setCentralWidget(mainFrame);
+
+ setCaption(i18n("Manage Markers"));
+
+ m_listView = new KListView(mainFrame);
+ m_listView->addColumn(i18n("Marker time "));
+ m_listView->addColumn(i18n("Marker text "));
+ m_listView->addColumn(i18n("Marker description "));
+
+ // Align centrally
+ for (int i = 0; i < 3; ++i)
+ m_listView->setColumnAlignment(i, Qt::AlignHCenter);
+
+ QGroupBox *posGroup = new QGroupBox(2, Horizontal,
+ i18n("Pointer position"), mainFrame);
+
+ new QLabel(i18n("Absolute time:"), posGroup);
+ m_absoluteTime = new QLabel(posGroup);
+
+ new QLabel(i18n("Real time:"), posGroup);
+ m_realTime = new QLabel(posGroup);
+
+ new QLabel(i18n("In measure:"), posGroup);
+ m_barTime = new QLabel(posGroup);
+
+ QFrame* btnBox = new QFrame(mainFrame);
+
+ btnBox->setSizePolicy(
+ QSizePolicy(QSizePolicy::Minimum, QSizePolicy::Fixed));
+
+ QHBoxLayout* layout = new QHBoxLayout(btnBox, 4, 10);
+
+ m_addButton = new QPushButton(i18n("Add"), btnBox);
+ m_deleteButton = new QPushButton(i18n("Delete"), btnBox);
+ m_deleteAllButton = new QPushButton(i18n("Delete All"), btnBox);
+
+ m_closeButton = new QPushButton(i18n("Close"), btnBox);
+
+ QToolTip::add
+ (m_addButton,
+ i18n("Add a Marker"));
+
+ QToolTip::add
+ (m_deleteButton,
+ i18n("Delete a Marker"));
+
+ QToolTip::add
+ (m_deleteAllButton,
+ i18n("Delete All Markers"));
+
+ QToolTip::add
+ (m_closeButton,
+ i18n("Close the Marker Editor"));
+
+ layout->addStretch(10);
+ layout->addWidget(m_addButton);
+ layout->addWidget(m_deleteButton);
+ layout->addWidget(m_deleteAllButton);
+ layout->addSpacing(30);
+
+ layout->addWidget(m_closeButton);
+ layout->addSpacing(5);
+
+ connect(m_addButton, SIGNAL(released()),
+ SLOT(slotAdd()));
+
+ connect(m_deleteButton, SIGNAL(released()),
+ SLOT(slotDelete()));
+
+ connect(m_closeButton, SIGNAL(released()),
+ SLOT(slotClose()));
+
+ connect(m_deleteAllButton, SIGNAL(released()),
+ SLOT(slotDeleteAll()));
+
+ setupActions();
+
+ m_doc->getCommandHistory()->attachView(actionCollection());
+ connect(m_doc->getCommandHistory(), SIGNAL(commandExecuted()),
+ this, SLOT(slotUpdate()));
+
+ connect(m_listView, SIGNAL(doubleClicked(QListViewItem *)),
+ SLOT(slotEdit(QListViewItem *)));
+
+ connect(m_listView, SIGNAL(pressed(QListViewItem *)),
+ this, SLOT(slotItemClicked(QListViewItem *)));
+
+ // Highlight all columns - enable extended selection mode
+ //
+ m_listView->setAllColumnsShowFocus(true);
+ m_listView->setSelectionMode(QListView::Extended);
+ m_listView->setItemsRenameable(true);
+
+ initDialog();
+
+ setAutoSaveSettings(MarkerEditorConfigGroup, true);
+
+ m_accelerators = new QAccel(this);
+}
+
+void
+MarkerEditor::updatePosition()
+{
+ timeT pos = m_doc->getComposition().getPosition();
+ m_absoluteTime->setText(QString("%1").arg(pos));
+
+ RealTime rT = m_doc->getComposition().getElapsedRealTime(pos);
+ long hours = rT.sec / (60 * 60);
+ long mins = rT.sec / 60;
+ long secs = rT.sec;
+ long msecs = rT.msec();
+
+ QString realTime, secsStr;
+ if (hours)
+ realTime += QString("%1h ").arg(hours);
+ if (mins)
+ realTime += QString("%1m ").arg(mins);
+ secsStr.sprintf("%ld.%03lds", secs, msecs);
+ realTime += secsStr;
+
+ // only update if we need to to try and avoid flickering
+ if (m_realTime->text() != realTime)
+ m_realTime->setText(realTime);
+
+ QString barTime =
+ QString("%1").arg(m_doc->getComposition().getBarNumber(pos) + 1);
+
+ // again only update if needed
+ if (m_barTime->text() != barTime)
+ m_barTime->setText(barTime);
+
+ /*
+ // Don't allow us to add another marker if there's already one
+ // at the current position.
+ //
+ if (m_doc->getComposition().
+ isMarkerAtPosition(m_doc->getComposition().getPosition()))
+ m_addButton->setEnabled(false);
+ else
+ m_addButton->setEnabled(true);
+ */
+}
+
+MarkerEditor::~MarkerEditor()
+{
+ RG_DEBUG << "MarkerEditor::~MarkerEditor" << endl;
+
+ m_listView->saveLayout(kapp->config(), MarkerEditorConfigGroup);
+
+ if (m_doc)
+ m_doc->getCommandHistory()->detachView(actionCollection());
+}
+
+void
+MarkerEditor::initDialog()
+{
+ RG_DEBUG << "MarkerEditor::initDialog" << endl;
+ slotUpdate();
+}
+
+void
+MarkerEditor::slotUpdate()
+{
+ RG_DEBUG << "MarkerEditor::slotUpdate" << endl;
+
+ //QPtrList<QListViewItem> selection = m_listView->selectedItems();
+
+ MarkerEditorViewItem *item;
+
+ m_listView->clear();
+
+ Composition::markercontainer markers =
+ m_doc->getComposition().getMarkers();
+
+ Composition::markerconstiterator it;
+
+ kapp->config()->setGroup(MarkerEditorConfigGroup);
+ int timeMode = kapp->config()->readNumEntry("timemode", 0);
+
+ for (it = markers.begin(); it != markers.end(); ++it) {
+ QString timeString = makeTimeString((*it)->getTime(), timeMode);
+
+ item = new
+ MarkerEditorViewItem(m_listView,
+ (*it)->getID(),
+ timeString,
+ strtoqstr((*it)->getName()),
+ strtoqstr((*it)->getDescription()));
+
+ // Set this for the MarkerEditor
+ //
+ item->setRawTime((*it)->getTime());
+
+ m_listView->insertItem(item);
+ }
+
+ if (m_listView->childCount() == 0) {
+ QListViewItem *item =
+ new MarkerEditorViewItem(m_listView, 0, i18n("<none>"));
+ ((MarkerEditorViewItem *)item)->setFake(true);
+ m_listView->insertItem(item);
+
+ m_listView->setSelectionMode(QListView::NoSelection);
+ } else {
+ m_listView->setSelectionMode(QListView::Extended);
+ }
+
+ updatePosition();
+
+}
+
+void
+MarkerEditor::slotDeleteAll()
+{
+ RG_DEBUG << "MarkerEditor::slotDeleteAll" << endl;
+ KMacroCommand *command = new KMacroCommand(i18n("Remove all markers"));
+
+ QListViewItem *item = m_listView->firstChild();
+
+ do {
+ MarkerEditorViewItem *ei =
+ dynamic_cast<MarkerEditorViewItem *>(item);
+ if (!ei || ei->isFake())
+ continue;
+
+ RemoveMarkerCommand *rc =
+ new RemoveMarkerCommand(&m_doc->getComposition(),
+ ei->getID(),
+ ei->getRawTime(),
+ qstrtostr(item->text(1)),
+ qstrtostr(item->text(2)));
+ command->addCommand(rc);
+ } while ((item = item->nextSibling()));
+
+ addCommandToHistory(command);
+}
+
+void
+MarkerEditor::slotAdd()
+{
+ RG_DEBUG << "MarkerEditor::slotAdd" << endl;
+
+ AddMarkerCommand *command =
+ new AddMarkerCommand(&m_doc->getComposition(),
+ m_doc->getComposition().getPosition(),
+ std::string("new marker"),
+ std::string("no description"));
+
+ addCommandToHistory(command);
+}
+
+void
+MarkerEditor::slotDelete()
+{
+ RG_DEBUG << "MarkerEditor::slotDelete" << endl;
+ QListViewItem *item = m_listView->currentItem();
+
+ MarkerEditorViewItem *ei =
+ dynamic_cast<MarkerEditorViewItem *>(item);
+
+ if (!ei || ei->isFake())
+ return ;
+
+ RemoveMarkerCommand *command =
+ new RemoveMarkerCommand(&m_doc->getComposition(),
+ ei->getID(),
+ ei->getRawTime(),
+ qstrtostr(item->text(1)),
+ qstrtostr(item->text(2)));
+
+ addCommandToHistory(command);
+
+}
+
+void
+MarkerEditor::slotClose()
+{
+ RG_DEBUG << "MarkerEditor::slotClose" << endl;
+
+ if (m_doc)
+ m_doc->getCommandHistory()->detachView(actionCollection());
+ m_doc = 0;
+
+ close();
+}
+
+void
+MarkerEditor::setupActions()
+{
+ KAction* close = KStdAction::close(this,
+ SLOT(slotClose()),
+ actionCollection());
+
+ m_closeButton->setText(close->text());
+ connect(m_closeButton, SIGNAL(released()), this, SLOT(slotClose()));
+
+ // some adjustments
+ new KToolBarPopupAction(i18n("Und&o"),
+ "undo",
+ KStdAccel::key(KStdAccel::Undo),
+ actionCollection(),
+ KStdAction::stdName(KStdAction::Undo));
+
+ new KToolBarPopupAction(i18n("Re&do"),
+ "redo",
+ KStdAccel::key(KStdAccel::Redo),
+ actionCollection(),
+ KStdAction::stdName(KStdAction::Redo));
+
+ QString pixmapDir = KGlobal::dirs()->findResource("appdata", "pixmaps/");
+ kapp->config()->setGroup(MarkerEditorConfigGroup);
+ int timeMode = kapp->config()->readNumEntry("timemode", 0);
+
+ KRadioAction *action;
+
+ QCanvasPixmap pixmap(pixmapDir + "/toolbar/time-musical.png");
+ QIconSet icon(pixmap);
+
+ action = new KRadioAction(i18n("&Musical Times"), icon, 0, this,
+ SLOT(slotMusicalTime()),
+ actionCollection(), "time_musical");
+ action->setExclusiveGroup("timeMode");
+ if (timeMode == 0)
+ action->setChecked(true);
+
+ pixmap.load(pixmapDir + "/toolbar/time-real.png");
+ icon = QIconSet(pixmap);
+
+ action = new KRadioAction(i18n("&Real Times"), icon, 0, this,
+ SLOT(slotRealTime()),
+ actionCollection(), "time_real");
+ action->setExclusiveGroup("timeMode");
+ if (timeMode == 1)
+ action->setChecked(true);
+
+ pixmap.load(pixmapDir + "/toolbar/time-raw.png");
+ icon = QIconSet(pixmap);
+
+ action = new KRadioAction(i18n("Ra&w Times"), icon, 0, this,
+ SLOT(slotRawTime()),
+ actionCollection(), "time_raw");
+ action->setExclusiveGroup("timeMode");
+ if (timeMode == 2)
+ action->setChecked(true);
+
+ createGUI("markereditor.rc");
+}
+
+void
+MarkerEditor::addCommandToHistory(KCommand *command)
+{
+ getCommandHistory()->addCommand(command);
+ setModified(false);
+}
+
+MultiViewCommandHistory*
+MarkerEditor::getCommandHistory()
+{
+ return m_doc->getCommandHistory();
+}
+
+void
+MarkerEditor::setModified(bool modified)
+{
+ RG_DEBUG << "MarkerEditor::setModified(" << modified << ")" << endl;
+
+ if (modified) {}
+ else {}
+
+ m_modified = modified;
+}
+
+void
+MarkerEditor::checkModified()
+{
+ RG_DEBUG << "MarkerEditor::checkModified(" << m_modified << ")"
+ << endl;
+
+}
+
+void
+MarkerEditor::slotEdit(QListViewItem *i)
+{
+ RG_DEBUG << "MarkerEditor::slotEdit" << endl;
+
+ if (m_listView->selectionMode() == QListView::NoSelection) {
+ // The marker list is empty, so we shouldn't allow editing the
+ // <none> placeholder
+ return ;
+ }
+
+ // Need to get the raw time from the ListViewItem
+ //
+ MarkerEditorViewItem *item =
+ dynamic_cast<MarkerEditorViewItem*>(i);
+
+ if (!item || item->isFake())
+ return ;
+
+ MarkerModifyDialog dialog(this,
+ &m_doc->getComposition(),
+ item->getRawTime(),
+ item->text(1),
+ item->text(2));
+
+ if (dialog.exec() == QDialog::Accepted) {
+ ModifyMarkerCommand *command =
+ new ModifyMarkerCommand(&m_doc->getComposition(),
+ item->getID(),
+ dialog.getOriginalTime(),
+ dialog.getTime(),
+ qstrtostr(dialog.getName()),
+ qstrtostr(dialog.getDescription()));
+
+ addCommandToHistory(command);
+ }
+
+
+}
+
+void
+MarkerEditor::closeEvent(QCloseEvent *e)
+{
+ emit closing();
+ KMainWindow::closeEvent(e);
+}
+
+void
+MarkerEditor::setDocument(RosegardenGUIDoc *doc)
+{
+ // reset our pointers
+ m_doc = doc;
+ m_modified = false;
+
+ slotUpdate();
+}
+
+void
+MarkerEditor::slotItemClicked(QListViewItem *item)
+{
+ RG_DEBUG << "MarkerEditor::slotItemClicked" << endl;
+ MarkerEditorViewItem *ei =
+ dynamic_cast<MarkerEditorViewItem *>(item);
+
+ if (ei && !ei->isFake()) {
+ RG_DEBUG << "MarkerEditor::slotItemClicked - "
+ << "jump to marker at " << ei->getRawTime() << endl;
+
+ emit jumpToMarker(timeT(ei->getRawTime()));
+ }
+}
+
+QString
+MarkerEditor::makeTimeString(timeT time, int timeMode)
+{
+ switch (timeMode) {
+
+ case 0: // musical time
+ {
+ int bar, beat, fraction, remainder;
+ m_doc->getComposition().getMusicalTimeForAbsoluteTime
+ (time, bar, beat, fraction, remainder);
+ ++bar;
+ return QString("%1%2%3-%4%5-%6%7-%8%9 ")
+ .arg(bar / 100)
+ .arg((bar % 100) / 10)
+ .arg(bar % 10)
+ .arg(beat / 10)
+ .arg(beat % 10)
+ .arg(fraction / 10)
+ .arg(fraction % 10)
+ .arg(remainder / 10)
+ .arg(remainder % 10);
+ }
+
+ case 1: // real time
+ {
+ RealTime rt =
+ m_doc->getComposition().getElapsedRealTime(time);
+ // return QString("%1 ").arg(rt.toString().c_str());
+ return QString("%1 ").arg(rt.toText().c_str());
+ }
+
+ default:
+ return QString("%1 ").arg(time);
+ }
+}
+
+void
+MarkerEditor::slotMusicalTime()
+{
+ kapp->config()->setGroup(MarkerEditorConfigGroup);
+ kapp->config()->writeEntry("timemode", 0);
+ slotUpdate();
+}
+
+void
+MarkerEditor::slotRealTime()
+{
+ kapp->config()->setGroup(MarkerEditorConfigGroup);
+ kapp->config()->writeEntry("timemode", 1);
+ slotUpdate();
+}
+
+void
+MarkerEditor::slotRawTime()
+{
+ kapp->config()->setGroup(MarkerEditorConfigGroup);
+ kapp->config()->writeEntry("timemode", 2);
+ slotUpdate();
+}
+
+}
+#include "MarkerEditor.moc"
diff --git a/src/gui/editors/segment/MarkerEditor.h b/src/gui/editors/segment/MarkerEditor.h
new file mode 100644
index 0000000..d3c9ac7
--- /dev/null
+++ b/src/gui/editors/segment/MarkerEditor.h
@@ -0,0 +1,124 @@
+
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
+
+/*
+ Rosegarden
+ A MIDI and audio sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_MARKEREDITOR_H_
+#define _RG_MARKEREDITOR_H_
+
+#include <kmainwindow.h>
+#include <qstring.h>
+#include "base/Event.h"
+
+
+class QWidget;
+class QPushButton;
+class QListViewItem;
+class QLabel;
+class QCloseEvent;
+class QAccel;
+class KListView;
+class KCommand;
+
+
+namespace Rosegarden
+{
+
+class RosegardenGUIDoc;
+class MultiViewCommandHistory;
+
+
+class MarkerEditor : public KMainWindow
+{
+ Q_OBJECT
+
+public:
+ MarkerEditor(QWidget *parent,
+ RosegardenGUIDoc *doc);
+ ~MarkerEditor();
+
+ void initDialog();
+
+ void addCommandToHistory(KCommand *command);
+ MultiViewCommandHistory* getCommandHistory();
+
+ void setModified(bool value);
+ void checkModified();
+
+ // reset the document
+ void setDocument(RosegardenGUIDoc *doc);
+
+ // update pointer position
+ void updatePosition();
+
+ QAccel* getAccelerators() { return m_accelerators; }
+
+public slots:
+ void slotUpdate();
+
+ void slotAdd();
+ void slotDelete();
+ void slotDeleteAll();
+ void slotClose();
+ void slotEdit(QListViewItem *);
+ void slotItemClicked(QListViewItem *);
+
+ void slotMusicalTime();
+ void slotRealTime();
+ void slotRawTime();
+
+signals:
+ void closing();
+ void jumpToMarker(timeT);
+
+protected:
+ virtual void closeEvent(QCloseEvent *);
+
+ void setupActions();
+ QString makeTimeString(timeT time, int timeMode);
+
+ //--------------- Data members ---------------------------------
+ RosegardenGUIDoc *m_doc;
+
+ QLabel *m_absoluteTime;
+ QLabel *m_realTime;
+ QLabel *m_barTime;
+
+ QPushButton *m_closeButton;
+
+
+ QPushButton *m_addButton;
+ QPushButton *m_deleteButton;
+ QPushButton *m_deleteAllButton;
+
+ KListView *m_listView;
+
+ bool m_modified;
+
+ QAccel *m_accelerators;
+};
+
+
+}
+
+#endif
diff --git a/src/gui/editors/segment/MarkerEditorViewItem.cpp b/src/gui/editors/segment/MarkerEditorViewItem.cpp
new file mode 100644
index 0000000..9ff2bda
--- /dev/null
+++ b/src/gui/editors/segment/MarkerEditorViewItem.cpp
@@ -0,0 +1,51 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
+
+/*
+ Rosegarden
+ A MIDI and audio sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#include "MarkerEditorViewItem.h"
+
+namespace Rosegarden {
+
+int
+MarkerEditorViewItem::compare(QListViewItem * i, int col, bool ascending) const
+{
+ MarkerEditorViewItem *ei =
+ dynamic_cast<MarkerEditorViewItem *>(i);
+
+ if (!ei) return KListViewItem::compare(i, col, ascending);
+
+ // Raw time sorting on time column
+ //
+ if (col == 0) {
+
+ if (m_rawTime < ei->getRawTime()) return -1;
+ else if (ei->getRawTime() < m_rawTime) return 1;
+ else return 0;
+
+ } else {
+ return KListViewItem::compare(i, col, ascending);
+ }
+}
+
+}
+
diff --git a/src/gui/editors/segment/MarkerEditorViewItem.h b/src/gui/editors/segment/MarkerEditorViewItem.h
new file mode 100644
index 0000000..010d227
--- /dev/null
+++ b/src/gui/editors/segment/MarkerEditorViewItem.h
@@ -0,0 +1,70 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
+
+/*
+ Rosegarden
+ A MIDI and audio sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_MARKEREDITORVIEWITEM_H_
+#define _RG_MARKEREDITORVIEWITEM_H_
+
+#include <klistview.h>
+
+#include "base/Event.h"
+
+namespace Rosegarden {
+
+
+class MarkerEditorViewItem : public KListViewItem
+{
+public:
+ MarkerEditorViewItem(QListView * parent, int id,
+ QString label1,
+ QString label2 = QString::null,
+ QString label3 = QString::null,
+ QString label4 = QString::null,
+ QString label5 = QString::null,
+ QString label6 = QString::null,
+ QString label7 = QString::null,
+ QString label8 = QString::null):
+ KListViewItem(parent, label1, label2, label3, label4,
+ label5, label6, label7, label8),
+ m_rawTime(0), m_fake(false), m_id(id) { ; }
+
+ virtual int compare(QListViewItem * i, int col, bool ascending) const;
+
+ void setRawTime(Rosegarden::timeT rawTime) { m_rawTime = rawTime; }
+ Rosegarden::timeT getRawTime() const { return m_rawTime; }
+
+ void setFake(bool fake) { m_fake = true; }
+ bool isFake() const { return m_fake; }
+
+ int getID() const { return m_id; }
+
+protected:
+ Rosegarden::timeT m_rawTime;
+ bool m_fake;
+ int m_id;
+};
+
+
+}
+
+#endif
diff --git a/src/gui/editors/segment/PlayList.cpp b/src/gui/editors/segment/PlayList.cpp
new file mode 100644
index 0000000..bfc795c
--- /dev/null
+++ b/src/gui/editors/segment/PlayList.cpp
@@ -0,0 +1,254 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
+
+/*
+ Rosegarden
+ A MIDI and audio sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "PlayList.h"
+#include "PlayListView.h"
+#include "PlayListViewItem.h"
+#include "document/ConfigGroups.h"
+#include <qlayout.h>
+
+#include <klocale.h>
+#include <kconfig.h>
+#include <kfiledialog.h>
+#include <kglobal.h>
+#include <kurl.h>
+#include <qframe.h>
+#include <qpushbutton.h>
+#include <qstring.h>
+#include <qstringlist.h>
+#include <qstrlist.h>
+#include <qvbox.h>
+#include <qwidget.h>
+#include <qdragobject.h>
+
+
+namespace Rosegarden
+{
+
+PlayList::PlayList(QWidget *parent, const char *name)
+ : QVBox(parent, name),
+ m_listView(new PlayListView(this)),
+ m_buttonBar(new QFrame(this)),
+ m_barLayout(new QHBoxLayout(m_buttonBar)),
+ m_playButton(0),
+ m_moveUpButton(0),
+ m_moveDownButton(0),
+ m_deleteButton(0),
+ m_clearButton(0)
+{
+ m_openButton = new QPushButton(m_buttonBar);
+ m_playButton = new QPushButton(m_buttonBar);
+ m_moveUpButton = new QPushButton(m_buttonBar);
+ m_moveDownButton = new QPushButton(m_buttonBar);
+ m_deleteButton = new QPushButton(m_buttonBar);
+ m_clearButton = new QPushButton(m_buttonBar);
+ m_barLayout->addWidget(m_openButton);
+ m_barLayout->addWidget(m_playButton);
+ m_barLayout->addWidget(m_moveUpButton);
+ m_barLayout->addWidget(m_moveDownButton);
+ m_barLayout->addWidget(m_deleteButton);
+ m_barLayout->addWidget(m_clearButton);
+ m_barLayout->addStretch();
+
+
+ m_openButton ->setText(i18n("Add..."));
+ m_playButton ->setText(i18n("Play"));
+ m_moveUpButton ->setText(i18n("Move Up"));
+ m_moveDownButton->setText(i18n("Move Down"));
+ m_deleteButton ->setText(i18n("Delete"));
+ m_clearButton ->setText(i18n("Clear"));
+
+ connect(m_openButton, SIGNAL(clicked()),
+ SLOT(slotOpenFiles()));
+
+ connect(m_playButton, SIGNAL(clicked()),
+ SLOT(slotPlay()));
+
+ connect(m_deleteButton, SIGNAL(clicked()),
+ SLOT(slotDeleteCurrent()));
+
+ connect(m_clearButton, SIGNAL(clicked()),
+ SLOT(slotClear()));
+
+ connect(m_moveUpButton, SIGNAL(clicked()),
+ SLOT(slotMoveUp()));
+
+ connect(m_moveDownButton, SIGNAL(clicked()),
+ SLOT(slotMoveDown()));
+
+ connect(m_listView, SIGNAL(currentChanged(QListViewItem*)),
+ SLOT(slotCurrentItemChanged(QListViewItem*)));
+
+ connect(m_listView, SIGNAL(dropped(QDropEvent*, QListViewItem*)),
+ SLOT(slotDropped(QDropEvent*, QListViewItem*)));
+
+ restore();
+
+ enableButtons(0);
+
+}
+
+PlayList::~PlayList()
+{
+ save();
+}
+
+void PlayList::slotOpenFiles()
+{
+ KURL::List kurlList =
+ KFileDialog::getOpenURLs(":ROSEGARDEN",
+ "audio/x-rosegarden audio/x-midi audio/x-rosegarden21",
+ this,
+ i18n("Select one or more Rosegarden files"));
+
+ KURL::List::iterator it;
+
+ for (it = kurlList.begin(); it != kurlList.end(); ++it) {
+ new PlayListViewItem(m_listView, *it);
+ }
+
+ enableButtons(m_listView->currentItem());
+}
+
+void
+PlayList::slotDropped(QDropEvent *event, QListViewItem* after)
+{
+ QStrList uri;
+
+ // see if we can decode a URI.. if not, just ignore it
+ if (QUriDrag::decode(event, uri)) {
+
+ // okay, we have a URI.. process it
+ // weed out non-rg files
+ //
+ for (QString url = uri.first(); url; url = uri.next()) {
+ if (url.right(3).lower() == ".rg")
+ new PlayListViewItem(m_listView, after, KURL(url));
+
+ }
+ }
+
+ enableButtons(m_listView->currentItem());
+}
+
+void PlayList::slotPlay()
+{
+ PlayListViewItem *currentItem = dynamic_cast<PlayListViewItem*>(m_listView->currentItem());
+
+ if (currentItem)
+ emit play(currentItem->getURL().url());
+}
+
+void PlayList::slotMoveUp()
+{
+ QListViewItem *currentItem = m_listView->currentItem();
+ QListViewItem *previousItem = m_listView->previousSibling(currentItem);
+
+ if (previousItem)
+ previousItem->moveItem(currentItem);
+
+ enableButtons(currentItem);
+}
+
+void PlayList::slotMoveDown()
+{
+ QListViewItem *currentItem = m_listView->currentItem();
+ QListViewItem *nextItem = currentItem->nextSibling();
+
+ if (nextItem)
+ currentItem->moveItem(nextItem);
+
+ enableButtons(currentItem);
+}
+
+void PlayList::slotClear()
+{
+ m_listView->clear();
+ enableButtons(0);
+}
+
+void PlayList::slotDeleteCurrent()
+{
+ QListViewItem* currentItem = m_listView->currentItem();
+ if (currentItem)
+ delete currentItem;
+}
+
+void PlayList::slotCurrentItemChanged(QListViewItem* currentItem)
+{
+ enableButtons(currentItem);
+}
+
+void PlayList::enableButtons(QListViewItem* currentItem)
+{
+ bool enable = (currentItem != 0);
+
+ m_playButton->setEnabled(enable);
+ m_deleteButton->setEnabled(enable);
+
+ if (currentItem) {
+ m_moveUpButton->setEnabled(currentItem != m_listView->firstChild());
+ m_moveDownButton->setEnabled(currentItem != m_listView->lastItem());
+ } else {
+ m_moveUpButton->setEnabled(false);
+ m_moveDownButton->setEnabled(false);
+ }
+
+ m_clearButton->setEnabled(m_listView->childCount() > 0);
+}
+
+void PlayList::save()
+{
+ QStringList urlList;
+ PlayListViewItem* item = dynamic_cast<PlayListViewItem*>(getListView()->firstChild());
+
+ while (item) {
+ urlList << item->getURL().url();
+ item = dynamic_cast<PlayListViewItem*>(item->nextSibling());
+ }
+
+ KConfig *kc = KGlobal::config();
+ KConfigGroupSaver cs(kc, PlayListConfigGroup);
+ kc->writeEntry("Playlist Files", urlList);
+
+ getListView()->saveLayout(kc, PlayListConfigGroup);
+}
+
+void PlayList::restore()
+{
+ KConfig *kc = KGlobal::config();
+ getListView()->restoreLayout(kc, PlayListConfigGroup);
+
+ KConfigGroupSaver cs(kc, PlayListConfigGroup);
+ QStringList urlList = kc->readListEntry("Playlist Files");
+
+ for (QStringList::Iterator it = urlList.begin();
+ it != urlList.end(); ++it) {
+ new PlayListViewItem(getListView(), KURL(*it));
+ }
+}
+
+}
+#include "PlayList.moc"
diff --git a/src/gui/editors/segment/PlayList.h b/src/gui/editors/segment/PlayList.h
new file mode 100644
index 0000000..8e40c8c
--- /dev/null
+++ b/src/gui/editors/segment/PlayList.h
@@ -0,0 +1,93 @@
+
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
+
+/*
+ Rosegarden
+ A MIDI and audio sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_PLAYLIST_H_
+#define _RG_PLAYLIST_H_
+
+#include <qvbox.h>
+
+
+class QWidget;
+class QPushButton;
+class QListViewItem;
+class QHBoxLayout;
+class QFrame;
+class QDropEvent;
+
+
+namespace Rosegarden
+{
+
+class PlayListView;
+
+
+class PlayList : public QVBox
+{
+ Q_OBJECT
+
+public:
+ PlayList(QWidget *parent = 0, const char *name = 0);
+ ~PlayList();
+
+ PlayListView* getListView() { return m_listView; }
+
+ void enableButtons(QListViewItem*);
+
+
+signals:
+ void play(QString);
+
+protected slots:
+ void slotOpenFiles();
+ void slotPlay();
+ void slotMoveUp();
+ void slotMoveDown();
+ void slotDeleteCurrent();
+ void slotClear();
+ void slotCurrentItemChanged(QListViewItem*);
+ void slotDropped(QDropEvent*, QListViewItem*);
+
+protected:
+ void save();
+ void restore();
+
+ //--------------- Data members ---------------------------------
+ PlayListView* m_listView;
+ QFrame* m_buttonBar;
+ QHBoxLayout* m_barLayout;
+
+ QPushButton* m_openButton;
+ QPushButton* m_playButton;
+ QPushButton* m_moveUpButton;
+ QPushButton* m_moveDownButton;
+ QPushButton* m_deleteButton;
+ QPushButton* m_clearButton;
+};
+
+
+
+}
+
+#endif
diff --git a/src/gui/editors/segment/PlayListDialog.cpp b/src/gui/editors/segment/PlayListDialog.cpp
new file mode 100644
index 0000000..7aa03a5
--- /dev/null
+++ b/src/gui/editors/segment/PlayListDialog.cpp
@@ -0,0 +1,76 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
+
+/*
+ Rosegarden
+ A MIDI and audio sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "PlayListDialog.h"
+
+#include "document/ConfigGroups.h"
+#include "PlayList.h"
+#include <kdialogbase.h>
+#include <qstring.h>
+#include <qwidget.h>
+
+
+namespace Rosegarden
+{
+
+PlayListDialog::PlayListDialog(QString caption,
+ QWidget* parent, const char* name)
+ : KDialogBase(parent, name, false, caption,
+ KDialogBase::Close, // standard buttons
+ KDialogBase::Close, // default button
+ true),
+ m_playList(new PlayList(this))
+{
+ setWFlags(WDestructiveClose);
+ setMainWidget(m_playList);
+ restore();
+}
+
+void PlayListDialog::save()
+{
+ saveDialogSize(PlayListConfigGroup);
+}
+
+void PlayListDialog::restore()
+{
+ setInitialSize(configDialogSize(PlayListConfigGroup));
+}
+
+void PlayListDialog::closeEvent(QCloseEvent *e)
+{
+ save();
+ emit closing();
+ KDialogBase::closeEvent(e);
+}
+
+void PlayListDialog::slotClose()
+{
+ save();
+ emit closing();
+ KDialogBase::slotClose();
+}
+
+}
+#include "PlayListDialog.moc"
diff --git a/src/gui/editors/segment/PlayListDialog.h b/src/gui/editors/segment/PlayListDialog.h
new file mode 100644
index 0000000..51db8ca
--- /dev/null
+++ b/src/gui/editors/segment/PlayListDialog.h
@@ -0,0 +1,71 @@
+
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
+
+/*
+ Rosegarden
+ A MIDI and audio sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_PLAYLISTDIALOG_H_
+#define _RG_PLAYLISTDIALOG_H_
+
+#include <kdialogbase.h>
+#include <qstring.h>
+
+
+class QWidget;
+class QCloseEvent;
+
+
+namespace Rosegarden
+{
+
+class PlayList;
+
+
+class PlayListDialog : public KDialogBase
+{
+ Q_OBJECT
+
+public:
+ PlayListDialog(QString caption, QWidget* parent = 0, const char* name = 0);
+
+ PlayList* getPlayList() { return m_playList; }
+
+public slots:
+ void slotClose();
+
+signals:
+ void closing();
+
+protected:
+ virtual void closeEvent(QCloseEvent *e);
+
+ void save();
+ void restore();
+
+ //--------------- Data members ---------------------------------
+ PlayList* m_playList;
+};
+
+
+}
+
+#endif
diff --git a/src/gui/editors/segment/PlayListView.cpp b/src/gui/editors/segment/PlayListView.cpp
new file mode 100644
index 0000000..8c17076
--- /dev/null
+++ b/src/gui/editors/segment/PlayListView.cpp
@@ -0,0 +1,66 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
+
+/*
+ Rosegarden
+ A MIDI and audio sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#include "PlayListView.h"
+
+#include <klocale.h>
+#include <qdragobject.h>
+
+namespace Rosegarden {
+
+PlayListView::PlayListView(QWidget *parent, const char *name)
+ : KListView(parent, name)
+{
+ addColumn(i18n("Title"));
+ addColumn(i18n("File name"));
+
+ setDragEnabled(true);
+ setAcceptDrops(true);
+ setDropVisualizer(true);
+
+ setShowToolTips(true);
+ setShowSortIndicator(true);
+ setAllColumnsShowFocus(true);
+ setItemsMovable(true);
+ setSorting(-1);
+}
+
+bool PlayListView::acceptDrag(QDropEvent* e) const
+{
+ return QUriDrag::canDecode(e) || KListView::acceptDrag(e);
+}
+
+
+QListViewItem* PlayListView::previousSibling(QListViewItem* item)
+{
+ QListViewItem* prevSib = firstChild();
+
+ while(prevSib && prevSib->nextSibling() != item)
+ prevSib = prevSib->nextSibling();
+
+ return prevSib;
+}
+
+}
+
diff --git a/src/gui/editors/segment/PlayListView.h b/src/gui/editors/segment/PlayListView.h
new file mode 100644
index 0000000..a18b8e8
--- /dev/null
+++ b/src/gui/editors/segment/PlayListView.h
@@ -0,0 +1,52 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
+
+/*
+ Rosegarden
+ A MIDI and audio sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_PLAYLISTVIEW_H_
+#define _RG_PLAYLISTVIEW_H_
+
+#include <klistview.h>
+
+namespace Rosegarden {
+
+class PlayListView : public KListView
+{
+public:
+ PlayListView(QWidget *parent=0, const char *name=0);
+
+ QListViewItem* previousSibling(QListViewItem*);
+
+protected:
+// virtual void dragEnterEvent(QDragEnterEvent *event);
+// virtual void dropEvent(QDropEvent*);
+
+// virtual void dragEnterEvent(QDragEnterEvent*);
+ virtual bool acceptDrag(QDropEvent*) const;
+
+
+};
+
+}
+
+#endif
+
diff --git a/src/gui/editors/segment/PlayListViewItem.cpp b/src/gui/editors/segment/PlayListViewItem.cpp
new file mode 100644
index 0000000..df04a2e
--- /dev/null
+++ b/src/gui/editors/segment/PlayListViewItem.cpp
@@ -0,0 +1,42 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
+
+/*
+ Rosegarden
+ A MIDI and audio sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#include "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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_PLAYLISTVIEWITEM_H_
+#define _RG_PLAYLISTVIEWITEM_H_
+
+#include <klistview.h>
+#include <kurl.h>
+
+namespace Rosegarden {
+
+class PlayListViewItem : public KListViewItem
+{
+public:
+ PlayListViewItem(KListView* parent, KURL);
+ PlayListViewItem(KListView* parent, QListViewItem*, KURL);
+
+ const KURL& getURL() { return m_kurl; }
+
+protected:
+ KURL m_kurl;
+};
+
+}
+
+#endif
diff --git a/src/gui/editors/segment/TrackButtons.cpp b/src/gui/editors/segment/TrackButtons.cpp
new file mode 100644
index 0000000..5cf9908
--- /dev/null
+++ b/src/gui/editors/segment/TrackButtons.cpp
@@ -0,0 +1,1149 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
+
+/*
+ Rosegarden
+ A MIDI and audio sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "TrackButtons.h"
+#include <qlayout.h>
+
+#include <klocale.h>
+#include <kstddirs.h>
+#include "misc/Debug.h"
+#include "misc/Strings.h"
+#include "base/AudioPluginInstance.h"
+#include "base/Composition.h"
+#include "base/Device.h"
+#include "base/Instrument.h"
+#include "base/MidiProgram.h"
+#include "base/Studio.h"
+#include "base/Track.h"
+#include "commands/segment/RenameTrackCommand.h"
+#include "document/RosegardenGUIDoc.h"
+#include "document/MultiViewCommandHistory.h"
+#include "gui/application/RosegardenGUIApp.h"
+#include "gui/general/GUIPalette.h"
+#include "gui/kdeext/KLedButton.h"
+#include "sound/AudioFileManager.h"
+#include "sound/PluginIdentifier.h"
+#include "TrackLabel.h"
+#include "TrackVUMeter.h"
+#include <kglobal.h>
+#include <kled.h>
+#include <kmessagebox.h>
+#include <qcursor.h>
+#include <qframe.h>
+#include <qiconset.h>
+#include <qlabel.h>
+#include <qobject.h>
+#include <qpixmap.h>
+#include <qpopupmenu.h>
+#include <qsignalmapper.h>
+#include <qstring.h>
+#include <qtimer.h>
+#include <qwidget.h>
+#include <qwidgetstack.h>
+#include <qtooltip.h>
+
+namespace Rosegarden
+{
+
+TrackButtons::TrackButtons(RosegardenGUIDoc* doc,
+ unsigned int trackCellHeight,
+ unsigned int trackLabelWidth,
+ bool showTrackLabels,
+ int overallHeight,
+ QWidget* parent,
+ const char* name,
+ WFlags f)
+ : QFrame(parent, name, f),
+ m_doc(doc),
+ m_layout(new QVBoxLayout(this)),
+ m_recordSigMapper(new QSignalMapper(this)),
+ m_muteSigMapper(new QSignalMapper(this)),
+ m_clickedSigMapper(new QSignalMapper(this)),
+ m_instListSigMapper(new QSignalMapper(this)),
+ m_tracks(doc->getComposition().getNbTracks()),
+ m_offset(4),
+ m_cellSize(trackCellHeight),
+ m_borderGap(1),
+ m_trackLabelWidth(trackLabelWidth),
+ m_popupItem(0),
+ m_lastSelected( -1)
+{
+ setFrameStyle(Plain);
+
+ // when we create the widget, what are we looking at?
+ if (showTrackLabels)
+ m_trackInstrumentLabels = TrackLabel::ShowTrack;
+ else
+ m_trackInstrumentLabels = TrackLabel::ShowInstrument;
+
+ // Set the spacing between vertical elements
+ //
+ m_layout->setSpacing(m_borderGap);
+
+ // Now draw the buttons and labels and meters
+ //
+ makeButtons();
+
+ m_layout->addStretch(20);
+
+ connect(m_recordSigMapper, SIGNAL(mapped(int)),
+ this, SLOT(slotToggleRecordTrack(int)));
+
+ connect(m_muteSigMapper, SIGNAL(mapped(int)),
+ this, SLOT(slotToggleMutedTrack(int)));
+
+ // connect signal mappers
+ connect(m_instListSigMapper, SIGNAL(mapped(int)),
+ this, SLOT(slotInstrumentSelection(int)));
+
+ connect(m_clickedSigMapper, SIGNAL(mapped(int)),
+ this, SIGNAL(trackSelected(int)));
+
+ // // Populate instrument popup menu just once at start-up
+ // //
+ // populateInstrumentPopup();
+
+ // We have to force the height for the moment
+ //
+ setMinimumHeight(overallHeight);
+
+}
+
+TrackButtons::~TrackButtons()
+{}
+
+void
+TrackButtons::makeButtons()
+{
+ if (!m_doc)
+ return ;
+
+ // Create a horizontal box for each track
+ // plus the two buttons
+ //
+ unsigned int nbTracks = m_doc->getComposition().getNbTracks();
+
+ for (unsigned int i = 0; i < nbTracks; ++i) {
+ Track *track = m_doc->getComposition().getTrackByPosition(i);
+
+ if (track) {
+ QFrame *trackHBox = makeButton(track->getId());
+
+ if (trackHBox) {
+ m_layout->addWidget(trackHBox);
+ m_trackHBoxes.push_back(trackHBox);
+ }
+ }
+ }
+
+ populateButtons();
+}
+
+void TrackButtons::setButtonMapping(QObject* obj, TrackId trackId)
+{
+ m_clickedSigMapper->setMapping(obj, trackId);
+ m_instListSigMapper->setMapping(obj, trackId);
+}
+
+void
+TrackButtons::populateButtons()
+{
+ Instrument *ins = 0;
+ Track *track;
+
+ for (unsigned int i = 0; i < m_trackLabels.size(); ++i) {
+ track = m_doc->getComposition().getTrackByPosition(i);
+
+ if (track) {
+ ins = m_doc->getStudio().getInstrumentById(track->getInstrument());
+
+ // Set mute button from track
+ //
+ if (track->isMuted())
+ m_muteLeds[i]->off();
+ else
+ m_muteLeds[i]->on();
+
+ // Set record button from track
+ //
+ bool recording =
+ m_doc->getComposition().isTrackRecording(track->getId());
+ setRecordTrack(track->getPosition(), recording);
+
+ // reset track tokens
+ m_trackLabels[i]->setId(track->getId());
+ setButtonMapping(m_trackLabels[i], track->getId());
+ m_trackLabels[i]->setPosition(i);
+ }
+
+ if (ins) {
+ m_trackLabels[i]->getInstrumentLabel()->setText
+ (strtoqstr(ins->getPresentationName()));
+ if (ins->sendsProgramChange()) {
+ m_trackLabels[i]->setAlternativeLabel(strtoqstr(ins->getProgramName()));
+ }
+
+ } else {
+ m_trackLabels[i]->getInstrumentLabel()->setText(i18n("<no instrument>"));
+ }
+
+ m_trackLabels[i]->update();
+ }
+
+}
+
+std::vector<int>
+TrackButtons::mutedTracks()
+{
+ std::vector<int> mutedTracks;
+
+ for (TrackId i = 0; i < m_tracks; i++) {
+ if (m_muteLeds[i]->state() == KLed::Off)
+ mutedTracks.push_back(i);
+ }
+
+ return mutedTracks;
+}
+
+void
+TrackButtons::slotToggleMutedTrack(int mutedTrackPos)
+{
+ RG_DEBUG << "TrackButtons::slotToggleMutedTrack(" << mutedTrackPos << ")\n";
+
+ if (mutedTrackPos < 0 || mutedTrackPos > (int)m_tracks )
+ return ;
+
+ Track *track =
+ m_doc->getComposition().getTrackByPosition(mutedTrackPos);
+
+ emit muteButton(track->getId(), !track->isMuted()); // will set the value
+}
+
+void
+TrackButtons::removeButtons(unsigned int position)
+{
+ RG_DEBUG << "TrackButtons::removeButtons - "
+ << "deleting track button at position "
+ << position << endl;
+
+ if (position >= m_trackHBoxes.size()) {
+ RG_DEBUG << "%%%%%%%%% BIG PROBLEM : TrackButtons::removeButtons() was passed a non-existing index\n";
+ return ;
+ }
+
+ std::vector<TrackLabel*>::iterator tit = m_trackLabels.begin();
+ tit += position;
+ m_trackLabels.erase(tit);
+
+ std::vector<TrackVUMeter*>::iterator vit = m_trackMeters.begin();
+ vit += position;
+ m_trackMeters.erase(vit);
+
+ std::vector<KLedButton*>::iterator mit = m_muteLeds.begin();
+ mit += position;
+ m_muteLeds.erase(mit);
+
+ mit = m_recordLeds.begin();
+ mit += position;
+ m_recordLeds.erase(mit);
+
+ delete m_trackHBoxes[position]; // deletes all child widgets (button, led, label...)
+
+ std::vector<QFrame*>::iterator it = m_trackHBoxes.begin();
+ it += position;
+ m_trackHBoxes.erase(it);
+
+}
+
+void
+TrackButtons::slotUpdateTracks()
+{
+ Composition &comp = m_doc->getComposition();
+ unsigned int newNbTracks = comp.getNbTracks();
+ Track *track = 0;
+
+ std::cerr << "TrackButtons::slotUpdateTracks" << std::endl;
+
+ if (newNbTracks < m_tracks) {
+ for (unsigned int i = m_tracks; i > newNbTracks; --i)
+ removeButtons(i - 1);
+ } else if (newNbTracks > m_tracks) {
+ for (unsigned int i = m_tracks; i < newNbTracks; ++i) {
+ track = m_doc->getComposition().getTrackByPosition(i);
+ if (track) {
+ QFrame *trackHBox = makeButton(track->getId());
+
+ if (trackHBox) {
+ trackHBox->show();
+ m_layout->insertWidget(i, trackHBox);
+ m_trackHBoxes.push_back(trackHBox);
+ }
+ } else
+ RG_DEBUG << "TrackButtons::slotUpdateTracks - can't find TrackId for position " << i << endl;
+ }
+ }
+
+ // Set height
+ //
+ for (unsigned int i = 0; i < m_trackHBoxes.size(); ++i) {
+
+ track = comp.getTrackByPosition(i);
+
+ if (track) {
+
+ int multiple = m_doc->getComposition()
+ .getMaxContemporaneousSegmentsOnTrack(track->getId());
+ if (multiple == 0) multiple = 1;
+
+ // nasty dupe from makeButton
+
+ int buttonGap = 8;
+ int vuWidth = 20;
+ int vuSpacing = 2;
+
+ int labelWidth = m_trackLabelWidth -
+ ((m_cellSize - buttonGap) * 2 +
+ vuSpacing * 2 + vuWidth);
+
+ m_trackHBoxes[i]->setMinimumSize
+ (labelWidth, m_cellSize * multiple - m_borderGap);
+
+ m_trackHBoxes[i]->setFixedHeight
+ (m_cellSize * multiple - m_borderGap);
+ }
+ }
+
+ // Renumber all the labels
+ //
+ for (unsigned int i = 0; i < m_trackLabels.size(); ++i) {
+ track = comp.getTrackByPosition(i);
+
+ if (track) {
+ m_trackLabels[i]->setId(track->getId());
+
+ QLabel *trackLabel = m_trackLabels[i]->getTrackLabel();
+
+ if (track->getLabel() == std::string("")) {
+ Instrument *ins =
+ m_doc->getStudio().getInstrumentById(track->getInstrument());
+ if (ins && ins->getType() == Instrument::Audio) {
+ trackLabel->setText(i18n("<untitled audio>"));
+ } else {
+ trackLabel->setText(i18n("<untitled>"));
+ }
+ } else {
+ trackLabel->setText(strtoqstr(track->getLabel()));
+ }
+
+ // RG_DEBUG << "TrackButtons::slotUpdateTracks - set button mapping at pos "
+ // << i << " to track id " << track->getId() << endl;
+ setButtonMapping(m_trackLabels[i], track->getId());
+ }
+ }
+ m_tracks = newNbTracks;
+
+ // Set record status and colour
+
+ for (unsigned int i = 0; i < m_trackLabels.size(); ++i) {
+
+ track = comp.getTrackByPosition(i);
+
+ if (track) {
+
+ setRecordTrack(i, comp.isTrackRecording(track->getId()));
+
+ Instrument *ins =
+ m_doc->getStudio().getInstrumentById(track->getInstrument());
+
+ if (ins &&
+ ins->getType() == Instrument::Audio) {
+ m_recordLeds[i]->setColor
+ (GUIPalette::getColour
+ (GUIPalette::RecordAudioTrackLED));
+ } else {
+ m_recordLeds[i]->setColor
+ (GUIPalette::getColour
+ (GUIPalette::RecordMIDITrackLED));
+ }
+ }
+ }
+
+ // repopulate the buttons
+ populateButtons();
+}
+
+void
+TrackButtons::slotToggleRecordTrack(int position)
+{
+ Composition &comp = m_doc->getComposition();
+ Track *track = comp.getTrackByPosition(position);
+
+ bool state = !comp.isTrackRecording(track->getId());
+
+ Instrument *instrument = m_doc->getStudio().getInstrumentById
+ (track->getInstrument());
+
+ bool audio = (instrument &&
+ instrument->getType() == Instrument::Audio);
+
+ if (audio && state) {
+ try {
+ m_doc->getAudioFileManager().testAudioPath();
+ } catch (AudioFileManager::BadAudioPathException e) {
+ if (KMessageBox::warningContinueCancel
+ (this,
+ i18n("The audio file path does not exist or is not writable.\nPlease set the audio file path to a valid directory in Document Properties before recording audio.\nWould you like to set it now?"),
+ i18n("Warning"),
+ i18n("Set audio file path")) == KMessageBox::Continue) {
+ RosegardenGUIApp::self()->slotOpenAudioPathSettings();
+ }
+ }
+ }
+
+ // can have any number of audio instruments armed, but only one
+ // track armed per instrument.
+
+ // Need to copy this container, as we're implicitly modifying it
+ // through calls to comp.setTrackRecording
+
+ Composition::recordtrackcontainer oldRecordTracks =
+ comp.getRecordTracks();
+
+ for (Composition::recordtrackcontainer::const_iterator i =
+ oldRecordTracks.begin();
+ i != oldRecordTracks.end(); ++i) {
+
+ if (!comp.isTrackRecording(*i)) {
+ // We've already reset this one
+ continue;
+ }
+
+ Track *otherTrack = comp.getTrackById(*i);
+
+ if (otherTrack &&
+ otherTrack != track) {
+
+ /* Obsolete code: audio, MIDI and plugin tracks behave the same now.
+ plcl, 06/2006 - Multitrack MIDI recording
+
+ bool unselect;
+
+ if (audio) {
+ unselect = (otherTrack->getInstrument() == track->getInstrument());
+ } else {
+ // our track is not an audio track, check that the
+ // other isn't either
+ Instrument *otherInstrument =
+ m_doc->getStudio().getInstrumentById(otherTrack->getInstrument());
+ bool otherAudio = (otherInstrument &&
+ otherInstrument->getType() ==
+ Instrument::Audio);
+
+ unselect = !otherAudio;
+ }
+
+ if (unselect) { */
+
+ if (otherTrack->getInstrument() == track->getInstrument()) {
+ // found another record track of the same type (and
+ // with the same instrument, if audio): unselect that
+
+ //!!! should we tell the user, particularly for the
+ //audio case? might seem odd otherwise
+
+ int otherPos = otherTrack->getPosition();
+ setRecordTrack(otherPos, false);
+ }
+ }
+ }
+
+ setRecordTrack(position, state);
+
+ emit recordButton(track->getId(), state);
+}
+
+void
+TrackButtons::setRecordTrack(int position, bool state)
+{
+ setRecordButton(position, state);
+ m_doc->getComposition().setTrackRecording
+ (m_trackLabels[position]->getId(), state);
+}
+
+void
+TrackButtons::setRecordButton(int position, bool state)
+{
+ if (position < 0 || position >= (int)m_tracks)
+ return ;
+
+ KLedButton* led = m_recordLeds[position];
+
+ led->setState(state ? KLed::On : KLed::Off);
+}
+
+void
+TrackButtons::selectLabel(int position)
+{
+ if (m_lastSelected >= 0 && m_lastSelected < (int)m_trackLabels.size()) {
+ m_trackLabels[m_lastSelected]->setSelected(false);
+ }
+
+ if (position >= 0 && position < (int)m_trackLabels.size()) {
+ m_trackLabels[position]->setSelected(true);
+ m_lastSelected = position;
+ }
+}
+
+std::vector<int>
+TrackButtons::getHighlightedTracks()
+{
+ std::vector<int> retList;
+
+ for (unsigned int i = 0; i < m_trackLabels.size(); ++i) {
+ if (m_trackLabels[i]->isSelected())
+ retList.push_back(i);
+ }
+
+ return retList;
+}
+
+void
+TrackButtons::slotRenameTrack(QString newName, TrackId trackId)
+{
+ m_doc->getCommandHistory()->addCommand
+ (new RenameTrackCommand(&m_doc->getComposition(),
+ trackId,
+ qstrtostr(newName)));
+
+ changeTrackLabel(trackId, newName);
+}
+
+void
+TrackButtons::slotSetTrackMeter(float value, int position)
+{
+ //Composition &comp = m_doc->getComposition();
+ //Studio &studio = m_doc->getStudio();
+ //Track *track;
+
+ for (unsigned int i = 0; i < m_trackMeters.size(); ++i) {
+ if (i == ((unsigned int)position)) {
+ m_trackMeters[i]->setLevel(value);
+ return ;
+ }
+ }
+}
+
+void
+TrackButtons::slotSetMetersByInstrument(float value,
+ InstrumentId id)
+{
+ Composition &comp = m_doc->getComposition();
+ //Studio &studio = m_doc->getStudio();
+ Track *track;
+
+ for (unsigned int i = 0; i < m_trackMeters.size(); ++i) {
+ track = comp.getTrackByPosition(i);
+
+ if (track != 0 && track->getInstrument() == id) {
+ m_trackMeters[i]->setLevel(value);
+ }
+ }
+}
+
+void
+TrackButtons::slotInstrumentSelection(int trackId)
+{
+ RG_DEBUG << "TrackButtons::slotInstrumentSelection(" << trackId << ")\n";
+
+ Composition &comp = m_doc->getComposition();
+ Studio &studio = m_doc->getStudio();
+
+ int position = comp.getTrackById(trackId)->getPosition();
+
+ QString instrumentName = i18n("<no instrument>");
+ Track *track = comp.getTrackByPosition(position);
+
+ Instrument *instrument = 0;
+ if (track != 0) {
+ instrument = studio.getInstrumentById(track->getInstrument());
+ if (instrument)
+ instrumentName = strtoqstr(instrument->getPresentationName());
+ }
+
+ //
+ // populate this instrument widget
+ m_trackLabels[position]->getInstrumentLabel()->setText(instrumentName);
+
+ // Ensure the instrument name is shown
+ m_trackLabels[position]->showLabel(TrackLabel::ShowInstrument);
+
+ // Yes, well as we might've changed the Device name in the
+ // Device/Bank dialog then we reload the whole menu here.
+ //
+
+ QPopupMenu instrumentPopup(this);
+
+ populateInstrumentPopup(instrument, &instrumentPopup);
+
+ // Store the popup item position
+ //
+ m_popupItem = position;
+
+ instrumentPopup.exec(QCursor::pos());
+
+ // Restore the label back to what it was showing
+ m_trackLabels[position]->showLabel(m_trackInstrumentLabels);
+
+ // Do this here as well as in slotInstrumentPopupActivated, so as
+ // to restore the correct alternative label even if no other
+ // program was selected from the menu
+ if (track != 0) {
+ instrument = studio.getInstrumentById(track->getInstrument());
+ if (instrument) {
+ m_trackLabels[position]->getInstrumentLabel()->
+ setText(strtoqstr(instrument->getPresentationName()));
+ m_trackLabels[position]->clearAlternativeLabel();
+ if (instrument->sendsProgramChange()) {
+ m_trackLabels[position]->setAlternativeLabel
+ (strtoqstr(instrument->getProgramName()));
+ }
+ }
+ }
+}
+
+void
+TrackButtons::populateInstrumentPopup(Instrument *thisTrackInstr, QPopupMenu* instrumentPopup)
+{
+ static QPixmap connectedPixmap, unconnectedPixmap,
+ connectedUsedPixmap, unconnectedUsedPixmap,
+ connectedSelectedPixmap, unconnectedSelectedPixmap;
+ static bool havePixmaps = false;
+
+ if (!havePixmaps) {
+
+ QString pixmapDir =
+ KGlobal::dirs()->findResource("appdata", "pixmaps/");
+
+ connectedPixmap.load
+ (QString("%1/misc/connected.xpm").arg(pixmapDir));
+ connectedUsedPixmap.load
+ (QString("%1/misc/connected-used.xpm").arg(pixmapDir));
+ connectedSelectedPixmap.load
+ (QString("%1/misc/connected-selected.xpm").arg(pixmapDir));
+ unconnectedPixmap.load
+ (QString("%1/misc/unconnected.xpm").arg(pixmapDir));
+ unconnectedUsedPixmap.load
+ (QString("%1/misc/unconnected-used.xpm").arg(pixmapDir));
+ unconnectedSelectedPixmap.load
+ (QString("%1/misc/unconnected-selected.xpm").arg(pixmapDir));
+
+ havePixmaps = true;
+ }
+
+ Composition &comp = m_doc->getComposition();
+ Studio &studio = m_doc->getStudio();
+
+ // clear the popup
+ instrumentPopup->clear();
+
+ std::vector<QPopupMenu*> instrumentSubMenus;
+
+ // position index
+ int i = 0;
+
+ // Get the list
+ InstrumentList list = studio.getPresentationInstruments();
+ InstrumentList::iterator it;
+ int currentDevId = -1;
+ bool deviceUsedByAnyone = false;
+
+ for (it = list.begin(); it != list.end(); it++) {
+
+ if (! (*it))
+ continue; // sanity check
+
+ QString iname(strtoqstr((*it)->getPresentationName()));
+ QString pname(strtoqstr((*it)->getProgramName()));
+ Device *device = (*it)->getDevice();
+ DeviceId devId = device->getId();
+ bool connected = false;
+
+ if ((*it)->getType() == Instrument::SoftSynth) {
+ pname = "";
+ AudioPluginInstance *plugin = (*it)->getPlugin
+ (Instrument::SYNTH_PLUGIN_POSITION);
+ if (plugin) {
+ pname = strtoqstr(plugin->getProgram());
+ QString identifier = strtoqstr(plugin->getIdentifier());
+ if (identifier != "") {
+ connected = true;
+ QString type, soName, label;
+ PluginIdentifier::parseIdentifier
+ (identifier, type, soName, label);
+ if (pname == "") {
+ pname = strtoqstr(plugin->getDistinctiveConfigurationText());
+ }
+ if (pname != "") {
+ pname = QString("%1: %2").arg(label).arg(pname);
+ } else {
+ pname = label;
+ }
+ } else {
+ connected = false;
+ }
+ }
+ } else if ((*it)->getType() == Instrument::Audio) {
+ connected = true;
+ } else {
+ connected = (device->getConnection() != "");
+ }
+
+ bool instrUsedByMe = false;
+ bool instrUsedByAnyone = false;
+
+ if (thisTrackInstr && thisTrackInstr->getId() == (*it)->getId()) {
+ instrUsedByMe = true;
+ instrUsedByAnyone = true;
+ }
+
+ if (devId != (DeviceId)(currentDevId)) {
+
+ deviceUsedByAnyone = false;
+
+ if (instrUsedByMe)
+ deviceUsedByAnyone = true;
+ else {
+ for (Composition::trackcontainer::iterator tit =
+ comp.getTracks().begin();
+ tit != comp.getTracks().end(); ++tit) {
+
+ if (tit->second->getInstrument() == (*it)->getId()) {
+ instrUsedByAnyone = true;
+ deviceUsedByAnyone = true;
+ break;
+ }
+
+ Instrument *instr =
+ studio.getInstrumentById(tit->second->getInstrument());
+ if (instr && (instr->getDevice()->getId() == devId)) {
+ deviceUsedByAnyone = true;
+ }
+ }
+ }
+
+ QIconSet iconSet
+ (connected ?
+ (deviceUsedByAnyone ?
+ connectedUsedPixmap : connectedPixmap) :
+ (deviceUsedByAnyone ?
+ unconnectedUsedPixmap : unconnectedPixmap));
+
+ currentDevId = int(devId);
+
+ QPopupMenu *subMenu = new QPopupMenu(instrumentPopup);
+ QString deviceName = strtoqstr(device->getName());
+ instrumentPopup->insertItem(iconSet, deviceName, subMenu);
+ instrumentSubMenus.push_back(subMenu);
+
+ // Connect up the submenu
+ //
+ connect(subMenu, SIGNAL(activated(int)),
+ SLOT(slotInstrumentPopupActivated(int)));
+
+ } else if (!instrUsedByMe) {
+
+ for (Composition::trackcontainer::iterator tit =
+ comp.getTracks().begin();
+ tit != comp.getTracks().end(); ++tit) {
+
+ if (tit->second->getInstrument() == (*it)->getId()) {
+ instrUsedByAnyone = true;
+ break;
+ }
+ }
+ }
+
+ QIconSet iconSet
+ (connected ?
+ (instrUsedByAnyone ?
+ instrUsedByMe ?
+ connectedSelectedPixmap :
+ connectedUsedPixmap : connectedPixmap) :
+ (instrUsedByAnyone ?
+ instrUsedByMe ?
+ unconnectedSelectedPixmap :
+ unconnectedUsedPixmap : unconnectedPixmap));
+
+ if (pname != "")
+ iname += " (" + pname + ")";
+
+ instrumentSubMenus[instrumentSubMenus.size() - 1]->insertItem(iconSet, iname, i++);
+ }
+
+}
+
+void
+TrackButtons::slotInstrumentPopupActivated(int item)
+{
+ RG_DEBUG << "TrackButtons::slotInstrumentPopupActivated " << item << endl;
+
+ Composition &comp = m_doc->getComposition();
+ Studio &studio = m_doc->getStudio();
+
+ Instrument *inst = studio.getInstrumentFromList(item);
+
+ RG_DEBUG << "TrackButtons::slotInstrumentPopupActivated: instrument " << inst << endl;
+
+ if (inst != 0) {
+ Track *track = comp.getTrackByPosition(m_popupItem);
+
+ if (track != 0) {
+ track->setInstrument(inst->getId());
+
+ // select instrument
+ emit instrumentSelected((int)inst->getId());
+
+ m_trackLabels[m_popupItem]->getInstrumentLabel()->
+ setText(strtoqstr(inst->getPresentationName()));
+
+ // reset the alternative label
+ m_trackLabels[m_popupItem]->clearAlternativeLabel();
+
+ // Now see if the program is being shown for this instrument
+ // and if so reset the label
+ //
+ if (inst->sendsProgramChange())
+ m_trackLabels[m_popupItem]->setAlternativeLabel(strtoqstr(inst->getProgramName()));
+
+ if (inst->getType() == Instrument::Audio) {
+ m_recordLeds[m_popupItem]->setColor
+ (GUIPalette::getColour
+ (GUIPalette::RecordAudioTrackLED));
+ } else {
+ m_recordLeds[m_popupItem]->setColor
+ (GUIPalette::getColour
+ (GUIPalette::RecordMIDITrackLED));
+ }
+ } else
+ RG_DEBUG << "slotInstrumentPopupActivated() - can't find item!\n";
+ } else
+ RG_DEBUG << "slotInstrumentPopupActivated() - can't find item!\n";
+
+}
+
+void
+TrackButtons::changeTrackInstrumentLabels(TrackLabel::InstrumentTrackLabels label)
+{
+ // Set new label
+ m_trackInstrumentLabels = label;
+
+ // update and reconnect with new value
+ for (int i = 0; i < (int)m_tracks; i++) {
+ m_trackLabels[i]->showLabel(label);
+ }
+}
+
+void
+TrackButtons::changeInstrumentLabel(InstrumentId id, QString label)
+{
+ Composition &comp = m_doc->getComposition();
+ Track *track;
+
+ for (int i = 0; i < (int)m_tracks; i++) {
+ track = comp.getTrackByPosition(i);
+
+ if (track && track->getInstrument() == id) {
+
+ m_trackLabels[i]->setAlternativeLabel(label);
+
+ Instrument *ins = m_doc->getStudio().
+ getInstrumentById(track->getInstrument());
+
+ if (ins && ins->getType() == Instrument::Audio) {
+ m_recordLeds[i]->setColor
+ (GUIPalette::getColour
+ (GUIPalette::RecordAudioTrackLED));
+ } else {
+ m_recordLeds[i]->setColor
+ (GUIPalette::getColour
+ (GUIPalette::RecordMIDITrackLED));
+ }
+ }
+ }
+}
+
+void
+TrackButtons::changeTrackLabel(TrackId id, QString label)
+{
+ Composition &comp = m_doc->getComposition();
+ Track *track;
+
+ for (int i = 0; i < (int)m_tracks; i++) {
+ track = comp.getTrackByPosition(i);
+ if (track && track->getId() == id) {
+ if (m_trackLabels[i]->getTrackLabel()->text() != label) {
+ m_trackLabels[i]->getTrackLabel()->setText(label);
+ emit widthChanged();
+ emit nameChanged();
+ }
+ return ;
+ }
+ }
+}
+
+void
+TrackButtons::slotSynchroniseWithComposition()
+{
+ Composition &comp = m_doc->getComposition();
+ Studio &studio = m_doc->getStudio();
+ Track *track;
+
+ for (int i = 0; i < (int)m_tracks; i++) {
+ track = comp.getTrackByPosition(i);
+
+ if (track) {
+ if (track->isMuted())
+ m_muteLeds[i]->off();
+ else
+ m_muteLeds[i]->on();
+
+ Instrument *ins = studio.
+ getInstrumentById(track->getInstrument());
+
+ QString instrumentName(i18n("<no instrument>"));
+ if (ins)
+ instrumentName = strtoqstr(ins->getPresentationName());
+
+ m_trackLabels[i]->getInstrumentLabel()->setText(instrumentName);
+
+ setRecordButton(i, comp.isTrackRecording(track->getId()));
+
+ if (ins && ins->getType() == Instrument::Audio) {
+ m_recordLeds[i]->setColor
+ (GUIPalette::getColour
+ (GUIPalette::RecordAudioTrackLED));
+ } else {
+ m_recordLeds[i]->setColor
+ (GUIPalette::getColour
+ (GUIPalette::RecordMIDITrackLED));
+ }
+ }
+ }
+}
+
+void
+TrackButtons::slotLabelSelected(int position)
+{
+ Track *track =
+ m_doc->getComposition().getTrackByPosition(position);
+
+ if (track) {
+ emit trackSelected(track->getId());
+ }
+}
+
+void
+TrackButtons::setMuteButton(TrackId track, bool value)
+{
+ Track *trackObj = m_doc->getComposition().getTrackById(track);
+ if (trackObj == 0)
+ return ;
+
+ int pos = trackObj->getPosition();
+
+ RG_DEBUG << "TrackButtons::setMuteButton() trackId = "
+ << track << ", pos = " << pos << endl;
+
+ m_muteLeds[pos]->setState(value ? KLed::Off : KLed::On);
+}
+
+void
+TrackButtons::slotTrackInstrumentSelection(TrackId trackId, int item)
+{
+ RG_DEBUG << "TrackButtons::slotTrackInstrumentSelection(" << trackId << ")\n";
+
+ Composition &comp = m_doc->getComposition();
+ int position = comp.getTrackById(trackId)->getPosition();
+ m_popupItem = position;
+ slotInstrumentPopupActivated( item );
+}
+
+QFrame* TrackButtons::makeButton(Rosegarden::TrackId trackId)
+{
+ // The buttonGap sets up the sizes of the buttons
+ //
+ static const int buttonGap = 8;
+
+ QFrame *trackHBox = 0;
+
+ KLedButton *mute = 0;
+ KLedButton *record = 0;
+
+ TrackVUMeter *vuMeter = 0;
+ TrackLabel *trackLabel = 0;
+
+ int vuWidth = 20;
+ int vuSpacing = 2;
+ int multiple = m_doc->getComposition()
+ .getMaxContemporaneousSegmentsOnTrack(trackId);
+ if (multiple == 0) multiple = 1;
+ int labelWidth = m_trackLabelWidth - ( (m_cellSize - buttonGap) * 2 +
+ vuSpacing * 2 + vuWidth );
+
+ // Set the label from the Track object on the Composition
+ //
+ Rosegarden::Track *track = m_doc->getComposition().getTrackById(trackId);
+
+ if (track == 0) return 0;
+
+ // Create a horizontal box for each track
+ //
+ trackHBox = new QFrame(this);
+ QHBoxLayout *hblayout = new QHBoxLayout(trackHBox);
+
+ trackHBox->setMinimumSize(labelWidth, m_cellSize * multiple - m_borderGap);
+ trackHBox->setFixedHeight(m_cellSize * multiple - m_borderGap);
+
+ // Try a style for the box
+ //
+ trackHBox->setFrameStyle(StyledPanel);
+ trackHBox->setFrameShape(StyledPanel);
+ trackHBox->setFrameShadow(Raised);
+
+ // Insert a little gap
+ hblayout->addSpacing(vuSpacing);
+
+ // Create a VU meter
+ vuMeter = new TrackVUMeter(trackHBox,
+ VUMeter::PeakHold,
+ vuWidth,
+ buttonGap,
+ track->getPosition());
+
+ m_trackMeters.push_back(vuMeter);
+
+ hblayout->addWidget(vuMeter);
+
+ // Create another little gap
+ hblayout->addSpacing(vuSpacing);
+
+ //
+ // 'mute' and 'record' leds
+ //
+
+ mute = new KLedButton(Rosegarden::GUIPalette::getColour
+ (Rosegarden::GUIPalette::MuteTrackLED), trackHBox);
+ QToolTip::add(mute, i18n("Mute track"));
+ hblayout->addWidget(mute);
+
+ record = new KLedButton(Rosegarden::GUIPalette::getColour
+ (Rosegarden::GUIPalette::RecordMIDITrackLED), trackHBox);
+ QToolTip::add(record, i18n("Record on this track"));
+ hblayout->addWidget(record);
+
+ record->setLook(KLed::Sunken);
+ mute->setLook(KLed::Sunken);
+ record->off();
+
+ // Connect them to their sigmappers
+ connect(record, SIGNAL(stateChanged(bool)),
+ m_recordSigMapper, SLOT(map()));
+ connect(mute, SIGNAL(stateChanged(bool)),
+ m_muteSigMapper, SLOT(map()));
+ m_recordSigMapper->setMapping(record, track->getPosition());
+ m_muteSigMapper->setMapping(mute, track->getPosition());
+
+ // Store the KLedButton
+ //
+ m_muteLeds.push_back(mute);
+ m_recordLeds.push_back(record);
+
+ //
+ // Track label
+ //
+ trackLabel = new TrackLabel(trackId, track->getPosition(), trackHBox);
+ hblayout->addWidget(trackLabel);
+ hblayout->addSpacing(vuSpacing);
+
+ if (track->getLabel() == std::string("")) {
+ Rosegarden::Instrument *ins =
+ m_doc->getStudio().getInstrumentById(track->getInstrument());
+ if (ins && ins->getType() == Rosegarden::Instrument::Audio) {
+ trackLabel->getTrackLabel()->setText(i18n("<untitled audio>"));
+ } else {
+ trackLabel->getTrackLabel()->setText(i18n("<untitled>"));
+ }
+ }
+ else
+ trackLabel->getTrackLabel()->setText(strtoqstr(track->getLabel()));
+
+ trackLabel->setFixedSize(labelWidth, m_cellSize - buttonGap);
+ trackLabel->setFixedHeight(m_cellSize - buttonGap);
+ trackLabel->setIndent(7);
+
+ connect(trackLabel, SIGNAL(renameTrack(QString, TrackId)),
+ SLOT(slotRenameTrack(QString, TrackId)));
+
+ // Store the TrackLabel pointer
+ //
+ m_trackLabels.push_back(trackLabel);
+
+ // Connect it
+ setButtonMapping(trackLabel, trackId);
+
+ connect(trackLabel, SIGNAL(changeToInstrumentList()),
+ m_instListSigMapper, SLOT(map()));
+ connect(trackLabel, SIGNAL(clicked()),
+ m_clickedSigMapper, SLOT(map()));
+
+ //
+ // instrument label
+ //
+ Rosegarden::Instrument *ins =
+ m_doc->getStudio().getInstrumentById(track->getInstrument());
+
+ QString instrumentName(i18n("<no instrument>"));
+ if (ins) instrumentName = strtoqstr(ins->getPresentationName());
+
+ // Set label to program change if it's being sent
+ //
+ if (ins != 0 && ins->sendsProgramChange())
+ trackLabel->setAlternativeLabel(strtoqstr(ins->getProgramName()));
+
+ trackLabel->showLabel(m_trackInstrumentLabels);
+
+ mute->setFixedSize(m_cellSize - buttonGap, m_cellSize - buttonGap);
+ record->setFixedSize(m_cellSize - buttonGap, m_cellSize - buttonGap);
+
+ // set the mute button
+ //
+ if (track->isMuted())
+ mute->off();
+
+ return trackHBox;
+}
+
+}
+#include "TrackButtons.moc"
diff --git a/src/gui/editors/segment/TrackButtons.h b/src/gui/editors/segment/TrackButtons.h
new file mode 100644
index 0000000..a61601d
--- /dev/null
+++ b/src/gui/editors/segment/TrackButtons.h
@@ -0,0 +1,228 @@
+
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
+
+/*
+ Rosegarden
+ A MIDI and audio sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_TRACKBUTTONS_H_
+#define _RG_TRACKBUTTONS_H_
+
+#include "base/MidiProgram.h"
+#include "base/Track.h"
+#include "gui/application/RosegardenGUIApp.h"
+#include "TrackLabel.h"
+#include <qframe.h>
+#include <qstring.h>
+#include <vector>
+
+
+class QWidget;
+class QVBoxLayout;
+class QSignalMapper;
+class QPopupMenu;
+class QObject;
+
+
+namespace Rosegarden
+{
+
+class TrackVUMeter;
+class RosegardenGUIDoc;
+class KLedButton;
+class Instrument;
+
+
+class TrackButtons : public QFrame
+{
+ Q_OBJECT
+public:
+
+ TrackButtons(RosegardenGUIDoc* doc,
+ unsigned int trackCellHeight,
+ unsigned int trackLabelWidth,
+ bool showTrackLabels,
+ int overallHeight,
+ QWidget* parent = 0,
+ const char* name = 0,
+ WFlags f=0);
+
+ ~TrackButtons();
+
+ /// Return a vector of muted tracks
+ std::vector<int> mutedTracks();
+
+ /// Return a vector of highlighted tracks
+ std::vector<int> getHighlightedTracks();
+
+ void changeTrackInstrumentLabels(TrackLabel::InstrumentTrackLabels label);
+
+ /**
+ * Change the instrument label to something else like
+ * an actual program name rather than a meaningless
+ * device number and midi channel
+ */
+ void changeInstrumentLabel(InstrumentId id, QString label);
+
+ void changeTrackLabel(TrackId id, QString label);
+
+ // Select a label from outside this class by position
+ //
+ void selectLabel(int trackId);
+
+ /*
+ * Set the mute button down or up
+ */
+ void setMuteButton(TrackId track, bool value);
+
+ /*
+ * Make this available so that others can set record buttons down
+ */
+ void setRecordTrack(int position, bool value);
+
+ /**
+ * Precalculate the Instrument popup so we don't have to every
+ * time it appears
+ * not protected because also used by the RosegardenGUIApp
+ *
+ * @see RosegardenGUIApp#slotPopulateTrackInstrumentPopup()
+ */
+ void populateInstrumentPopup(Instrument *thisTrackInstr, QPopupMenu* instrumentPopup);
+
+signals:
+ // to emit what Track has been selected
+ //
+ void trackSelected(int);
+ void instrumentSelected(int);
+
+ void widthChanged();
+
+ // to tell the notation canvas &c when a name changes
+ //
+ void nameChanged();
+
+ // document modified (mute button)
+ //
+ void modified();
+
+ // A record button has been pressed - if we're setting to an audio
+ // track we need to tell the sequencer for live monitoring
+ // purposes.
+ //
+ void recordButton(TrackId track, bool state);
+
+ // A mute button has been pressed
+ //
+ void muteButton(TrackId track, bool state);
+
+public slots:
+
+ void slotToggleRecordTrack(int position);
+ void slotToggleMutedTrack(int mutedTrack);
+ void slotUpdateTracks();
+ void slotRenameTrack(QString newName, TrackId trackId);
+ void slotSetTrackMeter(float value, int position);
+ void slotSetMetersByInstrument(float value, InstrumentId id);
+
+ void slotInstrumentSelection(int);
+ void slotInstrumentPopupActivated(int);
+ void slotTrackInstrumentSelection(TrackId, int);
+
+ // ensure track buttons match the Composition
+ //
+ void slotSynchroniseWithComposition();
+
+ // Convert a positional selection into a track selection and re-emit
+ //
+ void slotLabelSelected(int position);
+
+protected:
+
+ /**
+ * Populate the track buttons themselves with Instrument information
+ */
+ void populateButtons();
+
+ /**
+ * Remove buttons and clear iterators for a position
+ */
+ void removeButtons(unsigned int position);
+
+ /**
+ * Set record button - graphically only
+ */
+ void setRecordButton(int position, bool down);
+
+ /**
+ * buttons, starting at the specified index
+ */
+ void makeButtons();
+
+ QFrame* makeButton(TrackId trackId);
+ QString getPresentationName(Instrument *);
+
+ void setButtonMapping(QObject*, TrackId);
+
+ //--------------- Data members ---------------------------------
+
+ RosegardenGUIDoc *m_doc;
+
+ QVBoxLayout *m_layout;
+
+ std::vector<KLedButton *> m_muteLeds;
+ std::vector<KLedButton *> m_recordLeds;
+ std::vector<TrackLabel *> m_trackLabels;
+ std::vector<TrackVUMeter *> m_trackMeters;
+ std::vector<QFrame *> m_trackHBoxes;
+
+ QSignalMapper *m_recordSigMapper;
+ QSignalMapper *m_muteSigMapper;
+ QSignalMapper *m_clickedSigMapper;
+ QSignalMapper *m_instListSigMapper;
+
+ // Number of tracks on our view
+ //
+ unsigned int m_tracks;
+
+ // The pixel offset from the top - just to overcome
+ // the borders
+ int m_offset;
+
+ // The height of the cells
+ //
+ int m_cellSize;
+
+ // gaps between elements
+ //
+ int m_borderGap;
+
+ int m_trackLabelWidth;
+ int m_popupItem;
+
+ TrackLabel::InstrumentTrackLabels m_trackInstrumentLabels;
+ int m_lastSelected;
+};
+
+
+
+}
+
+#endif
diff --git a/src/gui/editors/segment/TrackEditor.cpp b/src/gui/editors/segment/TrackEditor.cpp
new file mode 100644
index 0000000..32c2b02
--- /dev/null
+++ b/src/gui/editors/segment/TrackEditor.cpp
@@ -0,0 +1,827 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
+
+/*
+ Rosegarden
+ A MIDI and audio sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "TrackEditor.h"
+#include <qlayout.h>
+#include <kapplication.h>
+
+#include <klocale.h>
+#include <kconfig.h>
+#include <kstddirs.h>
+#include "misc/Debug.h"
+#include "document/ConfigGroups.h"
+#include "gui/application/RosegardenDCOP.h"
+#include "gui/seqmanager/SequenceManager.h"
+#include "gui/rulers/StandardRuler.h"
+#include "base/Composition.h"
+#include "base/MidiProgram.h"
+#include "base/RealTime.h"
+#include "base/RulerScale.h"
+#include "base/Segment.h"
+#include "base/Selection.h"
+#include "commands/segment/AddTracksCommand.h"
+#include "commands/segment/DeleteTracksCommand.h"
+#include "commands/segment/SegmentEraseCommand.h"
+#include "commands/segment/SegmentInsertCommand.h"
+#include "commands/segment/SegmentRepeatToCopyCommand.h"
+#include "segmentcanvas/CompositionModel.h"
+#include "segmentcanvas/CompositionModelImpl.h"
+#include "segmentcanvas/CompositionView.h"
+#include "document/MultiViewCommandHistory.h"
+#include "document/RosegardenGUIDoc.h"
+#include "gui/application/RosegardenGUIApp.h"
+#include "gui/rulers/ChordNameRuler.h"
+#include "gui/rulers/TempoRuler.h"
+#include "gui/rulers/LoopRuler.h"
+#include "gui/widgets/ProgressDialog.h"
+#include "gui/widgets/QDeferScrollView.h"
+#include "sound/AudioFile.h"
+#include "TrackButtons.h"
+#include "TrackEditorIface.h"
+#include <dcopobject.h>
+#include <kcommand.h>
+#include <kglobal.h>
+#include <kmessagebox.h>
+#include <qapplication.h>
+#include <qcursor.h>
+#include <qfont.h>
+#include <qpixmap.h>
+#include <qpoint.h>
+#include <qscrollview.h>
+#include <qstring.h>
+#include <qstringlist.h>
+#include <qstrlist.h>
+#include <qwidget.h>
+#include <qvalidator.h>
+#include <qdragobject.h>
+#include <qtextstream.h>
+
+
+namespace Rosegarden
+{
+
+TrackEditor::TrackEditor(RosegardenGUIDoc* doc,
+ QWidget* rosegardenguiview,
+ RulerScale *rulerScale,
+ bool showTrackLabels,
+ double initialUnitsPerPixel,
+ QWidget* parent, const char* name,
+ WFlags) :
+ DCOPObject("TrackEditorIface"),
+ QWidget(parent, name),
+ m_doc(doc),
+ m_rulerScale(rulerScale),
+ m_topStandardRuler(0),
+ m_bottomStandardRuler(0),
+ m_trackButtons(0),
+ m_segmentCanvas(0),
+ m_trackButtonScroll(0),
+ m_showTrackLabels(showTrackLabels),
+ m_canvasWidth(0),
+ m_compositionRefreshStatusId(doc->getComposition().getNewRefreshStatusId()),
+ m_playTracking(true),
+ m_initialUnitsPerPixel(initialUnitsPerPixel)
+{
+ // accept dnd
+ setAcceptDrops(true);
+
+ init(rosegardenguiview);
+ slotReadjustCanvasSize();
+}
+
+TrackEditor::~TrackEditor()
+{
+ delete m_chordNameRuler;
+ delete m_compositionModel;
+}
+
+void
+TrackEditor::init(QWidget* rosegardenguiview)
+{
+ QGridLayout *grid = new QGridLayout(this, 4, 2);
+
+ int trackLabelWidth = 230;
+ int barButtonsHeight = 25;
+
+ m_chordNameRuler = new ChordNameRuler(m_rulerScale,
+ m_doc,
+ 0.0,
+ 20,
+ this);
+ grid->addWidget(m_chordNameRuler, 0, 1);
+
+ m_tempoRuler = new TempoRuler(m_rulerScale,
+ m_doc,
+ RosegardenGUIApp::self(),
+ 0.0,
+ 24,
+ true,
+ this);
+
+ grid->addWidget(m_tempoRuler, 1, 1);
+
+ m_tempoRuler->connectSignals();
+
+ //
+ // Top Bar Buttons
+ //
+ m_topStandardRuler = new StandardRuler(m_doc,
+ m_rulerScale,
+ 0,
+ barButtonsHeight,
+ false,
+ this, "topbarbuttons");
+ m_topStandardRuler->connectRulerToDocPointer(m_doc);
+
+ grid->addWidget(m_topStandardRuler, 2, 1);
+
+ //
+ // Segment Canvas
+ //
+ m_compositionModel = new CompositionModelImpl(m_doc->getComposition(),
+ m_doc->getStudio(),
+ m_rulerScale, getTrackCellHeight());
+
+ connect(rosegardenguiview, SIGNAL(instrumentParametersChanged(InstrumentId)),
+ m_compositionModel, SLOT(slotInstrumentParametersChanged(InstrumentId)));
+ connect(rosegardenguiview->parent(), SIGNAL(instrumentParametersChanged(InstrumentId)),
+ m_compositionModel, SLOT(slotInstrumentParametersChanged(InstrumentId)));
+
+ m_segmentCanvas = new CompositionView(m_doc, m_compositionModel, this);
+
+ kapp->config()->setGroup(GeneralOptionsConfigGroup);
+ if (kapp->config()->readBoolEntry("backgroundtextures", true)) {
+ QPixmap background;
+ QString pixmapDir = KGlobal::dirs()->findResource("appdata", "pixmaps/");
+ if (background.load(QString("%1/misc/bg-segmentcanvas.xpm").
+ arg(pixmapDir))) {
+ m_segmentCanvas->setBackgroundPixmap(background);
+ m_segmentCanvas->viewport()->setBackgroundPixmap(background);
+ }
+ }
+
+ //
+ // Bottom Bar Buttons
+ //
+ m_bottomStandardRuler = new StandardRuler(m_doc,
+ m_rulerScale,
+ 0,
+ barButtonsHeight,
+ true,
+ m_segmentCanvas, "bottombarbuttons");
+ m_bottomStandardRuler->connectRulerToDocPointer(m_doc);
+
+ m_segmentCanvas->setBottomFixedWidget(m_bottomStandardRuler);
+
+ grid->addWidget(m_segmentCanvas, 3, 1);
+
+ grid->setColStretch(1, 10); // to make sure the seg canvas doesn't leave a "blank" grey space when
+ // loading a file which has a low zoom factor
+
+ // Track Buttons
+ //
+ // (must be put in a QScrollView)
+ //
+ m_trackButtonScroll = new QDeferScrollView(this);
+ grid->addWidget(m_trackButtonScroll, 3, 0);
+
+ int canvasHeight = getTrackCellHeight() *
+ std::max(40u, m_doc->getComposition().getNbTracks());
+
+ m_trackButtons = new TrackButtons(m_doc,
+ getTrackCellHeight(),
+ trackLabelWidth,
+ m_showTrackLabels,
+ canvasHeight,
+ m_trackButtonScroll->viewport());
+ m_trackButtonScroll->addChild(m_trackButtons);
+ m_trackButtonScroll->setHScrollBarMode(QScrollView::AlwaysOff);
+ m_trackButtonScroll->setVScrollBarMode(QScrollView::AlwaysOff);
+ m_trackButtonScroll->setResizePolicy(QScrollView::AutoOneFit);
+ m_trackButtonScroll->setBottomMargin(m_bottomStandardRuler->height() +
+ m_segmentCanvas->horizontalScrollBar()->height());
+
+ connect(m_trackButtons, SIGNAL(widthChanged()),
+ this, SLOT(slotTrackButtonsWidthChanged()));
+
+ connect(m_trackButtons, SIGNAL(trackSelected(int)),
+ rosegardenguiview, SLOT(slotSelectTrackSegments(int)));
+
+ connect(m_trackButtons, SIGNAL(instrumentSelected(int)),
+ rosegardenguiview, SLOT(slotUpdateInstrumentParameterBox(int)));
+
+ connect(this, SIGNAL(stateChange(QString, bool)),
+ rosegardenguiview, SIGNAL(stateChange(QString, bool)));
+
+ connect(m_trackButtons, SIGNAL(modified()),
+ m_doc, SLOT(slotDocumentModified()));
+
+ connect(m_trackButtons, SIGNAL(muteButton(TrackId, bool)),
+ rosegardenguiview, SLOT(slotSetMuteButton(TrackId, bool)));
+
+ // connect loop rulers' follow-scroll signals
+ connect(m_topStandardRuler->getLoopRuler(), SIGNAL(startMouseMove(int)),
+ m_segmentCanvas, SLOT(startAutoScroll(int)));
+ connect(m_topStandardRuler->getLoopRuler(), SIGNAL(stopMouseMove()),
+ m_segmentCanvas, SLOT(stopAutoScroll()));
+ connect(m_bottomStandardRuler->getLoopRuler(), SIGNAL(startMouseMove(int)),
+ m_segmentCanvas, SLOT(startAutoScroll(int)));
+ connect(m_bottomStandardRuler->getLoopRuler(), SIGNAL(stopMouseMove()),
+ m_segmentCanvas, SLOT(stopAutoScroll()));
+
+ connect(m_segmentCanvas, SIGNAL(contentsMoving(int, int)),
+ this, SLOT(slotCanvasScrolled(int, int)));
+
+ // Synchronize bar buttons' scrollview with segment canvas' scrollbar
+ //
+ connect(m_segmentCanvas->verticalScrollBar(), SIGNAL(valueChanged(int)),
+ this, SLOT(slotVerticalScrollTrackButtons(int)));
+
+ connect(m_segmentCanvas->verticalScrollBar(), SIGNAL(sliderMoved(int)),
+ this, SLOT(slotVerticalScrollTrackButtons(int)));
+
+ // scrolling with mouse wheel
+ connect(m_trackButtonScroll, SIGNAL(gotWheelEvent(QWheelEvent*)),
+ m_segmentCanvas, SLOT(slotExternalWheelEvent(QWheelEvent*)));
+
+ // Connect horizontal scrollbar
+ //
+ connect(m_segmentCanvas->horizontalScrollBar(), SIGNAL(valueChanged(int)),
+ m_topStandardRuler, SLOT(slotScrollHoriz(int)));
+ connect(m_segmentCanvas->horizontalScrollBar(), SIGNAL(sliderMoved(int)),
+ m_topStandardRuler, SLOT(slotScrollHoriz(int)));
+
+ connect(m_segmentCanvas->horizontalScrollBar(), SIGNAL(valueChanged(int)),
+ m_bottomStandardRuler, SLOT(slotScrollHoriz(int)));
+ connect(m_segmentCanvas->horizontalScrollBar(), SIGNAL(sliderMoved(int)),
+ m_bottomStandardRuler, SLOT(slotScrollHoriz(int)));
+
+ connect(m_segmentCanvas->horizontalScrollBar(), SIGNAL(valueChanged(int)),
+ m_tempoRuler, SLOT(slotScrollHoriz(int)));
+ connect(m_segmentCanvas->horizontalScrollBar(), SIGNAL(sliderMoved(int)),
+ m_tempoRuler, SLOT(slotScrollHoriz(int)));
+
+ connect(m_segmentCanvas->horizontalScrollBar(), SIGNAL(valueChanged(int)),
+ m_chordNameRuler, SLOT(slotScrollHoriz(int)));
+ connect(m_segmentCanvas->horizontalScrollBar(), SIGNAL(sliderMoved(int)),
+ m_chordNameRuler, SLOT(slotScrollHoriz(int)));
+
+ connect(this, SIGNAL(needUpdate()), m_segmentCanvas, SLOT(slotUpdateSegmentsDrawBuffer()));
+
+ connect(m_segmentCanvas->getModel(),
+ SIGNAL(selectedSegments(const SegmentSelection &)),
+ rosegardenguiview,
+ SLOT(slotSelectedSegments(const SegmentSelection &)));
+
+ connect(m_segmentCanvas, SIGNAL(zoomIn()),
+ RosegardenGUIApp::self(), SLOT(slotZoomIn()));
+ connect(m_segmentCanvas, SIGNAL(zoomOut()),
+ RosegardenGUIApp::self(), SLOT(slotZoomOut()));
+
+ connect(getCommandHistory(), SIGNAL(commandExecuted()),
+ this, SLOT(update()));
+
+ connect(m_doc, SIGNAL(pointerPositionChanged(timeT)),
+ this, SLOT(slotSetPointerPosition(timeT)));
+
+ //
+ // pointer and loop drag signals from top and bottom bar buttons (loop rulers actually)
+ //
+ connect(m_topStandardRuler, SIGNAL(dragPointerToPosition(timeT)),
+ this, SLOT(slotPointerDraggedToPosition(timeT)));
+ connect(m_bottomStandardRuler, SIGNAL(dragPointerToPosition(timeT)),
+ this, SLOT(slotPointerDraggedToPosition(timeT)));
+
+ connect(m_topStandardRuler, SIGNAL(dragLoopToPosition(timeT)),
+ this, SLOT(slotLoopDraggedToPosition(timeT)));
+ connect(m_bottomStandardRuler, SIGNAL(dragLoopToPosition(timeT)),
+ this, SLOT(slotLoopDraggedToPosition(timeT)));
+
+ connect(m_doc, SIGNAL(loopChanged(timeT,
+ timeT)),
+ this, SLOT(slotSetLoop(timeT, timeT)));
+}
+
+void TrackEditor::slotReadjustCanvasSize()
+{
+ m_segmentCanvas->slotUpdateSize();
+}
+
+void TrackEditor::slotTrackButtonsWidthChanged()
+{
+ // We need to make sure the trackButtons geometry is fully updated
+ //
+ ProgressDialog::processEvents();
+
+ m_trackButtonScroll->setMinimumWidth(m_trackButtons->width());
+ m_doc->slotDocumentModified();
+}
+
+int TrackEditor::getTrackCellHeight() const
+{
+ int size;
+ static QFont defaultFont;
+
+ // do some scrabbling around for a reasonable size
+ //
+ size = defaultFont.pixelSize();
+
+ if (size < 8) {
+ if (QApplication::font(this).pixelSize() < 8)
+ size = 12;
+ else
+ size = QApplication::font(this).pixelSize();
+ }
+
+ return size + 12;
+}
+
+bool TrackEditor::isCompositionModified()
+{
+ return m_doc->getComposition().getRefreshStatus
+ (m_compositionRefreshStatusId).needsRefresh();
+}
+
+void TrackEditor::setCompositionModified(bool c)
+{
+ m_doc->getComposition().getRefreshStatus
+ (m_compositionRefreshStatusId).setNeedsRefresh(c);
+}
+
+void TrackEditor::updateRulers()
+{
+ if (getTempoRuler() != 0)
+ getTempoRuler()->update();
+
+ if (getChordNameRuler() != 0)
+ getChordNameRuler()->update();
+
+ getTopStandardRuler()->update();
+ getBottomStandardRuler()->update();
+}
+
+void TrackEditor::paintEvent(QPaintEvent* e)
+{
+ if (isCompositionModified()) {
+
+ slotReadjustCanvasSize();
+ m_trackButtons->slotUpdateTracks();
+ m_segmentCanvas->clearSegmentRectsCache(true);
+ m_segmentCanvas->updateContents();
+
+ Composition &composition = m_doc->getComposition();
+
+ if (composition.getNbSegments() == 0) {
+ emit stateChange("have_segments", false); // no segments : reverse state
+ emit stateChange("have_selection", false); // no segments : reverse state
+ } else {
+ emit stateChange("have_segments", true);
+ if (m_segmentCanvas->haveSelection())
+ emit stateChange("have_selection", true);
+ else
+ emit stateChange("have_selection", false); // no selection : reverse state
+ }
+
+ setCompositionModified(false);
+ }
+
+ QWidget::paintEvent(e);
+}
+
+void TrackEditor::slotAddTracks(unsigned int nbNewTracks,
+ InstrumentId id,
+ int position)
+{
+ Composition &comp = m_doc->getComposition();
+
+ AddTracksCommand* command = new AddTracksCommand(&comp, nbNewTracks, id,
+ position);
+ addCommandToHistory(command);
+ slotReadjustCanvasSize();
+}
+
+void TrackEditor::slotDeleteTracks(std::vector<TrackId> tracks)
+{
+ Composition &comp = m_doc->getComposition();
+
+ DeleteTracksCommand* command = new DeleteTracksCommand(&comp, tracks);
+ addCommandToHistory(command);
+}
+
+void TrackEditor::addSegment(int track, int time, unsigned int duration)
+{
+ if (!m_doc)
+ return ; // sanity check
+
+ SegmentInsertCommand *command =
+ new SegmentInsertCommand(m_doc, track, time, duration);
+
+ addCommandToHistory(command);
+}
+
+void TrackEditor::slotSegmentOrderChanged(int section, int fromIdx, int toIdx)
+{
+ RG_DEBUG << QString("TrackEditor::segmentOrderChanged(section : %1, from %2, to %3)")
+ .arg(section).arg(fromIdx).arg(toIdx) << endl;
+
+ //!!! how do we get here? need to involve a command
+ emit needUpdate();
+}
+
+void
+TrackEditor::slotCanvasScrolled(int x, int y)
+{
+ // update the pointer position if the user is dragging it from the loop ruler
+ if ((m_topStandardRuler && m_topStandardRuler->getLoopRuler() &&
+ m_topStandardRuler->getLoopRuler()->hasActiveMousePress() &&
+ !m_topStandardRuler->getLoopRuler()->getLoopingMode()) ||
+ (m_bottomStandardRuler && m_bottomStandardRuler->getLoopRuler() &&
+ m_bottomStandardRuler->getLoopRuler()->hasActiveMousePress() &&
+ !m_bottomStandardRuler->getLoopRuler()->getLoopingMode())) {
+
+ int mx = m_segmentCanvas->viewport()->mapFromGlobal(QCursor::pos()).x();
+ m_segmentCanvas->setPointerPos(x + mx);
+
+ // bad idea, creates a feedback loop
+ // timeT t = m_segmentCanvas->grid().getRulerScale()->getTimeForX(x + mx);
+ // slotSetPointerPosition(t);
+ }
+}
+
+void
+TrackEditor::slotSetPointerPosition(timeT position)
+{
+ SimpleRulerScale *ruler =
+ dynamic_cast<SimpleRulerScale*>(m_rulerScale);
+
+ if (!ruler)
+ return ;
+
+ double pos = m_segmentCanvas->grid().getRulerScale()->getXForTime(position);
+
+ int currentPointerPos = m_segmentCanvas->getPointerPos();
+
+ double distance = pos - currentPointerPos;
+ if (distance < 0.0)
+ distance = -distance;
+
+ if (distance >= 1.0) {
+
+ if (m_doc && m_doc->getSequenceManager() &&
+ (m_doc->getSequenceManager()->getTransportStatus() != STOPPED)) {
+
+ if (m_playTracking) {
+ getSegmentCanvas()->slotScrollHoriz(int(double(position) / ruler->getUnitsPerPixel()));
+ }
+ } else if (!getSegmentCanvas()->isAutoScrolling()) {
+ int newpos = int(double(position) / ruler->getUnitsPerPixel());
+ // RG_DEBUG << "TrackEditor::slotSetPointerPosition("
+ // << position
+ // << ") : calling canvas->slotScrollHoriz() "
+ // << newpos << endl;
+ getSegmentCanvas()->slotScrollHoriz(newpos);
+ }
+
+ m_segmentCanvas->setPointerPos(pos);
+ }
+
+}
+
+void
+TrackEditor::slotPointerDraggedToPosition(timeT position)
+{
+ int currentPointerPos = m_segmentCanvas->getPointerPos();
+
+ double newPosition;
+
+ if (handleAutoScroll(currentPointerPos, position, newPosition))
+ m_segmentCanvas->setPointerPos(int(newPosition));
+}
+
+void
+TrackEditor::slotLoopDraggedToPosition(timeT position)
+{
+ if (m_doc) {
+ int currentEndLoopPos = m_doc->getComposition().getLoopEnd();
+ double dummy;
+ handleAutoScroll(currentEndLoopPos, position, dummy);
+ }
+}
+
+bool TrackEditor::handleAutoScroll(int currentPosition, timeT newTimePosition, double &newPosition)
+{
+ SimpleRulerScale *ruler =
+ dynamic_cast<SimpleRulerScale*>(m_rulerScale);
+
+ if (!ruler)
+ return false;
+
+ newPosition = m_segmentCanvas->grid().getRulerScale()->getXForTime(newTimePosition);
+
+ double distance = fabs(newPosition - currentPosition);
+
+ bool moveDetected = distance >= 1.0;
+
+ if (moveDetected) {
+
+ if (m_doc && m_doc->getSequenceManager() &&
+ (m_doc->getSequenceManager()->getTransportStatus() != STOPPED)) {
+
+ if (m_playTracking) {
+ getSegmentCanvas()->slotScrollHoriz(int(double(newTimePosition) / ruler->getUnitsPerPixel()));
+ }
+ } else {
+ int newpos = int(double(newTimePosition) / ruler->getUnitsPerPixel());
+ getSegmentCanvas()->slotScrollHorizSmallSteps(newpos);
+ getSegmentCanvas()->doAutoScroll();
+ }
+
+ }
+
+ return moveDetected;
+}
+
+void
+TrackEditor::slotToggleTracking()
+{
+ m_playTracking = !m_playTracking;
+}
+
+void
+TrackEditor::slotSetLoop(timeT start, timeT end)
+{
+ getTopStandardRuler()->getLoopRuler()->slotSetLoopMarker(start, end);
+ getBottomStandardRuler()->getLoopRuler()->slotSetLoopMarker(start, end);
+}
+
+MultiViewCommandHistory*
+TrackEditor::getCommandHistory()
+{
+ return m_doc->getCommandHistory();
+}
+
+void
+TrackEditor::addCommandToHistory(KCommand *command)
+{
+ getCommandHistory()->addCommand(command);
+}
+
+void
+TrackEditor::slotScrollToTrack(int track)
+{
+ // Find the vertical track pos
+ int newY = track * getTrackCellHeight();
+
+ RG_DEBUG << "TrackEditor::scrollToTrack(" << track <<
+ ") scrolling to Y " << newY << endl;
+
+ // Scroll the segment view; it will scroll tracks by connected signals
+ // slotVerticalScrollTrackButtons(newY);
+ m_segmentCanvas->slotScrollVertSmallSteps(newY);
+}
+
+void
+TrackEditor::slotDeleteSelectedSegments()
+{
+ KMacroCommand *macro = new KMacroCommand("Delete Segments");
+
+ SegmentSelection segments =
+ m_segmentCanvas->getSelectedSegments();
+
+ if (segments.size() == 0)
+ return ;
+
+ SegmentSelection::iterator it;
+
+ // Clear the selection before erasing the Segments
+ // the selection points to
+ //
+ m_segmentCanvas->getModel()->clearSelected();
+
+ // Create the compound command
+ //
+ for (it = segments.begin(); it != segments.end(); it++) {
+ macro->addCommand(new SegmentEraseCommand(*it,
+ &m_doc->getAudioFileManager()));
+ }
+
+ addCommandToHistory(macro);
+
+}
+
+void
+TrackEditor::slotTurnRepeatingSegmentToRealCopies()
+{
+ RG_DEBUG << "TrackEditor::slotTurnRepeatingSegmentToRealCopies" << endl;
+
+ SegmentSelection segments =
+ m_segmentCanvas->getSelectedSegments();
+
+ if (segments.size() == 0)
+ return ;
+
+ QString text;
+
+ if (segments.size() == 1)
+ text = i18n("Turn Repeating Segment into Real Copies");
+ else
+ text = i18n("Turn Repeating Segments into Real Copies");
+
+ KMacroCommand *macro = new KMacroCommand(text);
+
+ SegmentSelection::iterator it = segments.begin();
+ for (; it != segments.end(); it++) {
+ if ((*it)->isRepeating()) {
+ macro->addCommand(new SegmentRepeatToCopyCommand(*it));
+ }
+ }
+
+ addCommandToHistory(macro);
+
+}
+
+void
+TrackEditor::slotVerticalScrollTrackButtons(int y)
+{
+ m_trackButtonScroll->setContentsPos(0, y);
+}
+
+void TrackEditor::dragEnterEvent(QDragEnterEvent *event)
+{
+ event->accept(QUriDrag::canDecode(event) ||
+ QTextDrag::canDecode(event));
+}
+
+void TrackEditor::dropEvent(QDropEvent* event)
+{
+ QStrList uri;
+ QString text;
+
+ int heightAdjust = 0;
+ //int widthAdjust = 0;
+
+ // Adjust any drop event height position by visible rulers
+ //
+ if (m_topStandardRuler && m_topStandardRuler->isVisible())
+ heightAdjust += m_topStandardRuler->height();
+
+ if (m_tempoRuler && m_tempoRuler->isVisible())
+ heightAdjust += m_tempoRuler->height();
+
+ if (m_chordNameRuler && m_chordNameRuler->isVisible())
+ heightAdjust += m_chordNameRuler->height();
+
+ QPoint posInSegmentCanvas =
+ m_segmentCanvas->viewportToContents
+ (m_segmentCanvas->
+ viewport()->mapFrom(this, event->pos()));
+
+ int trackPos = m_segmentCanvas->grid().getYBin(posInSegmentCanvas.y());
+
+ timeT time =
+// m_segmentCanvas->grid().getRulerScale()->
+// getTimeForX(posInSegmentCanvas.x());
+ m_segmentCanvas->grid().snapX(posInSegmentCanvas.x());
+
+
+ if (QUriDrag::decode(event, uri)) {
+ RG_DEBUG << "TrackEditor::dropEvent() : got URI :"
+ << uri.first() << endl;
+ QString uriPath = uri.first();
+
+ if (uriPath.endsWith(".rg")) {
+ emit droppedDocument(uriPath);
+ } else {
+
+ QStrList uris;
+ QString uri;
+ if (QUriDrag::decode(event, uris)) uri = uris.first();
+// QUriDrag::decodeLocalFiles(event, files);
+// QString filePath = files.first();
+
+ RG_DEBUG << "TrackEditor::dropEvent() : got URI: "
+ << uri << endl;
+
+ RG_DEBUG << "TrackEditor::dropEvent() : dropping at track pos = "
+ << trackPos
+ << ", time = "
+ << time
+ << ", x = "
+ << event->pos().x()
+ << ", mapped x = "
+ << posInSegmentCanvas.x()
+ << endl;
+
+ Track* track = m_doc->getComposition().getTrackByPosition(trackPos);
+ if (track) {
+ QString audioText;
+ QTextOStream t(&audioText);
+
+ t << uri << "\n";
+ t << track->getId() << "\n";
+ t << time << "\n";
+
+ emit droppedNewAudio(audioText);
+ }
+
+ }
+
+ } else if (QTextDrag::decode(event, text)) {
+ RG_DEBUG << "TrackEditor::dropEvent() : got text info " << endl;
+ //<< text << endl;
+
+ if (text.endsWith(".rg")) {
+ emit droppedDocument(text);
+ //
+ // WARNING
+ //
+ // DO NOT PERFORM ANY OPERATIONS AFTER THAT
+ // EMITTING THIS SIGNAL TRIGGERS THE LOADING OF A NEW DOCUMENT
+ // AND AS A CONSEQUENCE THE DELETION OF THIS TrackEditor OBJECT
+ //
+ } else {
+
+ QTextIStream s(&text);
+
+ QString id;
+ AudioFileId audioFileId;
+ RealTime startTime, endTime;
+
+ // read the audio info checking for end of stream
+ s >> id;
+ s >> audioFileId;
+ s >> startTime.sec;
+ s >> startTime.nsec;
+ s >> endTime.sec;
+ s >> endTime.nsec;
+
+ if (id == "AudioFileManager") { // only create something if this is data from the right client
+
+
+ // Drop this audio segment if we have a valid track number
+ // (could also check for time limits too)
+ //
+ Track* track = m_doc->getComposition().getTrackByPosition(trackPos);
+ if (track) {
+
+ RG_DEBUG << "TrackEditor::dropEvent() : dropping at track pos = "
+ << trackPos
+ << ", time = "
+ << time
+ << ", x = "
+ << event->pos().x()
+ << ", map = "
+ << posInSegmentCanvas.x()
+ << endl;
+
+ QString audioText;
+ QTextOStream t(&audioText);
+ t << audioFileId << "\n";
+ t << track->getId() << "\n";
+ t << time << "\n"; // time on canvas
+ t << startTime.sec << "\n";
+ t << startTime.nsec << "\n";
+ t << endTime.sec << "\n";
+ t << endTime.nsec << "\n";
+
+ emit droppedAudio(audioText);
+ }
+
+ } else {
+
+ KMessageBox::sorry(this, i18n("You can't drop files into Rosegarden from this client. Try using Konqueror instead."));
+
+ }
+
+ }
+
+ // SEE WARNING ABOVE - DON'T DO ANYTHING, THIS OBJECT MAY NOT
+ // EXIST AT THIS POINT.
+
+ }
+}
+
+}
+#include "TrackEditor.moc"
diff --git a/src/gui/editors/segment/TrackEditor.h b/src/gui/editors/segment/TrackEditor.h
new file mode 100644
index 0000000..6670a15
--- /dev/null
+++ b/src/gui/editors/segment/TrackEditor.h
@@ -0,0 +1,248 @@
+
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
+
+/*
+ Rosegarden
+ A MIDI and audio sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_TRACKEDITOR_H_
+#define _RG_TRACKEDITOR_H_
+
+#include "base/MidiProgram.h"
+#include <map>
+#include "TrackEditorIface.h"
+#include <qstring.h>
+#include <qwidget.h>
+#include "base/Event.h"
+#include "gui/editors/segment/TrackButtons.h"
+
+
+class QPaintEvent;
+class QDropEvent;
+class QDragEnterEvent;
+class KCommand;
+
+
+namespace Rosegarden
+{
+
+class TrackButtons;
+class TempoRuler;
+class Segment;
+class RulerScale;
+class RosegardenGUIDoc;
+class QDeferScrollView;
+class MultiViewCommandHistory;
+class CompositionView;
+class CompositionModel;
+class ChordNameRuler;
+class StandardRuler;
+
+
+/**
+ * Global widget for segment edition.
+ *
+ * Shows a global overview of the composition, and lets the user
+ * manipulate the segments
+ *
+ * @see CompositionView
+ */
+class TrackEditor : public QWidget, virtual public TrackEditorIface
+{
+ Q_OBJECT
+public:
+ /**
+ * Create a new TrackEditor representing the document \a doc
+ */
+ TrackEditor(RosegardenGUIDoc* doc,
+ QWidget* rosegardenguiview,
+ RulerScale *rulerScale,
+ bool showTrackLabels,
+ double initialUnitsPerPixel = 0,
+ QWidget* parent = 0, const char* name = 0,
+ WFlags f=0);
+
+ ~TrackEditor();
+
+ CompositionView* getSegmentCanvas() { return m_segmentCanvas; }
+ TempoRuler* getTempoRuler() { return m_tempoRuler; }
+ ChordNameRuler*getChordNameRuler() { return m_chordNameRuler; }
+ StandardRuler* getTopStandardRuler() { return m_topStandardRuler; }
+ StandardRuler* getBottomStandardRuler() { return m_bottomStandardRuler; }
+ TrackButtons* getTrackButtons() { return m_trackButtons; }
+ RulerScale* getRulerScale() { return m_rulerScale; }
+
+ int getTrackCellHeight() const;
+
+ /**
+ * Add a new segment - DCOP interface
+ */
+ virtual void addSegment(int track, int start, unsigned int duration);
+
+ /**
+ * Manage command history
+ */
+ MultiViewCommandHistory *getCommandHistory();
+ void addCommandToHistory(KCommand *command);
+
+ void updateRulers();
+
+ bool isTracking() const { return m_playTracking; }
+
+public slots:
+
+ /**
+ * Scroll the view such that the numbered track is on-screen
+ */
+ void slotScrollToTrack(int trackPosition);
+
+ /**
+ * Set the position pointer during playback
+ */
+ void slotSetPointerPosition(timeT position);
+
+ /**
+ * Update the pointer position as it is being dragged along
+ * This changes how the segment canvas will scroll to follow the pointer
+ */
+ void slotPointerDraggedToPosition(timeT position);
+
+ /**
+ * Update the loop end position as it is being dragged along
+ * This changes how the segment canvas will scroll to follow the pointer
+ */
+ void slotLoopDraggedToPosition(timeT position);
+
+ /**
+ * Act on a canvas scroll event
+ */
+ void slotCanvasScrolled(int, int);
+
+ /**
+ * Adjust the canvas size to that required for the composition
+ */
+ void slotReadjustCanvasSize();
+
+ /**
+ * Show the given loop on the ruler or wherever
+ */
+ void slotSetLoop(timeT start, timeT end);
+
+ /**
+ * Add given number of tracks
+ */
+ void slotAddTracks(unsigned int nbTracks, InstrumentId id, int position);
+
+ /*
+ * Delete a given track
+ */
+ void slotDeleteTracks(std::vector<TrackId> tracks);
+
+ void slotDeleteSelectedSegments();
+ void slotTurnRepeatingSegmentToRealCopies();
+
+ void slotToggleTracking();
+
+protected slots:
+ void slotSegmentOrderChanged(int section, int fromIdx, int toIdx);
+
+ void slotTrackButtonsWidthChanged();
+
+ /// Scroll the track buttons along with the segment canvas
+ void slotVerticalScrollTrackButtons(int y);
+
+signals:
+ /**
+ * Emitted when the represented data changed and the SegmentCanvas
+ * needs to update itself
+ *
+ * @see SegmentCanvas::update()
+ */
+ void needUpdate();
+
+ /**
+ * sent back to RosegardenGUI
+ */
+ void stateChange(QString, bool);
+
+ /**
+ * A URI to a Rosegarden document was dropped on the canvas
+ *
+ * @see RosegardenGUI#slotOpenURL()
+ */
+ void droppedDocument(QString uri);
+
+ /**
+ * An audio file was dropped from the audio manager dialog
+ */
+ void droppedAudio(QString audioDesc);
+
+ /**
+ * And audio file was dropped from konqi say and needs to be
+ * inserted into AudioManagerDialog before adding to the
+ * composition.
+ */
+ void droppedNewAudio(QString audioDesc);
+
+protected:
+
+ virtual void dragEnterEvent(QDragEnterEvent *event);
+ virtual void dropEvent(QDropEvent*);
+
+ virtual void paintEvent(QPaintEvent* e);
+
+ void init(QWidget *);
+
+ bool isCompositionModified();
+ void setCompositionModified(bool);
+
+ /// return true if an actual move occurred between current and new position, newPosition contains the horiz. pos corresponding to newTimePosition
+ bool handleAutoScroll(int currentPosition, timeT newTimePosition, double& newPosition);
+
+ //--------------- Data members ---------------------------------
+
+ RosegardenGUIDoc *m_doc;
+ RulerScale *m_rulerScale;
+ TempoRuler *m_tempoRuler;
+ ChordNameRuler *m_chordNameRuler;
+ StandardRuler *m_topStandardRuler;
+ StandardRuler *m_bottomStandardRuler;
+ TrackButtons *m_trackButtons;
+ CompositionView *m_segmentCanvas;
+ CompositionModel *m_compositionModel;
+ QDeferScrollView *m_trackButtonScroll;
+
+ bool m_showTrackLabels;
+ unsigned int m_canvasWidth;
+ unsigned int m_compositionRefreshStatusId;
+ bool m_playTracking;
+
+ typedef std::map<Segment *, unsigned int>
+ SegmentRefreshStatusIdMap;
+ SegmentRefreshStatusIdMap m_segmentsRefreshStatusIds;
+
+ double m_initialUnitsPerPixel;
+};
+
+
+}
+
+#endif
diff --git a/src/gui/editors/segment/TrackEditorIface.cpp b/src/gui/editors/segment/TrackEditorIface.cpp
new file mode 100644
index 0000000..8238c1d
--- /dev/null
+++ b/src/gui/editors/segment/TrackEditorIface.cpp
@@ -0,0 +1,33 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
+
+/*
+ Rosegarden
+ A MIDI and audio sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "TrackEditorIface.h"
+
+#include <dcopobject.h>
+
+
+namespace Rosegarden
+{
+}
diff --git a/src/gui/editors/segment/TrackEditorIface.h b/src/gui/editors/segment/TrackEditorIface.h
new file mode 100644
index 0000000..1637591
--- /dev/null
+++ b/src/gui/editors/segment/TrackEditorIface.h
@@ -0,0 +1,55 @@
+
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
+
+/*
+ Rosegarden
+ A MIDI and audio sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_TRACKEDITORIFACE_H_
+#define _RG_TRACKEDITORIFACE_H_
+
+#include <dcopobject.h>
+
+
+
+
+namespace Rosegarden
+{
+
+
+
+/**
+ * TrackEditor DCOP Interface
+ *
+ * @see TrackEditor
+ */
+class TrackEditorIface : virtual public DCOPObject
+{
+ K_DCOP
+public:
+k_dcop:
+ virtual void addSegment(int instrument, int start, unsigned int length) = 0;
+};
+
+
+}
+
+#endif
diff --git a/src/gui/editors/segment/TrackHeader.cpp b/src/gui/editors/segment/TrackHeader.cpp
new file mode 100644
index 0000000..d7ca6d3
--- /dev/null
+++ b/src/gui/editors/segment/TrackHeader.cpp
@@ -0,0 +1,64 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
+
+/*
+ Rosegarden
+ A MIDI and audio sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "TrackHeader.h"
+
+#include <qheader.h>
+#include <qpainter.h>
+#include <qrect.h>
+#include <qwidget.h>
+
+
+namespace Rosegarden
+{
+
+TrackHeader::~TrackHeader()
+{}
+
+void
+TrackHeader::paintEvent(QPaintEvent *e)
+{
+ QPainter p( this );
+ p.setPen( colorGroup().buttonText() );
+ int pos = (orientation() == Horizontal)
+ ? e->rect().left()
+ : e->rect().top();
+ int id = mapToIndex( sectionAt( pos + offset() ) );
+ if ( id < 0 )
+ if ( pos > 0 )
+ return ;
+ else
+ id = 0;
+ for ( int i = id; i < count(); i++ ) {
+ QRect r = sRect( i );
+ paintSection( &p, i, r );
+ if ( orientation() == Horizontal && r. right() >= e->rect().right() ||
+ orientation() == Vertical && r. bottom() >= e->rect().bottom() )
+ return ;
+ }
+
+}
+
+}
diff --git a/src/gui/editors/segment/TrackHeader.h b/src/gui/editors/segment/TrackHeader.h
new file mode 100644
index 0000000..fe404c3
--- /dev/null
+++ b/src/gui/editors/segment/TrackHeader.h
@@ -0,0 +1,65 @@
+
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
+
+/*
+ Rosegarden
+ A MIDI and audio sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_TRACKHEADER_H_
+#define _RG_TRACKHEADER_H_
+
+#include <qheader.h>
+
+
+class QWidget;
+class QPaintEvent;
+
+
+namespace Rosegarden
+{
+
+
+
+class TrackHeader : public QHeader
+{
+
+public:
+ TrackHeader(int number,
+ QWidget *parent=0,
+ const char *name=0 ):
+ QHeader(number, parent, name) {;}
+ ~TrackHeader();
+
+protected:
+ virtual void paintEvent(QPaintEvent *pe);
+// void paintSection(QPainter * p, int index, QRect fr);
+// void paintSectionLabel (QPainter * p, int index, const QRect & fr);
+// QRect sRect (int index);
+
+private:
+
+};
+
+
+
+}
+
+#endif
diff --git a/src/gui/editors/segment/TrackLabel.cpp b/src/gui/editors/segment/TrackLabel.cpp
new file mode 100644
index 0000000..90561d1
--- /dev/null
+++ b/src/gui/editors/segment/TrackLabel.cpp
@@ -0,0 +1,203 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
+
+/*
+ Rosegarden
+ A MIDI and audio sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "TrackLabel.h"
+
+#include <klocale.h>
+#include "base/Track.h"
+#include <klineeditdlg.h>
+#include <qfont.h>
+#include <qframe.h>
+#include <qlabel.h>
+#include <qregexp.h>
+#include <qstring.h>
+#include <qtimer.h>
+#include <qtooltip.h>
+#include <qwidget.h>
+#include <qwidgetstack.h>
+#include <qvalidator.h>
+
+
+namespace Rosegarden
+{
+
+TrackLabel::TrackLabel(TrackId id,
+ int position,
+ QWidget *parent,
+ const char *name):
+ QWidgetStack(parent, name),
+ m_instrumentLabel(new QLabel(this)),
+ m_trackLabel(new QLabel(this)),
+ m_id(id),
+ m_position(position)
+{
+ QFont font;
+ font.setPointSize(font.pointSize() * 95 / 100);
+ if (font.pixelSize() > 14)
+ font.setPixelSize(14);
+ font.setBold(false);
+ m_instrumentLabel->setFont(font);
+ m_trackLabel->setFont(font);
+
+ addWidget(m_instrumentLabel, ShowInstrument);
+ addWidget(m_trackLabel, ShowTrack);
+ raiseWidget(ShowTrack);
+
+ m_instrumentLabel->setFrameShape(QFrame::NoFrame);
+ m_trackLabel->setFrameShape(QFrame::NoFrame);
+
+ m_pressTimer = new QTimer(this);
+
+ connect(m_pressTimer, SIGNAL(timeout()),
+ this, SIGNAL(changeToInstrumentList()));
+
+ QToolTip::add
+ (this, i18n("Click and hold with left mouse button to assign this Track to an Instrument."));
+
+}
+
+TrackLabel::~TrackLabel()
+{}
+
+void TrackLabel::setIndent(int i)
+{
+ m_instrumentLabel->setIndent(i);
+ m_trackLabel->setIndent(i);
+}
+
+void TrackLabel::setAlternativeLabel(const QString &label)
+{
+ // recover saved original
+ if (label.isEmpty()) {
+
+ if (!m_alternativeLabel.isEmpty())
+ m_instrumentLabel->setText(m_alternativeLabel);
+
+ // do nothing if we've got nothing to swap
+ return ;
+ }
+
+ // Store the current (first) label
+ //
+ if (m_alternativeLabel.isEmpty())
+ m_alternativeLabel = m_instrumentLabel->text();
+
+ // set new label
+ m_instrumentLabel->setText(label);
+}
+
+void TrackLabel::clearAlternativeLabel()
+{
+ m_alternativeLabel = "";
+}
+
+void TrackLabel::showLabel(InstrumentTrackLabels l)
+{
+ raiseWidget(l);
+}
+
+void
+TrackLabel::setSelected(bool on)
+{
+ if (on) {
+ m_selected = true;
+
+ m_instrumentLabel->setPaletteBackgroundColor(colorGroup().highlight());
+ m_instrumentLabel->setPaletteForegroundColor(colorGroup().highlightedText());
+ m_trackLabel->setPaletteBackgroundColor(colorGroup().highlight());
+ m_trackLabel->setPaletteForegroundColor(colorGroup().highlightedText());
+
+ } else {
+ m_selected = false;
+
+ m_instrumentLabel->setPaletteBackgroundColor(colorGroup().background());
+ m_trackLabel->setPaletteBackgroundColor(colorGroup().background());
+ m_instrumentLabel->setPaletteForegroundColor(colorGroup().text());
+ m_trackLabel->setPaletteForegroundColor(colorGroup().text());
+ }
+ if (visibleWidget())
+ visibleWidget()->update();
+}
+
+void
+TrackLabel::mousePressEvent(QMouseEvent *e)
+{
+ if (e->button() == RightButton) {
+
+ emit clicked();
+ emit changeToInstrumentList();
+
+ } else if (e->button() == LeftButton) {
+
+ // start a timer on this hold
+ m_pressTimer->start(200, true); // 200ms, single shot
+ }
+}
+
+void
+TrackLabel::mouseReleaseEvent(QMouseEvent *e)
+{
+ // stop the timer if running
+ if (m_pressTimer->isActive())
+ m_pressTimer->stop();
+
+ if (e->button() == LeftButton) {
+ emit clicked();
+ }
+}
+
+void
+TrackLabel::mouseDoubleClickEvent(QMouseEvent *e)
+{
+ if (e->button() != LeftButton)
+ return ;
+
+ // Highlight this label alone and cheat using
+ // the clicked signal
+ //
+ emit clicked();
+
+ // Just in case we've got our timing wrong - reapply
+ // this label highlight
+ //
+ setSelected(true);
+
+ bool ok = false;
+
+ QRegExpValidator validator(QRegExp(".*"), this); // empty is OK
+
+ QString newText = KLineEditDlg::getText(i18n("Change track name"),
+ i18n("Enter new track name"),
+ m_trackLabel->text(),
+ &ok,
+ this,
+ &validator);
+
+ if ( ok )
+ emit renameTrack(newText, m_id);
+}
+
+}
+#include "TrackLabel.moc"
diff --git a/src/gui/editors/segment/TrackLabel.h b/src/gui/editors/segment/TrackLabel.h
new file mode 100644
index 0000000..e56d0e5
--- /dev/null
+++ b/src/gui/editors/segment/TrackLabel.h
@@ -0,0 +1,122 @@
+
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
+
+/*
+ Rosegarden
+ A MIDI and audio sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_TRACKLABEL_H_
+#define _RG_TRACKLABEL_H_
+
+#include "base/Track.h"
+#include <qstring.h>
+#include <qwidgetstack.h>
+
+
+class QWidget;
+class QTimer;
+class QMouseEvent;
+class QLabel;
+
+
+namespace Rosegarden
+{
+
+
+
+/**
+ * Specialises QLabel to create in effect a toggleable and hence
+ * selectable label/label list. In conjunction with TrackButtons
+ * provides a framework for Track selection on the TrackCanvas.
+ */
+class TrackLabel : public QWidgetStack
+{
+Q_OBJECT
+public:
+
+ enum InstrumentTrackLabels
+ {
+ ShowTrack,
+ ShowInstrument,
+ ShowBoth
+ };
+
+ TrackLabel(TrackId id,
+ int position,
+ QWidget *parent,
+ const char *name=0);
+
+ ~TrackLabel();
+
+ // QLabel API delegation - applies on both labels
+ void setIndent(int);
+
+ QLabel* getInstrumentLabel() { return m_instrumentLabel; }
+ QLabel* getTrackLabel() { return m_trackLabel; }
+ void setAlternativeLabel(const QString &label);
+ void clearAlternativeLabel();
+ void showLabel(InstrumentTrackLabels);
+
+ // Encapsulates setting the label to highlighted or not
+ //
+ void setSelected(bool on);
+ bool isSelected() const { return m_selected; }
+
+ void setId(TrackId id) { m_id = id; }
+ TrackId getId() const { return m_id; }
+
+ int getPosition() const { return m_position; }
+ void setPosition(int position) { m_position = position; }
+
+signals:
+ void clicked();
+
+ // We emit this once we've renamed a track
+ //
+ void renameTrack(QString, TrackId);
+
+ void changeToInstrumentList();
+
+protected:
+
+ virtual void mousePressEvent(QMouseEvent *e);
+ virtual void mouseReleaseEvent(QMouseEvent *e);
+ virtual void mouseDoubleClickEvent(QMouseEvent *e);
+
+ QLabel* getVisibleLabel();
+
+ //--------------- Data members ---------------------------------
+
+ QLabel *m_instrumentLabel;
+ QLabel *m_trackLabel;
+ QString m_alternativeLabel;
+
+ TrackId m_id;
+ int m_position;
+ bool m_selected;
+
+ QTimer *m_pressTimer;
+};
+
+
+}
+
+#endif
diff --git a/src/gui/editors/segment/TrackVUMeter.cpp b/src/gui/editors/segment/TrackVUMeter.cpp
new file mode 100644
index 0000000..a638ee7
--- /dev/null
+++ b/src/gui/editors/segment/TrackVUMeter.cpp
@@ -0,0 +1,77 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
+
+/*
+ Rosegarden
+ A MIDI and audio sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "TrackVUMeter.h"
+
+#include "gui/widgets/VUMeter.h"
+#include <qfont.h>
+#include <qstring.h>
+#include <qwidget.h>
+
+
+namespace Rosegarden
+{
+
+TrackVUMeter::TrackVUMeter(QWidget *parent,
+ VUMeterType type,
+ int width,
+ int height,
+ int position,
+ const char *name):
+ VUMeter(parent, type, false, false, width, height, VUMeter::Horizontal, name),
+ m_position(position), m_textHeight(12)
+{
+ setAlignment(AlignCenter);
+
+ QFont font;
+ font.setPointSize(font.pointSize() * 95 / 100);
+ if (font.pointSize() > 14)
+ font.setPointSize(14);
+ font.setBold(false);
+ setFont(font);
+}
+
+void
+TrackVUMeter::meterStart()
+{
+ clear();
+ setMinimumHeight(m_originalHeight);
+ setMaximumHeight(m_originalHeight);
+ m_active = true;
+}
+
+void
+TrackVUMeter::meterStop()
+{
+ setMinimumHeight(m_textHeight);
+ setMaximumHeight(m_textHeight);
+ setText(QString("%1").arg(m_position + 1));
+ if (m_active) {
+ m_active = false;
+ update();
+ }
+}
+
+}
diff --git a/src/gui/editors/segment/TrackVUMeter.h b/src/gui/editors/segment/TrackVUMeter.h
new file mode 100644
index 0000000..26b8e4e
--- /dev/null
+++ b/src/gui/editors/segment/TrackVUMeter.h
@@ -0,0 +1,65 @@
+
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
+
+/*
+ Rosegarden
+ A MIDI and audio sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#include "TriggerManagerItem.h"
+
+namespace Rosegarden {
+
+int
+TriggerManagerItem::compare(QListViewItem * i, int col, bool ascending) const
+{
+ TriggerManagerItem *ei =
+ dynamic_cast<TriggerManagerItem *>(i);
+
+ if (!ei) return QListViewItem::compare(i, col, ascending);
+
+ // col 0 -> index -- numeric compare
+ // col 1 -> ID -- numeric compare
+ // col 2 -> label -- default string compare
+ // col 3 -> duration -- raw duration compare
+ // col 4 -> base pitch -- pitch compare
+ // col 5 -> base velocity -- numeric compare
+ // col 6 -> usage count -- numeric compare
+ //
+ if (col == 2) {
+ return QListViewItem::compare(i, col, ascending);
+ } else if (col == 3) {
+ if (m_rawDuration < ei->getRawDuration()) return -1;
+ else if (ei->getRawDuration() < m_rawDuration) return 1;
+ else return 0;
+ } else if (col == 4) {
+ if (m_pitch < ei->getPitch()) return -1;
+ else if (ei->getPitch() < m_pitch) return 1;
+ else return 0;
+ } else {
+ return key(col, ascending).toInt() - i->key(col, ascending).toInt();
+ }
+}
+
+}
diff --git a/src/gui/editors/segment/TriggerManagerItem.h b/src/gui/editors/segment/TriggerManagerItem.h
new file mode 100644
index 0000000..c1eb95a
--- /dev/null
+++ b/src/gui/editors/segment/TriggerManagerItem.h
@@ -0,0 +1,72 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
+
+/*
+ Rosegarden
+ A MIDI and audio sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_TRIGGERMANAGERITEM_H_
+#define _RG_TRIGGERMANAGERITEM_H_
+
+#include <klistview.h>
+
+#include "base/Event.h"
+#include "base/TriggerSegment.h"
+
+namespace Rosegarden {
+
+class TriggerManagerItem : public QListViewItem
+{
+public:
+ TriggerManagerItem(QListView * parent, QString label1,
+ QString label2 = QString::null,
+ QString label3 = QString::null,
+ QString label4 = QString::null,
+ QString label5 = QString::null,
+ QString label6 = QString::null,
+ QString label7 = QString::null,
+ QString label8 = QString::null):
+ QListViewItem(parent, label1, label2, label3, label4,
+ label5, label6, label7, label8) { ; }
+
+ virtual int compare(QListViewItem * i, int col, bool ascending) const;
+
+ void setRawDuration(timeT raw) { m_rawDuration = raw; }
+ timeT getRawDuration() const { return m_rawDuration; }
+
+ void setId(TriggerSegmentId id) { m_id = id; }
+ TriggerSegmentId getId() const { return m_id; }
+
+ void setUsage(int usage) { m_usage = usage; }
+ int getUsage() const { return m_usage; }
+
+ void setPitch(int pitch) { m_pitch = pitch; }
+ int getPitch() const { return m_pitch; }
+
+protected:
+ timeT m_rawDuration;
+ TriggerSegmentId m_id;
+ int m_usage;
+ int m_pitch;
+};
+
+}
+
+#endif
diff --git a/src/gui/editors/segment/TriggerSegmentManager.cpp b/src/gui/editors/segment/TriggerSegmentManager.cpp
new file mode 100644
index 0000000..3fb1732
--- /dev/null
+++ b/src/gui/editors/segment/TriggerSegmentManager.cpp
@@ -0,0 +1,576 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
+
+/*
+ Rosegarden
+ A MIDI and audio sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "TriggerSegmentManager.h"
+#include "TriggerManagerItem.h"
+#include <qlayout.h>
+#include <kapplication.h>
+
+#include "base/BaseProperties.h"
+#include <klocale.h>
+#include <kstddirs.h>
+#include "misc/Debug.h"
+#include "misc/Strings.h"
+#include "base/Clipboard.h"
+#include "base/Composition.h"
+#include "base/CompositionTimeSliceAdapter.h"
+#include "base/RealTime.h"
+#include "base/Segment.h"
+#include "base/TriggerSegment.h"
+#include "commands/segment/AddTriggerSegmentCommand.h"
+#include "commands/segment/DeleteTriggerSegmentCommand.h"
+#include "commands/segment/PasteToTriggerSegmentCommand.h"
+#include "document/MultiViewCommandHistory.h"
+#include "document/RosegardenGUIDoc.h"
+#include "document/ConfigGroups.h"
+#include "gui/dialogs/TimeDialog.h"
+#include "gui/general/MidiPitchLabel.h"
+#include <kaction.h>
+#include <kcommand.h>
+#include <kglobal.h>
+#include <klistview.h>
+#include <kmainwindow.h>
+#include <kmessagebox.h>
+#include <kstdaccel.h>
+#include <kstdaction.h>
+#include <kconfig.h>
+#include <qaccel.h>
+#include <qdialog.h>
+#include <qframe.h>
+#include <qiconset.h>
+#include <qlistview.h>
+#include <qpushbutton.h>
+#include <qsizepolicy.h>
+#include <qstring.h>
+#include <qtooltip.h>
+#include <qvbox.h>
+#include <qwidget.h>
+#include <qcanvas.h>
+
+
+namespace Rosegarden
+{
+
+TriggerSegmentManager::TriggerSegmentManager(QWidget *parent,
+ RosegardenGUIDoc *doc):
+ KMainWindow(parent, "triggereditordialog"),
+ m_doc(doc),
+ m_modified(false)
+{
+ QVBox* mainFrame = new QVBox(this);
+ setCentralWidget(mainFrame);
+
+ setCaption(i18n("Manage Triggered Segments"));
+
+ m_listView = new KListView(mainFrame);
+ m_listView->addColumn("Index");
+ m_listView->addColumn(i18n("ID"));
+ m_listView->addColumn(i18n("Label"));
+ m_listView->addColumn(i18n("Duration"));
+ m_listView->addColumn(i18n("Base pitch"));
+ m_listView->addColumn(i18n("Base velocity"));
+ m_listView->addColumn(i18n("Triggers"));
+
+ // Align centrally
+ for (int i = 0; i < 2; ++i)
+ m_listView->setColumnAlignment(i, Qt::AlignHCenter);
+
+ QFrame* btnBox = new QFrame(mainFrame);
+
+ btnBox->setSizePolicy(
+ QSizePolicy(QSizePolicy::Minimum, QSizePolicy::Fixed));
+
+ QHBoxLayout* layout = new QHBoxLayout(btnBox, 4, 10);
+
+ m_addButton = new QPushButton(i18n("Add"), btnBox);
+ m_deleteButton = new QPushButton(i18n("Delete"), btnBox);
+ m_deleteAllButton = new QPushButton(i18n("Delete All"), btnBox);
+
+ m_closeButton = new QPushButton(i18n("Close"), btnBox);
+
+ QToolTip::add
+ (m_addButton,
+ i18n("Add a Triggered Segment"));
+
+ QToolTip::add
+ (m_deleteButton,
+ i18n("Delete a Triggered Segment"));
+
+ QToolTip::add
+ (m_deleteAllButton,
+ i18n("Delete All Triggered Segments"));
+
+ QToolTip::add
+ (m_closeButton,
+ i18n("Close the Triggered Segment Manager"));
+
+ layout->addStretch(10);
+ layout->addWidget(m_addButton);
+ layout->addWidget(m_deleteButton);
+ layout->addWidget(m_deleteAllButton);
+ layout->addSpacing(30);
+
+ layout->addWidget(m_closeButton);
+ layout->addSpacing(5);
+
+ connect(m_addButton, SIGNAL(released()),
+ SLOT(slotAdd()));
+
+ connect(m_deleteButton, SIGNAL(released()),
+ SLOT(slotDelete()));
+
+ connect(m_closeButton, SIGNAL(released()),
+ SLOT(slotClose()));
+
+ connect(m_deleteAllButton, SIGNAL(released()),
+ SLOT(slotDeleteAll()));
+
+ setupActions();
+
+ m_doc->getCommandHistory()->attachView(actionCollection());
+ connect(m_doc->getCommandHistory(), SIGNAL(commandExecuted()),
+ this, SLOT(slotUpdate()));
+
+ connect(m_listView, SIGNAL(doubleClicked(QListViewItem *)),
+ SLOT(slotEdit(QListViewItem *)));
+
+ connect(m_listView, SIGNAL(pressed(QListViewItem *)),
+ this, SLOT(slotItemClicked(QListViewItem *)));
+
+ // Highlight all columns - enable extended selection mode
+ //
+ m_listView->setAllColumnsShowFocus(true);
+ m_listView->setSelectionMode(QListView::Extended);
+ m_listView->setItemsRenameable(true);
+
+ initDialog();
+
+ setAutoSaveSettings(TriggerManagerConfigGroup, true);
+
+ m_accelerators = new QAccel(this);
+}
+
+TriggerSegmentManager::~TriggerSegmentManager()
+{
+ RG_DEBUG << "TriggerSegmentManager::~TriggerSegmentManager" << endl;
+
+ m_listView->saveLayout(kapp->config(), TriggerManagerConfigGroup);
+
+ if (m_doc)
+ m_doc->getCommandHistory()->detachView(actionCollection());
+}
+
+void
+TriggerSegmentManager::initDialog()
+{
+ RG_DEBUG << "TriggerSegmentManager::initDialog" << endl;
+ slotUpdate();
+}
+
+void
+TriggerSegmentManager::slotUpdate()
+{
+ RG_DEBUG << "TriggerSegmentManager::slotUpdate" << endl;
+
+ TriggerManagerItem *item;
+
+ m_listView->clear();
+
+ Composition &comp = m_doc->getComposition();
+
+ const Composition::triggersegmentcontainer &triggers =
+ comp.getTriggerSegments();
+
+ Composition::triggersegmentcontainerconstiterator it;
+
+ kapp->config()->setGroup(TriggerManagerConfigGroup);
+ int timeMode = kapp->config()->readNumEntry("timemode", 0);
+
+ int i = 0;
+
+ for (it = triggers.begin(); it != triggers.end(); ++it) {
+
+ // duration is as of first usage, or 0
+
+ int uses = 0;
+ timeT first = 0;
+ std::set
+ <int> tracks;
+
+ CompositionTimeSliceAdapter tsa(&m_doc->getComposition());
+ for (CompositionTimeSliceAdapter::iterator ci = tsa.begin();
+ ci != tsa.end(); ++ci) {
+ if ((*ci)->has(BaseProperties::TRIGGER_SEGMENT_ID) &&
+ (*ci)->get
+ <Int>(BaseProperties::TRIGGER_SEGMENT_ID) == (long)(*it)->getId()) {
+ ++uses;
+ if (tracks.empty()) {
+ first = (*ci)->getAbsoluteTime();
+ }
+ tracks.insert(ci.getTrack());
+ }
+ }
+
+ timeT duration =
+ (*it)->getSegment()->getEndMarkerTime() -
+ (*it)->getSegment()->getStartTime();
+
+ QString timeString = makeDurationString
+ (first, duration, timeMode);
+
+ QString label = strtoqstr((*it)->getSegment()->getLabel());
+ if (label == "")
+ label = i18n("<no label>");
+
+ QString used = i18n("%1 on 1 track",
+ "%1 on %n tracks",
+ tracks.size()).arg(uses);
+
+ QString pitch = QString("%1 (%2)")
+ .arg(MidiPitchLabel((*it)->getBasePitch()).getQString())
+ .arg((*it)->getBasePitch());
+
+ QString velocity = QString("%1").arg((*it)->getBaseVelocity());
+
+ item = new TriggerManagerItem
+ (m_listView, QString("%1").arg(i + 1), QString("%1").arg((*it)->getId()),
+ label, timeString, pitch, velocity, used);
+
+ item->setRawDuration(duration);
+ item->setId((*it)->getId());
+ item->setUsage(uses);
+ item->setPitch((*it)->getBasePitch());
+
+ m_listView->insertItem(item);
+ ++i;
+ }
+
+ if (m_listView->childCount() == 0) {
+ QListViewItem *item =
+ new TriggerManagerItem(m_listView, i18n("<none>"));
+ m_listView->insertItem(item);
+
+ m_listView->setSelectionMode(QListView::NoSelection);
+ } else {
+ m_listView->setSelectionMode(QListView::Extended);
+ }
+}
+
+void
+TriggerSegmentManager::slotDeleteAll()
+{
+ if (KMessageBox::warningContinueCancel(this, i18n("This will remove all triggered segments from the whole composition. Are you sure?")) != KMessageBox::Continue)
+ return ;
+
+ RG_DEBUG << "TriggerSegmentManager::slotDeleteAll" << endl;
+ KMacroCommand *command = new KMacroCommand(i18n("Remove all triggered segments"));
+
+ QListViewItem *it = m_listView->firstChild();
+
+ do {
+
+ TriggerManagerItem *item =
+ dynamic_cast<TriggerManagerItem*>(it);
+
+ if (!item)
+ continue;
+
+ DeleteTriggerSegmentCommand *c =
+ new DeleteTriggerSegmentCommand(m_doc,
+ item->getId());
+ command->addCommand(c);
+
+ } while ((it = it->nextSibling()));
+
+ addCommandToHistory(command);
+}
+
+void
+TriggerSegmentManager::slotAdd()
+{
+ TimeDialog dialog(this, i18n("Trigger Segment Duration"),
+ &m_doc->getComposition(),
+ 0, 3840, false);
+
+ if (dialog.exec() == QDialog::Accepted) {
+ addCommandToHistory(new AddTriggerSegmentCommand
+ (m_doc, dialog.getTime(), 64));
+ }
+}
+
+void
+TriggerSegmentManager::slotDelete()
+{
+ RG_DEBUG << "TriggerSegmentManager::slotDelete" << endl;
+
+ TriggerManagerItem *item =
+ dynamic_cast<TriggerManagerItem*>(m_listView->currentItem());
+
+ if (!item)
+ return ;
+
+ if (item->getUsage() > 0) {
+ if (KMessageBox::warningContinueCancel(this, i18n("This triggered segment is used 1 time in the current composition. Are you sure you want to remove it?",
+ "This triggered segment is used %n times in the current composition. Are you sure you want to remove it?", item->getUsage())) != KMessageBox::Continue)
+ return ;
+ }
+
+ DeleteTriggerSegmentCommand *command =
+ new DeleteTriggerSegmentCommand(m_doc, item->getId());
+
+ addCommandToHistory(command);
+}
+
+void
+TriggerSegmentManager::slotPasteAsNew()
+{
+ Clipboard *clipboard = m_doc->getClipboard();
+
+ if (clipboard->isEmpty()) {
+ KMessageBox::information(this, i18n("Clipboard is empty"));
+ return ;
+ }
+
+ addCommandToHistory(new PasteToTriggerSegmentCommand
+ (&m_doc->getComposition(),
+ clipboard,
+ "",
+ -1));
+}
+
+void
+TriggerSegmentManager::slotClose()
+{
+ RG_DEBUG << "TriggerSegmentManager::slotClose" << endl;
+
+ if (m_doc)
+ m_doc->getCommandHistory()->detachView(actionCollection());
+ m_doc = 0;
+
+ close();
+}
+
+void
+TriggerSegmentManager::setupActions()
+{
+ KAction* close = KStdAction::close(this,
+ SLOT(slotClose()),
+ actionCollection());
+
+ m_closeButton->setText(close->text());
+ connect(m_closeButton, SIGNAL(released()), this, SLOT(slotClose()));
+
+ QString pixmapDir = KGlobal::dirs()->findResource("appdata", "pixmaps/");
+
+ // some adjustments
+ new KToolBarPopupAction(i18n("Und&o"),
+ "undo",
+ KStdAccel::key(KStdAccel::Undo),
+ actionCollection(),
+ KStdAction::stdName(KStdAction::Undo));
+
+ new KToolBarPopupAction(i18n("Re&do"),
+ "redo",
+ KStdAccel::key(KStdAccel::Redo),
+ actionCollection(),
+ KStdAction::stdName(KStdAction::Redo));
+
+ new KAction(i18n("Pa&ste as New Triggered Segment"), CTRL + SHIFT + Key_V, this,
+ SLOT(slotPasteAsNew()), actionCollection(),
+ "paste_to_trigger_segment");
+
+ kapp->config()->setGroup(TriggerManagerConfigGroup);
+ int timeMode = kapp->config()->readNumEntry("timemode", 0);
+
+ KRadioAction *action;
+
+ QCanvasPixmap pixmap(pixmapDir + "/toolbar/time-musical.png");
+ QIconSet icon(pixmap);
+
+ action = new KRadioAction(i18n("&Musical Times"), icon, 0, this,
+ SLOT(slotMusicalTime()),
+ actionCollection(), "time_musical");
+ action->setExclusiveGroup("timeMode");
+ if (timeMode == 0)
+ action->setChecked(true);
+
+ pixmap.load(pixmapDir + "/toolbar/time-real.png");
+ icon = QIconSet(pixmap);
+
+ action = new KRadioAction(i18n("&Real Times"), icon, 0, this,
+ SLOT(slotRealTime()),
+ actionCollection(), "time_real");
+ action->setExclusiveGroup("timeMode");
+ if (timeMode == 1)
+ action->setChecked(true);
+
+ pixmap.load(pixmapDir + "/toolbar/time-raw.png");
+ icon = QIconSet(pixmap);
+
+ action = new KRadioAction(i18n("Ra&w Times"), icon, 0, this,
+ SLOT(slotRawTime()),
+ actionCollection(), "time_raw");
+ action->setExclusiveGroup("timeMode");
+ if (timeMode == 2)
+ action->setChecked(true);
+
+ createGUI("triggermanager.rc");
+}
+
+void
+TriggerSegmentManager::addCommandToHistory(KCommand *command)
+{
+ getCommandHistory()->addCommand(command);
+ setModified(false);
+}
+
+MultiViewCommandHistory*
+TriggerSegmentManager::getCommandHistory()
+{
+ return m_doc->getCommandHistory();
+}
+
+void
+TriggerSegmentManager::setModified(bool modified)
+{
+ RG_DEBUG << "TriggerSegmentManager::setModified(" << modified << ")" << endl;
+
+ m_modified = modified;
+}
+
+void
+TriggerSegmentManager::checkModified()
+{
+ RG_DEBUG << "TriggerSegmentManager::checkModified(" << m_modified << ")"
+ << endl;
+
+}
+
+void
+TriggerSegmentManager::slotEdit(QListViewItem *i)
+{
+ RG_DEBUG << "TriggerSegmentManager::slotEdit" << endl;
+
+ TriggerManagerItem *item =
+ dynamic_cast<TriggerManagerItem*>(i);
+
+ if (!item)
+ return ;
+
+ TriggerSegmentId id = item->getId();
+
+ RG_DEBUG << "id is " << id << endl;
+
+ emit editTriggerSegment(id);
+}
+
+void
+TriggerSegmentManager::closeEvent(QCloseEvent *e)
+{
+ emit closing();
+ KMainWindow::closeEvent(e);
+}
+
+void
+TriggerSegmentManager::setDocument(RosegardenGUIDoc *doc)
+{
+ // reset our pointers
+ m_doc = doc;
+ m_modified = false;
+
+ slotUpdate();
+}
+
+void
+TriggerSegmentManager::slotItemClicked(QListViewItem *item)
+{
+ RG_DEBUG << "TriggerSegmentManager::slotItemClicked" << endl;
+}
+
+QString
+TriggerSegmentManager::makeDurationString(timeT time,
+ timeT duration, int timeMode)
+{
+ //!!! duplication with EventView::makeDurationString -- merge somewhere?
+
+ switch (timeMode) {
+
+ case 0: // musical time
+ {
+ int bar, beat, fraction, remainder;
+ m_doc->getComposition().getMusicalTimeForDuration
+ (time, duration, bar, beat, fraction, remainder);
+ return QString("%1%2%3-%4%5-%6%7-%8%9 ")
+ .arg(bar / 100)
+ .arg((bar % 100) / 10)
+ .arg(bar % 10)
+ .arg(beat / 10)
+ .arg(beat % 10)
+ .arg(fraction / 10)
+ .arg(fraction % 10)
+ .arg(remainder / 10)
+ .arg(remainder % 10);
+ }
+
+ case 1: // real time
+ {
+ RealTime rt =
+ m_doc->getComposition().getRealTimeDifference
+ (time, time + duration);
+ // return QString("%1 ").arg(rt.toString().c_str());
+ return QString("%1 ").arg(rt.toText().c_str());
+ }
+
+ default:
+ return QString("%1 ").arg(duration);
+ }
+}
+
+void
+TriggerSegmentManager::slotMusicalTime()
+{
+ kapp->config()->setGroup(TriggerManagerConfigGroup);
+ kapp->config()->writeEntry("timemode", 0);
+ slotUpdate();
+}
+
+void
+TriggerSegmentManager::slotRealTime()
+{
+ kapp->config()->setGroup(TriggerManagerConfigGroup);
+ kapp->config()->writeEntry("timemode", 1);
+ slotUpdate();
+}
+
+void
+TriggerSegmentManager::slotRawTime()
+{
+ kapp->config()->setGroup(TriggerManagerConfigGroup);
+ kapp->config()->writeEntry("timemode", 2);
+ slotUpdate();
+}
+
+}
+#include "TriggerSegmentManager.moc"
diff --git a/src/gui/editors/segment/TriggerSegmentManager.h b/src/gui/editors/segment/TriggerSegmentManager.h
new file mode 100644
index 0000000..2de6488
--- /dev/null
+++ b/src/gui/editors/segment/TriggerSegmentManager.h
@@ -0,0 +1,116 @@
+
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
+
+/*
+ Rosegarden
+ A MIDI and audio sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_TRIGGERSEGMENTMANAGER_H_
+#define _RG_TRIGGERSEGMENTMANAGER_H_
+
+#include <kmainwindow.h>
+#include <qstring.h>
+#include "base/Event.h"
+
+
+class QWidget;
+class QPushButton;
+class QListViewItem;
+class QCloseEvent;
+class QAccel;
+class KListView;
+class KCommand;
+
+
+namespace Rosegarden
+{
+
+class RosegardenGUIDoc;
+class MultiViewCommandHistory;
+
+
+class TriggerSegmentManager : public KMainWindow
+{
+ Q_OBJECT
+
+public:
+ TriggerSegmentManager(QWidget *parent,
+ RosegardenGUIDoc *doc);
+ ~TriggerSegmentManager();
+
+ void initDialog();
+
+ void addCommandToHistory(KCommand *command);
+ MultiViewCommandHistory* getCommandHistory();
+
+ void setModified(bool value);
+ void checkModified();
+
+ // reset the document
+ void setDocument(RosegardenGUIDoc *doc);
+
+ QAccel* getAccelerators() { return m_accelerators; }
+
+public slots:
+ void slotUpdate();
+
+ void slotAdd();
+ void slotDelete();
+ void slotDeleteAll();
+ void slotClose();
+ void slotEdit(QListViewItem *);
+ void slotItemClicked(QListViewItem *);
+ void slotPasteAsNew();
+
+ void slotMusicalTime();
+ void slotRealTime();
+ void slotRawTime();
+
+signals:
+ void editTriggerSegment(int);
+ void closing();
+
+protected:
+ virtual void closeEvent(QCloseEvent *);
+
+ void setupActions();
+ QString makeDurationString(timeT startTime,
+ timeT duration, int timeMode);
+
+ //--------------- Data members ---------------------------------
+ RosegardenGUIDoc *m_doc;
+
+ QPushButton *m_closeButton;
+ QPushButton *m_addButton;
+ QPushButton *m_deleteButton;
+ QPushButton *m_deleteAllButton;
+
+ KListView *m_listView;
+
+ bool m_modified;
+
+ QAccel *m_accelerators;
+};
+
+
+}
+
+#endif
diff --git a/src/gui/editors/segment/segmentcanvas/AudioPreviewPainter.cpp b/src/gui/editors/segment/segmentcanvas/AudioPreviewPainter.cpp
new file mode 100644
index 0000000..1b982dc
--- /dev/null
+++ b/src/gui/editors/segment/segmentcanvas/AudioPreviewPainter.cpp
@@ -0,0 +1,316 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
+
+/*
+ Rosegarden
+ A MIDI and audio sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#include "AudioPreviewPainter.h"
+
+#include "CompositionModelImpl.h"
+#include "CompositionColourCache.h"
+#include "base/Composition.h"
+#include "base/Track.h"
+#include "base/AudioLevel.h"
+#include "base/Studio.h"
+
+#include "misc/Debug.h"
+#include "document/ConfigGroups.h"
+
+#include <qimage.h>
+#include <qapplication.h>
+
+#include <kapp.h>
+#include <kconfig.h>
+
+namespace Rosegarden {
+
+AudioPreviewPainter::AudioPreviewPainter(CompositionModelImpl& model,
+ CompositionModel::AudioPreviewData* apData,
+ const Composition &composition,
+ const Segment* segment)
+ : m_model(model),
+ m_apData(apData),
+ m_composition(composition),
+ m_segment(segment),
+ m_rect(model.computeSegmentRect(*(segment))),
+ m_image(1, 1, 8, 4),
+ m_defaultCol(CompositionColourCache::getInstance()->SegmentAudioPreview),
+ m_height(model.grid().getYSnap()/2)
+{
+ int pixWidth = std::min(m_rect.getBaseWidth(), tileWidth());
+
+ m_image = QImage(pixWidth, m_rect.height(), 8, 4);
+ m_image.setAlphaBuffer(true);
+
+ m_penWidth = (std::max(1U, m_rect.getPen().width()) * 2);
+ m_halfRectHeight = m_model.grid().getYSnap()/2 - m_penWidth / 2 - 2;
+}
+
+int AudioPreviewPainter::tileWidth()
+{
+ static int tw = -1;
+ if (tw == -1) tw = QApplication::desktop()->width();
+ return tw;
+}
+
+void AudioPreviewPainter::paintPreviewImage()
+{
+ const std::vector<float>& values = m_apData->getValues();
+
+ if (values.size() == 0)
+ return;
+
+ float gain[2] = { 1.0, 1.0 };
+ int instrumentChannels = 2;
+ TrackId trackId = m_segment->getTrack();
+ Track *track = m_model.getComposition().getTrackById(trackId);
+ if (track) {
+ Instrument *instrument = m_model.getStudio().getInstrumentById(track->getInstrument());
+ if (instrument) {
+ float level = AudioLevel::dB_to_multiplier(instrument->getLevel());
+ float pan = instrument->getPan() - 100.0;
+ gain[0] = level * ((pan > 0.0) ? (1.0 - (pan / 100.0)) : 1.0);
+ gain[1] = level * ((pan < 0.0) ? ((pan + 100.0) / 100.0) : 1.0);
+ instrumentChannels = instrument->getAudioChannels();
+ }
+ }
+
+ bool showMinima = m_apData->showsMinima();
+ unsigned int channels = m_apData->getChannels();
+ if (channels == 0) {
+ RG_DEBUG << "AudioPreviewPainter::paintPreviewImage : problem with audio file for segment "
+ << m_segment->getLabel().c_str() << endl;
+ return;
+ }
+
+ int samplePoints = values.size() / (channels * (showMinima ? 2 : 1));
+ float h1, h2, l1 = 0, l2 = 0;
+ double sampleScaleFactor = samplePoints / double(m_rect.getBaseWidth());
+ m_sliceNb = 0;
+
+ m_image.fill(0);
+
+ int centre = m_image.height() / 2;
+
+ RG_DEBUG << "AudioPreviewPainter::paintPreviewImage width = " << m_rect.getBaseWidth() << ", height = " << m_rect.height() << ", halfRectHeight = " << m_halfRectHeight << endl;
+
+ RG_DEBUG << "AudioPreviewPainter::paintPreviewImage: channels = " << channels << ", gain left = " << gain[0] << ", right = " << gain[1] << endl;
+
+ double audioDuration = double(m_segment->getAudioEndTime().sec) +
+ double(m_segment->getAudioEndTime().nsec) / 1000000000.0;
+
+ // We need to take each pixel value and map it onto a point within
+ // the preview. We have samplePoints preview points in a known
+ // duration of audioDuration. Thus each point spans a real time
+ // of audioDuration / samplePoints. We need to convert the
+ // accumulated real time back into musical time, and map this
+ // proportionately across the segment width.
+
+ RealTime startRT =
+ m_model.getComposition().getElapsedRealTime(m_segment->getStartTime());
+ double startTime = double(startRT.sec) + double(startRT.nsec) / 1000000000.0;
+
+ RealTime endRT =
+ m_model.getComposition().getElapsedRealTime(m_segment->getEndMarkerTime());
+ double endTime = double(endRT.sec) + double(endRT.nsec) / 1000000000.0;
+
+ bool haveTempoChange = false;
+
+ int finalTempoChangeNumber =
+ m_model.getComposition().getTempoChangeNumberAt
+ (m_segment->getEndMarkerTime());
+
+ if ((finalTempoChangeNumber >= 0) &&
+
+ (finalTempoChangeNumber >
+ m_model.getComposition().getTempoChangeNumberAt
+ (m_segment->getStartTime()))) {
+
+ haveTempoChange = true;
+ }
+
+ KConfig* config = kapp->config();
+ config->setGroup(GeneralOptionsConfigGroup);
+
+ bool meterLevels = (config->readUnsignedNumEntry("audiopreviewstyle", 1)
+ == 1);
+
+ for (int i = 0; i < m_rect.getBaseWidth(); ++i) {
+
+ // i is the x coordinate within the rectangle. We need to
+ // calculate the position within the audio preview from which
+ // to draw the peak for this coordinate. It's possible there
+ // may be more than one, in which case we need to find the
+ // peak of all of them.
+
+ int position = 0;
+
+ if (haveTempoChange) {
+
+ // First find the time corresponding to this i.
+ timeT musicalTime =
+ m_model.grid().getRulerScale()->getTimeForX(m_rect.x() + i);
+ RealTime realTime =
+ m_model.getComposition().getElapsedRealTime(musicalTime);
+
+ double time = double(realTime.sec) +
+ double(realTime.nsec) / 1000000000.0;
+ double offset = time - startTime;
+
+ if (endTime > startTime) {
+ position = offset * m_rect.getBaseWidth() / (endTime - startTime);
+ position = int(channels * position);
+ }
+
+ } else {
+
+ position = int(channels * i * sampleScaleFactor);
+ }
+
+ if (position < 0) continue;
+
+ if (position >= values.size() - channels) {
+ finalizeCurrentSlice();
+ break;
+ }
+
+ if (channels == 1) {
+
+ h1 = values[position++];
+ h2 = h1;
+
+ if (showMinima) {
+ l1 = values[position++];
+ l2 = l1;
+ }
+ } else {
+
+ h1 = values[position++];
+ if (showMinima) l1 = values[position++];
+
+ h2 = values[position++];
+ if (showMinima) l2 = values[position++];
+
+ }
+
+ if (instrumentChannels == 1 && channels == 2) {
+ h1 = h2 = (h1 + h2) / 2;
+ l1 = l2 = (l1 + l2) / 2;
+ }
+
+ h1 *= gain[0];
+ h2 *= gain[1];
+
+ l1 *= gain[0];
+ l2 *= gain[1];
+
+ int width = 1;
+ int pixel;
+
+ // h1 left, h2 right
+ if (h1 >= 1.0) { h1 = 1.0; pixel = 2; }
+ else { pixel = 1; }
+
+ int h;
+
+ if (meterLevels) {
+ h = AudioLevel::multiplier_to_preview(h1, m_height);
+ } else {
+ h = h1 * m_height;
+ }
+ if (h <= 0) h = 1;
+ if (h > m_halfRectHeight) h = m_halfRectHeight;
+
+ int rectX = i % tileWidth();
+
+ for (int py = 0; py < h; ++py) {
+ m_image.setPixel(rectX, centre - py, pixel);
+ }
+
+ if (h2 >= 1.0) { h2 = 1.0; pixel = 2; }
+ else { pixel = 1; }
+
+ if (meterLevels) {
+ h = AudioLevel::multiplier_to_preview(h2, m_height);
+ } else {
+ h = h2 * m_height;
+ }
+ if (h < 0) h = 0;
+
+ for (int py = 0; py < h; ++py) {
+ m_image.setPixel(rectX, centre + py, pixel);
+ }
+
+ if (((i+1) % tileWidth()) == 0 || i == (m_rect.getBaseWidth() - 1)) {
+ finalizeCurrentSlice();
+ }
+ }
+
+/* Auto-fade not yet implemented.
+
+ if (m_segment->isAutoFading()) {
+
+ Composition &comp = m_model.getComposition();
+
+ int audioFadeInEnd = int(
+ m_model.grid().getRulerScale()->getXForTime(comp.
+ getElapsedTimeForRealTime(m_segment->getFadeInTime()) +
+ m_segment->getStartTime()) -
+ m_model.grid().getRulerScale()->getXForTime(m_segment->getStartTime()));
+
+ m_p.setPen(Qt::blue);
+ m_p.drawRect(0, m_apData->getSegmentRect().height() - 1, audioFadeInEnd, 1);
+ m_pb.drawRect(0, m_apData->getSegmentRect().height() - 1, audioFadeInEnd, 1);
+ }
+
+ m_p.end();
+ m_pb.end();
+*/
+}
+
+void AudioPreviewPainter::finalizeCurrentSlice()
+{
+// RG_DEBUG << "AudioPreviewPainter::finalizeCurrentSlice : copying pixmap to image at " << m_sliceNb * tileWidth() << endl;
+
+ // transparent background
+ m_image.setColor(0, qRgba(255, 255, 255, 0));
+
+ // foreground from computeSegmentPreviewColor
+ QColor c = m_model.computeSegmentPreviewColor(m_segment);
+ QRgb rgba = qRgba(c.red(), c.green(), c.blue(), 255);
+ m_image.setColor(1, rgba);
+
+ // red for clipping
+ m_image.setColor(2, qRgba(255, 0, 0, 255));
+
+ m_previewPixmaps.push_back(m_image.copy());
+
+ m_image.fill(0);
+
+ ++m_sliceNb;
+}
+
+PixmapArray AudioPreviewPainter::getPreviewImage()
+{
+ return m_previewPixmaps;
+}
+
+}
diff --git a/src/gui/editors/segment/segmentcanvas/AudioPreviewPainter.h b/src/gui/editors/segment/segmentcanvas/AudioPreviewPainter.h
new file mode 100644
index 0000000..b3c1cac
--- /dev/null
+++ b/src/gui/editors/segment/segmentcanvas/AudioPreviewPainter.h
@@ -0,0 +1,79 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
+
+/*
+ Rosegarden
+ A MIDI and audio sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_AUDIOPREVIEWPAINTER_H_
+#define _RG_AUDIOPREVIEWPAINTER_H_
+
+#include "CompositionModel.h"
+
+#include <qpainter.h>
+#include <qcolor.h>
+
+namespace Rosegarden {
+
+class CompositionModelImpl;
+class Composition;
+class Segment;
+class CompositionRect;
+
+class AudioPreviewPainter {
+public:
+ AudioPreviewPainter(CompositionModelImpl& model,
+ CompositionModel::AudioPreviewData* apData,
+ const Composition &composition,
+ const Segment* segment);
+
+ void paintPreviewImage();
+ PixmapArray getPreviewImage();
+ const CompositionRect& getSegmentRect() { return m_rect; }
+
+ static int tileWidth();
+
+protected:
+ void finalizeCurrentSlice();
+
+ //--------------- Data members ---------------------------------
+ CompositionModelImpl& m_model;
+ CompositionModel::AudioPreviewData* m_apData;
+ const Composition &m_composition;
+ const Segment* m_segment;
+ CompositionRect m_rect;
+
+ QImage m_image;
+ PixmapArray m_previewPixmaps;
+
+ QPainter m_p;
+ QPainter m_pb;
+ QColor m_defaultCol;
+ int m_penWidth;
+ int m_height;
+ int m_halfRectHeight;
+ int m_sliceNb;
+
+};
+
+}
+
+#endif
+
diff --git a/src/gui/editors/segment/segmentcanvas/AudioPreviewThread.cpp b/src/gui/editors/segment/segmentcanvas/AudioPreviewThread.cpp
new file mode 100644
index 0000000..ae64134
--- /dev/null
+++ b/src/gui/editors/segment/segmentcanvas/AudioPreviewThread.cpp
@@ -0,0 +1,267 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
+
+/*
+ Rosegarden
+ A MIDI and audio sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "AudioPreviewThread.h"
+
+#include "base/RealTime.h"
+#include "sound/AudioFileManager.h"
+#include "sound/PeakFileManager.h"
+#include <qapplication.h>
+#include <qevent.h>
+#include <qmutex.h>
+#include <qobject.h>
+#include <qthread.h>
+
+
+namespace Rosegarden
+{
+
+AudioPreviewThread::AudioPreviewThread(AudioFileManager *manager) :
+ m_manager(manager),
+ m_nextToken(0),
+ m_exiting(false),
+ m_emptyQueueListener(0)
+{}
+
+void
+AudioPreviewThread::run()
+{
+ bool emptyQueueSignalled = false;
+
+#ifdef DEBUG_AUDIO_PREVIEW_THREAD
+
+ std::cerr << "AudioPreviewThread::run entering\n";
+#endif
+
+ while (!m_exiting) {
+
+ if (m_queue.empty()) {
+ if (m_emptyQueueListener && !emptyQueueSignalled) {
+ QApplication::postEvent(m_emptyQueueListener,
+ new QCustomEvent(AudioPreviewQueueEmpty, 0));
+ emptyQueueSignalled = true;
+ }
+
+ usleep(300000);
+ } else {
+ process();
+ }
+ }
+
+#ifdef DEBUG_AUDIO_PREVIEW_THREAD
+ std::cerr << "AudioPreviewThread::run exiting\n";
+#endif
+}
+
+void
+AudioPreviewThread::finish()
+{
+ m_exiting = true;
+}
+
+bool
+AudioPreviewThread::process()
+{
+#ifdef DEBUG_AUDIO_PREVIEW_THREAD
+ std::cerr << "AudioPreviewThread::process()\n";
+#endif
+
+ if (!m_queue.empty()) {
+
+ int failed = 0;
+ int inQueue = 0;
+ //int count = 0;
+
+ m_mutex.lock();
+
+ // process 1st request and leave
+ inQueue = m_queue.size();
+ RequestQueue::iterator i = m_queue.begin();
+
+ // i->first is width, which we use only to provide an ordering to
+ // ensure we do smaller previews first. We don't use it here.
+
+ RequestRec &rec = i->second;
+ int token = rec.first;
+ Request req = rec.second;
+ m_mutex.unlock();
+
+ std::vector<float> results;
+
+ try {
+#ifdef DEBUG_AUDIO_PREVIEW_THREAD
+ std::cerr << "AudioPreviewThread::process() file id " << req.audioFileId << std::endl;
+#endif
+
+ // Requires thread-safe AudioFileManager::getPreview
+ results = m_manager->getPreview(req.audioFileId,
+ req.audioStartTime,
+ req.audioEndTime,
+ req.width,
+ req.showMinima);
+ } catch (AudioFileManager::BadAudioPathException e) {
+
+#ifdef DEBUG_AUDIO_PREVIEW_THREAD
+ std::cerr << "AudioPreviewThread::process: failed to update preview for audio file " << req.audioFileId << ": bad audio path: " << e.getMessage() << std::endl;
+#endif
+
+ // OK, we hope this just means we're still recording -- so
+ // leave this one in the queue
+ ++failed;
+
+ } catch (PeakFileManager::BadPeakFileException e) {
+
+#ifdef DEBUG_AUDIO_PREVIEW_THREAD
+ std::cerr << "AudioPreviewThread::process: failed to update preview for audio file " << req.audioFileId << ": bad peak file: " << e.getMessage() << std::endl;
+#endif
+
+ // As above
+ ++failed;
+ }
+
+ m_mutex.lock();
+
+ // We need to check that the token is still in the queue
+ // (i.e. hasn't been cancelled). Otherwise we shouldn't notify
+
+ bool found = false;
+ for (RequestQueue::iterator i = m_queue.begin(); i != m_queue.end(); ++i) {
+ if (i->second.first == token) {
+ found = true;
+ m_queue.erase(i);
+ break;
+ }
+ }
+
+ if (found) {
+ unsigned int channels =
+ m_manager->getAudioFile(req.audioFileId)->getChannels();
+ m_results[token] = ResultsPair(channels, results);
+ QObject *notify = req.notify;
+ QApplication::postEvent
+ (notify,
+ new QCustomEvent(AudioPreviewReady, (void *)token));
+ }
+
+ m_mutex.unlock();
+
+ if (failed > 0 && failed == inQueue) {
+#ifdef DEBUG_AUDIO_PREVIEW_THREAD
+ std::cerr << "AudioPreviewThread::process() - return true\n";
+#endif
+
+ return true; // delay and try again
+ }
+ }
+
+#ifdef DEBUG_AUDIO_PREVIEW_THREAD
+ std::cerr << "AudioPreviewThread::process() - return false\n";
+#endif
+
+ return false;
+}
+
+int
+AudioPreviewThread::requestPreview(const Request &request)
+{
+ m_mutex.lock();
+
+#ifdef DEBUG_AUDIO_PREVIEW_THREAD
+
+ std::cerr << "AudioPreviewThread::requestPreview for file id " << request.audioFileId << ", start " << request.audioStartTime << ", end " << request.audioEndTime << ", width " << request.width << ", notify " << request.notify << std::endl;
+#endif
+ /*!!!
+ for (RequestQueue::iterator i = m_queue.begin(); i != m_queue.end(); ++i) {
+ if (i->second.second.notify == request.notify) {
+ m_queue.erase(i);
+ break;
+ }
+ }
+ */
+ int token = m_nextToken;
+ m_queue.insert(RequestQueue::value_type(request.width,
+ RequestRec(token, request)));
+ ++m_nextToken;
+ m_mutex.unlock();
+
+ // if (!running()) start();
+
+#ifdef DEBUG_AUDIO_PREVIEW_THREAD
+
+ std::cerr << "AudioPreviewThread::requestPreview : thread running : " << running()
+ << " - thread finished : " << finished() << std::endl;
+
+ std::cerr << "AudioPreviewThread::requestPreview - token = " << token << std::endl;
+#endif
+
+ return token;
+}
+
+void
+AudioPreviewThread::cancelPreview(int token)
+{
+ m_mutex.lock();
+
+#ifdef DEBUG_AUDIO_PREVIEW_THREAD
+
+ std::cerr << "AudioPreviewThread::cancelPreview for token " << token << std::endl;
+#endif
+
+ for (RequestQueue::iterator i = m_queue.begin(); i != m_queue.end(); ++i) {
+ if (i->second.first == token) {
+ m_queue.erase(i);
+ break;
+ }
+ }
+
+ m_mutex.unlock();
+}
+
+void
+AudioPreviewThread::getPreview(int token, unsigned int &channels,
+ std::vector<float> &values)
+{
+ m_mutex.lock();
+
+ values.clear();
+ if (m_results.find(token) == m_results.end()) {
+ channels = 0;
+ m_mutex.unlock();
+ return ;
+ }
+
+ channels = m_results[token].first;
+ values = m_results[token].second;
+ m_results.erase(m_results.find(token));
+
+ m_mutex.unlock();
+
+ return ;
+}
+
+const QEvent::Type AudioPreviewThread::AudioPreviewReady = QEvent::Type(QEvent::User + 1);
+const QEvent::Type AudioPreviewThread::AudioPreviewQueueEmpty = QEvent::Type(QEvent::User + 2);
+
+}
diff --git a/src/gui/editors/segment/segmentcanvas/AudioPreviewThread.h b/src/gui/editors/segment/segmentcanvas/AudioPreviewThread.h
new file mode 100644
index 0000000..ae3ac81
--- /dev/null
+++ b/src/gui/editors/segment/segmentcanvas/AudioPreviewThread.h
@@ -0,0 +1,99 @@
+
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
+
+/*
+ Rosegarden
+ A MIDI and audio sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_AUDIOPREVIEWTHREAD_H_
+#define _RG_AUDIOPREVIEWTHREAD_H_
+
+#include "base/RealTime.h"
+#include <map>
+#include <qevent.h>
+#include <qmutex.h>
+#include <qthread.h>
+#include <utility>
+#include <vector>
+
+
+class QObject;
+
+
+namespace Rosegarden
+{
+
+class AudioFileManager;
+
+
+class AudioPreviewThread : public QThread
+{
+public:
+ AudioPreviewThread(AudioFileManager *manager);
+
+ virtual void run();
+ virtual void finish();
+
+ struct Request {
+ int audioFileId;
+ RealTime audioStartTime;
+ RealTime audioEndTime;
+ int width;
+ bool showMinima;
+ QObject *notify;
+ };
+
+ virtual int requestPreview(const Request &request);
+ virtual void cancelPreview(int token);
+ virtual void getPreview(int token, unsigned int &channels,
+ std::vector<float> &values);
+
+ void setEmptyQueueListener(QObject* o) { m_emptyQueueListener = o; }
+
+ static const QEvent::Type AudioPreviewReady;
+ static const QEvent::Type AudioPreviewQueueEmpty;
+
+
+protected:
+ virtual bool process();
+
+
+ AudioFileManager *m_manager;
+ int m_nextToken;
+ bool m_exiting;
+
+ QObject* m_emptyQueueListener;
+
+ typedef std::pair<int, Request> RequestRec;
+ typedef std::multimap<int, RequestRec> RequestQueue;
+ RequestQueue m_queue;
+
+ typedef std::pair<unsigned int, std::vector<float> > ResultsPair;
+ typedef std::map<int, ResultsPair> ResultsQueue;
+ ResultsQueue m_results;
+
+ QMutex m_mutex;
+};
+
+
+}
+
+#endif
diff --git a/src/gui/editors/segment/segmentcanvas/AudioPreviewUpdater.cpp b/src/gui/editors/segment/segmentcanvas/AudioPreviewUpdater.cpp
new file mode 100644
index 0000000..76497b9
--- /dev/null
+++ b/src/gui/editors/segment/segmentcanvas/AudioPreviewUpdater.cpp
@@ -0,0 +1,149 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
+
+/*
+ Rosegarden
+ A MIDI and audio sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "AudioPreviewUpdater.h"
+
+#include "misc/Debug.h"
+#include "AudioPreviewThread.h"
+#include "base/Composition.h"
+#include "base/RealTime.h"
+#include "base/Segment.h"
+#include "CompositionModelImpl.h"
+#include <qevent.h>
+#include <qobject.h>
+#include <qrect.h>
+
+
+namespace Rosegarden
+{
+
+static int apuExtantCount = 0;
+
+AudioPreviewUpdater::AudioPreviewUpdater(AudioPreviewThread &thread,
+ const Composition& c, const Segment* s,
+ const QRect& r,
+ CompositionModelImpl* parent)
+ : QObject(parent),
+ m_thread(thread),
+ m_composition(c),
+ m_segment(s),
+ m_rect(r),
+ m_showMinima(false),
+ m_channels(0),
+ m_previewToken( -1)
+{
+ ++apuExtantCount;
+ RG_DEBUG << "AudioPreviewUpdater::AudioPreviewUpdater " << this << " (now " << apuExtantCount << " extant)" << endl;
+}
+
+AudioPreviewUpdater::~AudioPreviewUpdater()
+{
+ --apuExtantCount;
+ RG_DEBUG << "AudioPreviewUpdater::~AudioPreviewUpdater on " << this << " ( token " << m_previewToken << ") (now " << apuExtantCount << " extant)" << endl;
+ if (m_previewToken >= 0)
+ m_thread.cancelPreview(m_previewToken);
+}
+
+void AudioPreviewUpdater::update()
+{
+ // Get sample start and end times and work out duration
+ //
+ RealTime audioStartTime = m_segment->getAudioStartTime();
+ RealTime audioEndTime = audioStartTime +
+ m_composition.getElapsedRealTime(m_segment->getEndMarkerTime()) -
+ m_composition.getElapsedRealTime(m_segment->getStartTime()) ;
+
+ RG_DEBUG << "AudioPreviewUpdater(" << this << ")::update() - for file id "
+ << m_segment->getAudioFileId() << " requesting values - thread running : "
+ << m_thread.running() << " - thread finished : " << m_thread.finished() << endl;
+
+ AudioPreviewThread::Request request;
+ request.audioFileId = m_segment->getAudioFileId();
+ request.audioStartTime = audioStartTime;
+ request.audioEndTime = audioEndTime;
+ request.width = m_rect.width();
+ request.showMinima = m_showMinima;
+ request.notify = this;
+ if (m_previewToken >= 0)
+ m_thread.cancelPreview(m_previewToken);
+ m_previewToken = m_thread.requestPreview(request);
+ if (!m_thread.running())
+ m_thread.start();
+}
+
+void AudioPreviewUpdater::cancel()
+{
+ if (m_previewToken >= 0)
+ m_thread.cancelPreview(m_previewToken);
+ m_previewToken = -1;
+}
+
+bool AudioPreviewUpdater::event(QEvent *e)
+{
+ RG_DEBUG << "AudioPreviewUpdater(" << this << ")::event (" << e << ")" << endl;
+
+ if (e->type() == AudioPreviewThread::AudioPreviewReady) {
+ QCustomEvent *ev = dynamic_cast<QCustomEvent *>(e);
+ if (ev) {
+ intptr_t token = (intptr_t)ev->data();
+ m_channels = 0; // to be filled as getPreview return value
+
+ RG_DEBUG << "AudioPreviewUpdater::token " << token << ", my token " << m_previewToken << endl;
+
+ if (m_previewToken >= 0 && token >= m_previewToken) {
+
+ m_previewToken = -1;
+ m_thread.getPreview(token, m_channels, m_values);
+
+ if (m_channels == 0) {
+ RG_DEBUG << "AudioPreviewUpdater: failed to find preview!\n";
+ } else {
+
+ RG_DEBUG << "AudioPreviewUpdater: got correct preview (" << m_channels
+ << " channels, " << m_values.size() << " samples)\n";
+ }
+
+ emit audioPreviewComplete(this);
+
+ } else {
+
+ // this one is out of date already
+ std::vector<float> tmp;
+ unsigned int tmpChannels;
+ m_thread.getPreview(token, tmpChannels, tmp);
+
+ RG_DEBUG << "AudioPreviewUpdater: got obsolete preview (" << tmpChannels
+ << " channels, " << tmp.size() << " samples)\n";
+ }
+
+ return true;
+ }
+ }
+
+ return QObject::event(e);
+}
+
+}
+#include "AudioPreviewUpdater.moc"
diff --git a/src/gui/editors/segment/segmentcanvas/AudioPreviewUpdater.h b/src/gui/editors/segment/segmentcanvas/AudioPreviewUpdater.h
new file mode 100644
index 0000000..ffc97c9
--- /dev/null
+++ b/src/gui/editors/segment/segmentcanvas/AudioPreviewUpdater.h
@@ -0,0 +1,90 @@
+
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
+
+/*
+ Rosegarden
+ A MIDI and audio sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_AUDIOPREVIEWUPDATER_H_
+#define _RG_AUDIOPREVIEWUPDATER_H_
+
+#include <qobject.h>
+#include <qrect.h>
+#include <vector>
+
+
+class QEvent;
+
+
+namespace Rosegarden
+{
+
+class Segment;
+class CompositionModelImpl;
+class Composition;
+class AudioPreviewThread;
+
+
+class AudioPreviewUpdater : public QObject
+{
+ Q_OBJECT
+
+public:
+ AudioPreviewUpdater(AudioPreviewThread &thread,
+ const Composition &composition,
+ const Segment *segment,
+ const QRect &displayExtent,
+ CompositionModelImpl *parent);
+ ~AudioPreviewUpdater();
+
+ void update();
+ void cancel();
+
+ QRect getDisplayExtent() const { return m_rect; }
+ void setDisplayExtent(const QRect &rect) { m_rect = rect; }
+
+ const Segment *getSegment() const { return m_segment; }
+
+ const std::vector<float> &getComputedValues(unsigned int &channels) const
+ { channels = m_channels; return m_values; }
+
+signals:
+ void audioPreviewComplete(AudioPreviewUpdater*);
+
+protected:
+ virtual bool event(QEvent*);
+
+ AudioPreviewThread& m_thread;
+
+ const Composition& m_composition;
+ const Segment* m_segment;
+ QRect m_rect;
+ bool m_showMinima;
+ unsigned int m_channels;
+ std::vector<float> m_values;
+
+ intptr_t m_previewToken;
+};
+
+
+}
+
+#endif
diff --git a/src/gui/editors/segment/segmentcanvas/CompositionColourCache.cpp b/src/gui/editors/segment/segmentcanvas/CompositionColourCache.cpp
new file mode 100644
index 0000000..b36d6e0
--- /dev/null
+++ b/src/gui/editors/segment/segmentcanvas/CompositionColourCache.cpp
@@ -0,0 +1,62 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
+
+/*
+ Rosegarden
+ A MIDI and audio sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "CompositionColourCache.h"
+
+#include "gui/general/GUIPalette.h"
+#include <qcolor.h>
+
+
+namespace Rosegarden
+{
+
+void CompositionColourCache::init()
+{
+ SegmentCanvas = GUIPalette::getColour(GUIPalette::SegmentCanvas);
+ SegmentAudioPreview = GUIPalette::getColour(GUIPalette::SegmentAudioPreview);
+ SegmentInternalPreview = GUIPalette::getColour(GUIPalette::SegmentInternalPreview);
+ SegmentLabel = GUIPalette::getColour(GUIPalette::SegmentLabel);
+ SegmentBorder = GUIPalette::getColour(GUIPalette::SegmentBorder);
+ RepeatSegmentBorder = GUIPalette::getColour(GUIPalette::RepeatSegmentBorder);
+ RecordingSegmentBorder = GUIPalette::getColour(GUIPalette::RecordingSegmentBorder);
+ RecordingAudioSegmentBlock = GUIPalette::getColour(GUIPalette::RecordingAudioSegmentBlock);
+ RecordingInternalSegmentBlock = GUIPalette::getColour(GUIPalette::RecordingInternalSegmentBlock);
+ RotaryFloatBackground = GUIPalette::getColour(GUIPalette::RotaryFloatBackground);
+ RotaryFloatForeground = GUIPalette::getColour(GUIPalette::RotaryFloatForeground);
+
+}
+
+CompositionColourCache* CompositionColourCache::getInstance()
+{
+ if (!m_instance) {
+ m_instance = new CompositionColourCache();
+ }
+
+ return m_instance;
+}
+
+CompositionColourCache* CompositionColourCache::m_instance = 0;
+
+}
diff --git a/src/gui/editors/segment/segmentcanvas/CompositionColourCache.h b/src/gui/editors/segment/segmentcanvas/CompositionColourCache.h
new file mode 100644
index 0000000..32d4719
--- /dev/null
+++ b/src/gui/editors/segment/segmentcanvas/CompositionColourCache.h
@@ -0,0 +1,69 @@
+
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
+
+/*
+ Rosegarden
+ A MIDI and audio sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_COMPOSITIONCOLOURCACHE_H_
+#define _RG_COMPOSITIONCOLOURCACHE_H_
+
+#include <qcolor.h>
+
+
+
+
+namespace Rosegarden
+{
+
+
+
+class CompositionColourCache
+{
+public:
+ static CompositionColourCache* getInstance();
+
+ void init();
+
+ QColor SegmentCanvas;
+ QColor SegmentAudioPreview;
+ QColor SegmentInternalPreview;
+ QColor SegmentLabel;
+ QColor SegmentBorder;
+ QColor RepeatSegmentBorder;
+ QColor RecordingSegmentBorder;
+ QColor RecordingAudioSegmentBlock;
+ QColor RecordingInternalSegmentBlock;
+ QColor Pointer;
+ QColor MovementGuide;
+ QColor RotaryFloatBackground;
+ QColor RotaryFloatForeground;
+
+protected:
+ CompositionColourCache() { init(); }
+ static CompositionColourCache* m_instance;
+};
+
+
+
+}
+
+#endif
diff --git a/src/gui/editors/segment/segmentcanvas/CompositionItem.cpp b/src/gui/editors/segment/segmentcanvas/CompositionItem.cpp
new file mode 100644
index 0000000..798178a
--- /dev/null
+++ b/src/gui/editors/segment/segmentcanvas/CompositionItem.cpp
@@ -0,0 +1,34 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
+
+/*
+ Rosegarden
+ A MIDI and audio sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "CompositionItem.h"
+
+#include <qobject.h>
+#include <qrect.h>
+
+
+namespace Rosegarden
+{
+}
diff --git a/src/gui/editors/segment/segmentcanvas/CompositionItem.h b/src/gui/editors/segment/segmentcanvas/CompositionItem.h
new file mode 100644
index 0000000..b5e749b
--- /dev/null
+++ b/src/gui/editors/segment/segmentcanvas/CompositionItem.h
@@ -0,0 +1,67 @@
+
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
+
+/*
+ Rosegarden
+ A MIDI and audio sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_COMPOSITIONITEM_H_
+#define _RG_COMPOSITIONITEM_H_
+
+#include <qguardedptr.h>
+#include <qobject.h>
+#include <qrect.h>
+
+
+namespace Rosegarden
+{
+
+class _CompositionItem : public QObject {
+public:
+ virtual bool isRepeating() const = 0;
+ virtual QRect rect() const = 0;
+ virtual void moveBy(int x, int y) = 0;
+ virtual void moveTo(int x, int y) = 0;
+ virtual void setX(int x) = 0;
+ virtual void setY(int y) = 0;
+ virtual void setZ(unsigned int z) = 0;
+ virtual int x() = 0;
+ virtual int y() = 0;
+ virtual unsigned int z() = 0;
+ virtual void setWidth(int w) = 0;
+
+ // used by itemcontainer
+ virtual long hashKey() = 0;
+
+ QRect savedRect() const { return m_savedRect; }
+ void saveRect() const { m_savedRect = rect(); }
+
+protected:
+ mutable QRect m_savedRect;
+};
+
+typedef QGuardedPtr<_CompositionItem> CompositionItem;
+bool operator<(const CompositionItem&, const CompositionItem&);
+
+
+}
+
+#endif
diff --git a/src/gui/editors/segment/segmentcanvas/CompositionItemHelper.cpp b/src/gui/editors/segment/segmentcanvas/CompositionItemHelper.cpp
new file mode 100644
index 0000000..e1705cd
--- /dev/null
+++ b/src/gui/editors/segment/segmentcanvas/CompositionItemHelper.cpp
@@ -0,0 +1,150 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
+
+/*
+ Rosegarden
+ A MIDI and audio sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#include <cmath>
+
+#include "CompositionItemHelper.h"
+
+#include "base/Segment.h"
+#include "base/SnapGrid.h"
+#include "misc/Debug.h"
+#include "CompositionModel.h"
+#include "CompositionItemImpl.h"
+#include <qcolor.h>
+#include <qpoint.h>
+#include <qrect.h>
+
+namespace Rosegarden
+{
+
+timeT CompositionItemHelper::getStartTime(const CompositionItem& item, const Rosegarden::SnapGrid& grid)
+{
+ timeT t = 0;
+
+ if (item) {
+ // t = std::max(grid.snapX(item->rect().x()), 0L); - wrong, we can have negative start times,
+ // and if we do this we 'crop' segments when they are moved before the start of the composition
+ t = grid.snapX(item->rect().x());
+
+// RG_DEBUG << "CompositionItemHelper::getStartTime(): item is repeating : " << item->isRepeating()
+// << " - startTime = " << t
+// << endl;
+ }
+
+ return t;
+}
+
+timeT CompositionItemHelper::getEndTime(const CompositionItem& item, const Rosegarden::SnapGrid& grid)
+{
+ timeT t = 0;
+
+ if (item) {
+ QRect itemRect = item->rect();
+
+ t = std::max(grid.snapX(itemRect.x() + itemRect.width()), 0L);
+
+// RG_DEBUG << "CompositionItemHelper::getEndTime() : rect width = "
+// << itemRect.width()
+// << " - item is repeating : " << item->isRepeating()
+// << " - endTime = " << t
+// << endl;
+
+ }
+
+ return t;
+}
+
+void CompositionItemHelper::setStartTime(CompositionItem& item, timeT time,
+ const Rosegarden::SnapGrid& grid)
+{
+ if (item) {
+ int x = int(nearbyint(grid.getRulerScale()->getXForTime(time)));
+
+ RG_DEBUG << "CompositionItemHelper::setStartTime() time = " << time
+ << " -> x = " << x << endl;
+
+ int curX = item->rect().x();
+ item->setX(x);
+ if (item->isRepeating()) {
+ int deltaX = curX - x;
+ CompositionRect& sr = dynamic_cast<CompositionItemImpl*>((_CompositionItem*)item)->getCompRect();
+ int curW = sr.getBaseWidth();
+ sr.setBaseWidth(curW + deltaX);
+ }
+
+ }
+
+}
+
+void CompositionItemHelper::setEndTime(CompositionItem& item, timeT time,
+ const Rosegarden::SnapGrid& grid)
+{
+ if (item) {
+ int x = int(nearbyint(grid.getRulerScale()->getXForTime(time)));
+ QRect r = item->rect();
+ QPoint topRight = r.topRight();
+ topRight.setX(x);
+ r.setTopRight(topRight);
+ item->setWidth(r.width());
+
+ if (item->isRepeating()) {
+ CompositionRect& sr = dynamic_cast<CompositionItemImpl*>((_CompositionItem*)item)->getCompRect();
+ sr.setBaseWidth(r.width());
+ }
+ }
+}
+
+int CompositionItemHelper::getTrackPos(const CompositionItem& item, const Rosegarden::SnapGrid& grid)
+{
+ return grid.getYBin(item->rect().y());
+}
+
+Rosegarden::Segment* CompositionItemHelper::getSegment(CompositionItem item)
+{
+ return (dynamic_cast<CompositionItemImpl*>((_CompositionItem*)item))->getSegment();
+}
+
+CompositionItem CompositionItemHelper::makeCompositionItem(Rosegarden::Segment* segment)
+{
+ return CompositionItem(new CompositionItemImpl(*segment, QRect()));
+}
+
+CompositionItem CompositionItemHelper::findSiblingCompositionItem(const CompositionModel::itemcontainer& items,
+ const CompositionItem& referenceItem)
+{
+ CompositionModel::itemcontainer::const_iterator it;
+ Rosegarden::Segment* currentSegment = CompositionItemHelper::getSegment(referenceItem);
+
+ for (it = items.begin(); it != items.end(); it++) {
+ CompositionItem item = *it;
+ Rosegarden::Segment* segment = CompositionItemHelper::getSegment(item);
+ if (segment == currentSegment) {
+ return item;
+ }
+ }
+
+ return referenceItem;
+}
+
+}
diff --git a/src/gui/editors/segment/segmentcanvas/CompositionItemHelper.h b/src/gui/editors/segment/segmentcanvas/CompositionItemHelper.h
new file mode 100644
index 0000000..1b3ad95
--- /dev/null
+++ b/src/gui/editors/segment/segmentcanvas/CompositionItemHelper.h
@@ -0,0 +1,61 @@
+
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
+
+/*
+ Rosegarden
+ A MIDI and audio sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "CompositionItemImpl.h"
+
+#include "misc/Debug.h"
+#include "base/Segment.h"
+#include "CompositionRect.h"
+#include <qbrush.h>
+#include <qcolor.h>
+#include <qpen.h>
+#include <qpoint.h>
+#include <qrect.h>
+#include <qsize.h>
+#include <qstring.h>
+
+
+namespace Rosegarden
+{
+
+CompositionItemImpl::CompositionItemImpl(Segment& s, const CompositionRect& rect)
+ : m_segment(s),
+ m_rect(rect),
+ m_z(0)
+{}
+
+QRect CompositionItemImpl::rect() const
+{
+ QRect res = m_rect;
+ if (m_rect.isRepeating()) {
+ CompositionRect::repeatmarks repeatMarks = m_rect.getRepeatMarks();
+ int neww = m_rect.getBaseWidth();
+
+ // RG_DEBUG << "CompositionItemImpl::rect() - width = "
+ // << m_rect.width() << " - base w = " << neww << endl;
+ res.setWidth(neww);
+ } else {
+ // RG_DEBUG << "CompositionItemImpl::rect() m_rect not repeating\n";
+ }
+
+
+ return res;
+}
+
+}
diff --git a/src/gui/editors/segment/segmentcanvas/CompositionItemImpl.h b/src/gui/editors/segment/segmentcanvas/CompositionItemImpl.h
new file mode 100644
index 0000000..b5b3ef7
--- /dev/null
+++ b/src/gui/editors/segment/segmentcanvas/CompositionItemImpl.h
@@ -0,0 +1,74 @@
+
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
+
+/*
+ Rosegarden
+ A MIDI and audio sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_COMPOSITIONITEMIMPL_H_
+#define _RG_COMPOSITIONITEMIMPL_H_
+
+#include "CompositionRect.h"
+#include "CompositionItem.h"
+#include <qrect.h>
+
+
+
+
+namespace Rosegarden
+{
+
+class Segment;
+
+
+class CompositionItemImpl : public _CompositionItem {
+public:
+ CompositionItemImpl(Segment& s, const CompositionRect&);
+ virtual bool isRepeating() const { return m_rect.isRepeating(); }
+ virtual QRect rect() const;
+ virtual void moveBy(int x, int y) { m_rect.moveBy(x, y); }
+ virtual void moveTo(int x, int y) { m_rect.setRect(x, y, m_rect.width(), m_rect.height()); }
+ virtual void setX(int x) { m_rect.setX(x); }
+ virtual void setY(int y) { m_rect.setY(y); }
+ virtual void setZ(unsigned int z) { m_z = z; }
+ virtual int x() { return m_rect.x(); }
+ virtual int y() { return m_rect.y(); }
+ virtual unsigned int z() { return m_z; }
+ virtual void setWidth(int w) { m_rect.setWidth(w); }
+ // use segment address as hash key
+ virtual long hashKey() { return (long)getSegment(); }
+
+ Segment* getSegment() { return &m_segment; }
+ const Segment* getSegment() const { return &m_segment; }
+ CompositionRect& getCompRect() { return m_rect; }
+
+protected:
+
+ //--------------- Data members ---------------------------------
+ Segment& m_segment;
+ CompositionRect m_rect;
+ unsigned int m_z;
+};
+
+
+}
+
+#endif
diff --git a/src/gui/editors/segment/segmentcanvas/CompositionModel.cpp b/src/gui/editors/segment/segmentcanvas/CompositionModel.cpp
new file mode 100644
index 0000000..9701c8a
--- /dev/null
+++ b/src/gui/editors/segment/segmentcanvas/CompositionModel.cpp
@@ -0,0 +1,43 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
+
+/*
+ Rosegarden
+ A MIDI and audio sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_COMPOSITIONMODEL_H_
+#define _RG_COMPOSITIONMODEL_H_
+
+#include "base/Composition.h"
+#include "base/Segment.h"
+#include <set>
+#include <qcolor.h>
+#include <qobject.h>
+#include <qimage.h>
+#include <qpoint.h>
+#include <qrect.h>
+#include <utility>
+#include <vector>
+#include "base/Event.h"
+#include "CompositionRect.h"
+#include "CompositionItem.h"
+
+
+class RectRanges;
+class CompositionItem;
+class AudioPreviewDrawData;
+
+
+namespace Rosegarden
+{
+
+class SnapGrid;
+typedef std::vector<QImage> PixmapArray;
+
+
+class CompositionModel : public QObject, public CompositionObserver, public SegmentObserver
+{
+ Q_OBJECT
+public:
+
+ struct CompositionItemCompare {
+ bool operator()(const CompositionItem &c1, const CompositionItem &c2) const;
+ };
+
+ typedef std::vector<QRect> rectlist;
+ typedef std::vector<int> heightlist;
+ typedef std::vector<CompositionRect> rectcontainer;
+ typedef std::set<CompositionItem, CompositionItemCompare> itemcontainer;
+
+ struct AudioPreviewDrawDataItem {
+ AudioPreviewDrawDataItem(PixmapArray p, QPoint bp, QRect r) :
+ pixmap(p), basePoint(bp), rect(r), resizeOffset(0) {};
+ PixmapArray pixmap;
+ QPoint basePoint;
+ QRect rect;
+
+ // when showing a segment that is being resized from the
+ // beginning, this contains the offset between the current
+ // rect of the segment and the resized one
+ int resizeOffset;
+ };
+
+ typedef std::vector<AudioPreviewDrawDataItem> AudioPreviewDrawData;
+
+ struct RectRange {
+ std::pair<rectlist::iterator, rectlist::iterator> range;
+ QPoint basePoint;
+ QColor color;
+ };
+
+ typedef std::vector<RectRange> RectRanges;
+
+ class AudioPreviewData {
+ public:
+ AudioPreviewData(bool showMinima, unsigned int channels) : m_showMinima(showMinima), m_channels(channels) {};
+ // ~AudioPreviewData();
+
+ bool showsMinima() { return m_showMinima; }
+ void setShowMinima(bool s) { m_showMinima = s; }
+
+ unsigned int getChannels() { return m_channels; }
+ void setChannels(unsigned int c) { m_channels = c; }
+
+ const std::vector<float> &getValues() const { return m_values; }
+ void setValues(const std::vector<float>&v) { m_values = v; }
+
+ QRect getSegmentRect() { return m_segmentRect; }
+ void setSegmentRect(const QRect& r) { m_segmentRect = r; }
+
+ protected:
+ std::vector<float> m_values;
+ bool m_showMinima;
+ unsigned int m_channels;
+ QRect m_segmentRect;
+
+ private:
+ // no copy ctor
+ AudioPreviewData(const AudioPreviewData&);
+ };
+
+
+ virtual ~CompositionModel() {};
+
+ virtual unsigned int getNbRows() = 0;
+ virtual const rectcontainer& getRectanglesIn(const QRect& rect,
+ RectRanges* notationRects, AudioPreviewDrawData* audioRects) = 0;
+
+ virtual heightlist getTrackDividersIn(const QRect& rect) = 0;
+
+ virtual itemcontainer getItemsAt (const QPoint&) = 0;
+ virtual timeT getRepeatTimeAt (const QPoint&, const CompositionItem&) = 0;
+
+ virtual SnapGrid& grid() = 0;
+
+ virtual void setPointerPos(int xPos) = 0;
+ virtual void setSelected(const CompositionItem&, bool selected = true) = 0;
+ virtual bool isSelected(const CompositionItem&) const = 0;
+ virtual void setSelected(const itemcontainer&) = 0;
+ virtual void clearSelected() = 0;
+ virtual bool haveSelection() const = 0;
+ virtual bool haveMultipleSelection() const = 0;
+ virtual void signalSelection() = 0;
+ virtual void setSelectionRect(const QRect&) = 0;
+ virtual void finalizeSelectionRect() = 0;
+ virtual QRect getSelectionContentsRect() = 0;
+ virtual void signalContentChange() = 0;
+
+ virtual void addRecordingItem(const CompositionItem&) = 0;
+ virtual void removeRecordingItem(const CompositionItem&) = 0;
+ virtual void clearRecordingItems() = 0;
+ virtual bool haveRecordingItems() = 0;
+
+ enum ChangeType { ChangeMove, ChangeResizeFromStart, ChangeResizeFromEnd };
+
+ virtual void startChange(const CompositionItem&, ChangeType change) = 0;
+ virtual void startChangeSelection(ChangeType change) = 0;
+ virtual itemcontainer& getChangingItems() = 0;
+ virtual void endChange() = 0;
+ virtual ChangeType getChangeType() = 0;
+
+ virtual void setLength(int width) = 0;
+ virtual int getLength() = 0;
+
+signals:
+ void needContentUpdate();
+ void needContentUpdate(const QRect&);
+ void needArtifactsUpdate();
+
+protected:
+ CompositionItem* m_currentCompositionItem;
+};
+
+class AudioPreviewThread;
+class AudioPreviewUpdater;
+
+
+}
+
+#endif
diff --git a/src/gui/editors/segment/segmentcanvas/CompositionModelImpl.cpp b/src/gui/editors/segment/segmentcanvas/CompositionModelImpl.cpp
new file mode 100644
index 0000000..39deb2e
--- /dev/null
+++ b/src/gui/editors/segment/segmentcanvas/CompositionModelImpl.cpp
@@ -0,0 +1,1328 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
+
+/*
+ Rosegarden
+ A MIDI and audio sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#include <cmath>
+#include <algorithm>
+#include "CompositionModelImpl.h"
+
+#include "base/BaseProperties.h"
+#include "misc/Debug.h"
+#include "misc/Strings.h"
+#include "AudioPreviewThread.h"
+#include "AudioPreviewUpdater.h"
+#include "base/Composition.h"
+#include "base/Event.h"
+#include "base/MidiProgram.h"
+#include "base/NotationTypes.h"
+#include "base/Profiler.h"
+#include "base/RulerScale.h"
+#include "base/Segment.h"
+#include "base/Selection.h"
+#include "base/SnapGrid.h"
+#include "base/Studio.h"
+#include "base/Track.h"
+#include "CompositionItemHelper.h"
+#include "CompositionItemImpl.h"
+#include "CompositionModel.h"
+#include "CompositionRect.h"
+#include "CompositionColourCache.h"
+#include "AudioPreviewPainter.h"
+#include "gui/general/GUIPalette.h"
+#include "SegmentOrderer.h"
+#include <qbrush.h>
+#include <qcolor.h>
+#include <qpen.h>
+#include <qpoint.h>
+#include <qrect.h>
+#include <qregexp.h>
+#include <qsize.h>
+#include <qstring.h>
+
+
+
+namespace Rosegarden
+{
+
+CompositionModelImpl::CompositionModelImpl(Composition& compo,
+ Studio& studio,
+ RulerScale *rulerScale,
+ int vStep)
+ : m_composition(compo),
+ m_studio(studio),
+ m_grid(rulerScale, vStep),
+ m_pointerTimePos(0),
+ m_audioPreviewThread(0)
+{
+ m_notationPreviewDataCache.setAutoDelete(true);
+ m_audioPreviewDataCache.setAutoDelete(true);
+ m_composition.addObserver(this);
+
+ setTrackHeights();
+
+ const Composition::segmentcontainer& segments = m_composition.getSegments();
+ Composition::segmentcontainer::iterator segEnd = segments.end();
+
+ for (Composition::segmentcontainer::iterator i = segments.begin();
+ i != segEnd; ++i) {
+
+ (*i)->addObserver(this);
+ }
+}
+
+CompositionModelImpl::~CompositionModelImpl()
+{
+ RG_DEBUG << "CompositionModelImpl::~CompositionModelImpl()" << endl;
+
+ if (!isCompositionDeleted()) {
+
+ m_composition.removeObserver(this);
+
+ const Composition::segmentcontainer& segments = m_composition.getSegments();
+ Composition::segmentcontainer::iterator segEnd = segments.end();
+
+ for (Composition::segmentcontainer::iterator i = segments.begin();
+ i != segEnd; ++i) {
+
+ (*i)->removeObserver(this);
+ }
+ }
+
+ RG_DEBUG << "CompositionModelImpl::~CompositionModelImpl(): removal from Segment & Composition observers OK" << endl;
+
+ if (m_audioPreviewThread) {
+ while (!m_audioPreviewUpdaterMap.empty()) {
+ // Cause any running previews to be cancelled
+ delete m_audioPreviewUpdaterMap.begin()->second;
+ m_audioPreviewUpdaterMap.erase(m_audioPreviewUpdaterMap.begin());
+ }
+ }
+}
+
+struct RectCompare {
+ bool operator()(const QRect &r1, const QRect &r2) const {
+ return r1.x() < r2.x();
+ }
+};
+
+void CompositionModelImpl::makeNotationPreviewRects(RectRanges* npRects, QPoint basePoint,
+ const Segment* segment, const QRect& clipRect)
+{
+
+ rectlist* cachedNPData = getNotationPreviewData(segment);
+
+ if (cachedNPData->empty())
+ return ;
+
+ rectlist::iterator npEnd = cachedNPData->end();
+
+ rectlist::iterator npi = std::lower_bound(cachedNPData->begin(), npEnd, clipRect, RectCompare());
+
+ if (npi == npEnd)
+ return ;
+
+ if (npi != cachedNPData->begin())
+ --npi;
+
+ RectRange interval;
+
+ interval.range.first = npi;
+
+ int segEndX = int(nearbyint(m_grid.getRulerScale()->getXForTime(segment->getEndMarkerTime())));
+ int xLim = std::min(clipRect.topRight().x(), segEndX);
+
+ // RG_DEBUG << "CompositionModelImpl::makeNotationPreviewRects : basePoint.x : "
+ // << basePoint.x() << endl;
+
+ // move iterator forward
+ //
+ while (npi->x() < xLim && npi != npEnd)
+ ++npi;
+
+ interval.range.second = npi;
+ interval.basePoint.setX(0);
+ interval.basePoint.setY(basePoint.y());
+ interval.color = computeSegmentPreviewColor(segment);
+
+ npRects->push_back(interval);
+}
+
+void CompositionModelImpl::makeNotationPreviewRectsMovingSegment(RectRanges* npRects, QPoint basePoint,
+ const Segment* segment, const QRect& currentSR)
+{
+ CompositionRect unmovedSR = computeSegmentRect(*segment);
+
+ rectlist* cachedNPData = getNotationPreviewData(segment);
+
+ if (cachedNPData->empty())
+ return ;
+
+ rectlist::iterator npEnd = cachedNPData->end(),
+ npBegin = cachedNPData->begin();
+
+ rectlist::iterator npi;
+
+ if (getChangeType() == ChangeResizeFromStart)
+ npi = std::lower_bound(npBegin, npEnd, currentSR, RectCompare());
+ else
+ npi = std::lower_bound(npBegin, npEnd, unmovedSR, RectCompare());
+
+ if (npi == npEnd)
+ return ;
+
+ if (npi != npBegin && getChangeType() != ChangeResizeFromStart) {
+ --npi;
+ }
+
+ RectRange interval;
+
+ interval.range.first = npi;
+
+ int xLim = getChangeType() == ChangeMove ? unmovedSR.topRight().x() : currentSR.topRight().x();
+
+ // RG_DEBUG << "CompositionModelImpl::makeNotationPreviewRectsMovingSegment : basePoint.x : "
+ // << basePoint.x() << endl;
+
+ // move iterator forward
+ //
+ while (npi->x() < xLim && npi != npEnd)
+ ++npi;
+
+ interval.range.second = npi;
+ interval.basePoint.setY(basePoint.y());
+
+ if (getChangeType() == ChangeMove)
+ interval.basePoint.setX(basePoint.x() - unmovedSR.x());
+ else
+ interval.basePoint.setX(0);
+
+ interval.color = computeSegmentPreviewColor(segment);
+
+ npRects->push_back(interval);
+}
+
+void CompositionModelImpl::makeAudioPreviewRects(AudioPreviewDrawData* apRects, const Segment* segment,
+ const CompositionRect& segRect, const QRect& clipRect)
+{
+ Profiler profiler("CompositionModelImpl::makeAudioPreviewRects", true);
+ RG_DEBUG << "CompositionModelImpl::makeAudioPreviewRects - segRect = " << segRect << endl;
+
+ PixmapArray previewImage = getAudioPreviewPixmap(segment);
+
+ QPoint basePoint = segRect.topLeft();
+
+ AudioPreviewDrawDataItem previewItem(previewImage, basePoint, segRect);
+
+ if (getChangeType() == ChangeResizeFromStart) {
+ CompositionRect originalRect = computeSegmentRect(*segment);
+ previewItem.resizeOffset = segRect.x() - originalRect.x();
+ }
+
+ apRects->push_back(previewItem);
+}
+
+void CompositionModelImpl::computeRepeatMarks(CompositionItem& item)
+{
+ Segment* s = CompositionItemHelper::getSegment(item);
+ CompositionRect& sr = dynamic_cast<CompositionItemImpl*>((_CompositionItem*)item)->getCompRect();
+ computeRepeatMarks(sr, s);
+}
+
+void CompositionModelImpl::computeRepeatMarks(CompositionRect& sr, const Segment* s)
+{
+ if (s->isRepeating()) {
+
+ timeT startTime = s->getStartTime();
+ timeT endTime = s->getEndMarkerTime();
+ timeT repeatInterval = endTime - startTime;
+
+ if (repeatInterval <= 0) {
+ // std::cerr << "WARNING: CompositionModelImpl::computeRepeatMarks: Segment at " << startTime << " has repeatInterval " << repeatInterval << std::endl;
+ // std::cerr << kdBacktrace() << std::endl;
+ return ;
+ }
+
+ timeT repeatStart = endTime;
+ timeT repeatEnd = s->getRepeatEndTime();
+ sr.setWidth(int(nearbyint(m_grid.getRulerScale()->getWidthForDuration(startTime,
+ repeatEnd - startTime))));
+
+ CompositionRect::repeatmarks repeatMarks;
+
+ // RG_DEBUG << "CompositionModelImpl::computeRepeatMarks : repeatStart = "
+ // << repeatStart << " - repeatEnd = " << repeatEnd << endl;
+
+ for (timeT repeatMark = repeatStart; repeatMark < repeatEnd; repeatMark += repeatInterval) {
+ int mark = int(nearbyint(m_grid.getRulerScale()->getXForTime(repeatMark)));
+ // RG_DEBUG << "CompositionModelImpl::computeRepeatMarks : mark at " << mark << endl;
+ repeatMarks.push_back(mark);
+ }
+ sr.setRepeatMarks(repeatMarks);
+ if (repeatMarks.size() > 0)
+ sr.setBaseWidth(repeatMarks[0] - sr.x());
+ else {
+ // RG_DEBUG << "CompositionModelImpl::computeRepeatMarks : no repeat marks\n";
+ sr.setBaseWidth(sr.width());
+ }
+
+ // RG_DEBUG << "CompositionModelImpl::computeRepeatMarks : s = "
+ // << s << " base width = " << sr.getBaseWidth()
+ // << " - nb repeat marks = " << repeatMarks.size() << endl;
+
+ }
+}
+
+void CompositionModelImpl::setAudioPreviewThread(AudioPreviewThread *thread)
+{
+ // std::cerr << "\nCompositionModelImpl::setAudioPreviewThread()\n" << std::endl;
+
+ while (!m_audioPreviewUpdaterMap.empty()) {
+ // Cause any running previews to be cancelled
+ delete m_audioPreviewUpdaterMap.begin()->second;
+ m_audioPreviewUpdaterMap.erase(m_audioPreviewUpdaterMap.begin());
+ }
+
+ m_audioPreviewThread = thread;
+}
+
+void CompositionModelImpl::clearPreviewCache()
+{
+ RG_DEBUG << "CompositionModelImpl::clearPreviewCache\n";
+
+ m_notationPreviewDataCache.clear();
+ m_audioPreviewDataCache.clear();
+ m_audioSegmentPreviewMap.clear();
+
+ for (AudioPreviewUpdaterMap::iterator i = m_audioPreviewUpdaterMap.begin();
+ i != m_audioPreviewUpdaterMap.end(); ++i) {
+ i->second->cancel();
+ }
+
+ const Composition::segmentcontainer& segments = m_composition.getSegments();
+ Composition::segmentcontainer::iterator segEnd = segments.end();
+
+ for (Composition::segmentcontainer::iterator i = segments.begin();
+ i != segEnd; ++i) {
+
+ if ((*i)->getType() == Segment::Audio) {
+ // This will create the audio preview updater. The
+ // preview won't be calculated and cached until the
+ // updater completes and calls back.
+ updatePreviewCacheForAudioSegment((*i), 0);
+ }
+ }
+}
+
+void CompositionModelImpl::updatePreviewCacheForNotationSegment(const Segment* segment, rectlist* npData)
+{
+ npData->clear();
+
+ int segStartX = int(nearbyint(m_grid.getRulerScale()->getXForTime(segment->getStartTime())));
+
+ bool isPercussion = false;
+ Track *track = m_composition.getTrackById(segment->getTrack());
+ if (track) {
+ InstrumentId iid = track->getInstrument();
+ Instrument *instrument = m_studio.getInstrumentById(iid);
+ if (instrument && instrument->isPercussion()) isPercussion = true;
+ }
+
+ for (Segment::iterator i = segment->begin();
+ i != segment->end(); ++i) {
+
+ long pitch = 0;
+ if (!(*i)->isa(Note::EventType) ||
+ !(*i)->get<Int>(BaseProperties::PITCH, pitch)) {
+ continue;
+ }
+
+ timeT eventStart = (*i)->getAbsoluteTime();
+ timeT eventEnd = eventStart + (*i)->getDuration();
+ // if (eventEnd > segment->getEndMarkerTime()) {
+ // eventEnd = segment->getEndMarkerTime();
+ // }
+
+ int x = int(nearbyint(m_grid.getRulerScale()->getXForTime(eventStart)));
+ int width = int(nearbyint(m_grid.getRulerScale()->getWidthForDuration(eventStart,
+ eventEnd - eventStart)));
+
+ if (x <= segStartX) {
+ ++x;
+ if (width > 1)
+ --width;
+ }
+ if (width > 1)
+ --width;
+ if (width < 1)
+ ++width;
+
+ double y0 = 0;
+ double y1 = m_grid.getYSnap();
+ double y = y1 + ((y0 - y1) * (pitch - 16)) / 96;
+
+ int height = 2;
+
+ if (isPercussion) {
+ height = 3;
+ if (width > 2) width = 2;
+ }
+
+ if (y < y0)
+ y = y0;
+ if (y > y1 - height + 1)
+ y = y1 - height + 1;
+
+ QRect r(x, (int)y, width, height);
+
+ // RG_DEBUG << "CompositionModelImpl::updatePreviewCacheForNotationSegment() : npData = "
+ // << npData << ", preview rect = "
+ // << r << endl;
+ npData->push_back(r);
+ }
+
+}
+
+QColor CompositionModelImpl::computeSegmentPreviewColor(const Segment* segment)
+{
+ // compute the preview color so it's as visible as possible over the segment's color
+ QColor segColor = GUIPalette::convertColour(m_composition.getSegmentColourMap().getColourByIndex(segment->getColourIndex()));
+ int h, s, v;
+ segColor.hsv(&h, &s, &v);
+
+ // colors with saturation lower than value should be pastel tints, and
+ // they get a value of 0; yellow and green hues close to the dead center
+ // point for that hue were taking a value of 255 with the (s < v)
+ // formula, so I added an extra hack to force hues in those two narrow
+ // ranges toward black. Black always looks good, while white washes out
+ // badly against intense yellow, and doesn't look very good against
+ // intense green either... hacky, but this produces pleasant results against
+ // every bizarre extreme of color I could cook up to throw at it, plus
+ // (the real reason for all this convoluted fiddling, it does all that while keeping
+ // white against bright reds and blues, which looks better than black)
+ if ( ((((h > 57) && (h < 66)) || ((h > 93) && (h < 131))) && (s > 127) && (v > 127) ) ||
+ (s < v) ) {
+ v = 0;
+ } else {
+ v = 255;
+ }
+ s = 31;
+ h += 180;
+
+ segColor.setHsv(h, s, v);
+
+ return segColor;
+}
+
+void CompositionModelImpl::updatePreviewCacheForAudioSegment(const Segment* segment, AudioPreviewData* apData)
+{
+ if (m_audioPreviewThread) {
+ // std::cerr << "CompositionModelImpl::updatePreviewCacheForAudioSegment() - new audio preview started" << std::endl;
+
+ CompositionRect segRect = computeSegmentRect(*segment);
+ segRect.setWidth(segRect.getBaseWidth()); // don't use repeating area
+ segRect.moveTopLeft(QPoint(0, 0));
+
+ if (apData)
+ apData->setSegmentRect(segRect);
+
+ if (m_audioPreviewUpdaterMap.find(segment) ==
+ m_audioPreviewUpdaterMap.end()) {
+
+ AudioPreviewUpdater *updater = new AudioPreviewUpdater
+ (*m_audioPreviewThread, m_composition, segment, segRect, this);
+
+ connect(updater, SIGNAL(audioPreviewComplete(AudioPreviewUpdater*)),
+ this, SLOT(slotAudioPreviewComplete(AudioPreviewUpdater*)));
+
+ m_audioPreviewUpdaterMap[segment] = updater;
+
+ } else {
+
+ m_audioPreviewUpdaterMap[segment]->setDisplayExtent(segRect);
+ }
+
+ m_audioPreviewUpdaterMap[segment]->update();
+
+ } else {
+ RG_DEBUG << "CompositionModelImpl::updatePreviewCacheForAudioSegment() - no audio preview thread set\n";
+ }
+}
+
+void CompositionModelImpl::slotAudioPreviewComplete(AudioPreviewUpdater* apu)
+{
+ RG_DEBUG << "CompositionModelImpl::slotAudioPreviewComplete()\n";
+
+ AudioPreviewData *apData = getAudioPreviewData(apu->getSegment());
+ QRect updateRect;
+
+ if (apData) {
+ RG_DEBUG << "CompositionModelImpl::slotAudioPreviewComplete(" << apu << "): apData contains " << apData->getValues().size() << " values already" << endl;
+ unsigned int channels = 0;
+ const std::vector<float> &values = apu->getComputedValues(channels);
+ if (channels > 0) {
+ RG_DEBUG << "CompositionModelImpl::slotAudioPreviewComplete: set "
+ << values.size() << " samples on " << channels << " channels\n";
+ apData->setChannels(channels);
+ apData->setValues(values);
+ updateRect = postProcessAudioPreview(apData, apu->getSegment());
+ }
+ }
+
+ if (!updateRect.isEmpty())
+ emit needContentUpdate(updateRect);
+}
+
+QRect CompositionModelImpl::postProcessAudioPreview(AudioPreviewData* apData, const Segment* segment)
+{
+ // RG_DEBUG << "CompositionModelImpl::postProcessAudioPreview()\n";
+
+ AudioPreviewPainter previewPainter(*this, apData, m_composition, segment);
+ previewPainter.paintPreviewImage();
+
+ m_audioSegmentPreviewMap[segment] = previewPainter.getPreviewImage();
+
+ return previewPainter.getSegmentRect();
+}
+
+void CompositionModelImpl::slotInstrumentParametersChanged(InstrumentId id)
+{
+ std::cerr << "CompositionModelImpl::slotInstrumentParametersChanged()\n";
+ const Composition::segmentcontainer& segments = m_composition.getSegments();
+ Composition::segmentcontainer::iterator segEnd = segments.end();
+
+ for (Composition::segmentcontainer::iterator i = segments.begin();
+ i != segEnd; ++i) {
+
+ Segment* s = *i;
+ TrackId trackId = s->getTrack();
+ Track *track = getComposition().getTrackById(trackId);
+
+ // We need to update the cache for audio segments, because the
+ // instrument playback level is reflected in the audio
+ // preview. And we need to update it for midi segments,
+ // because the preview style differs depending on whether the
+ // segment is on a percussion instrument or not
+
+ if (track && track->getInstrument() == id) {
+ removePreviewCache(s);
+ emit needContentUpdate(computeSegmentRect(*s));
+ }
+ }
+}
+
+void CompositionModelImpl::slotAudioFileFinalized(Segment* s)
+{
+ // RG_DEBUG << "CompositionModelImpl::slotAudioFileFinalized()\n";
+ removePreviewCache(s);
+}
+
+PixmapArray CompositionModelImpl::getAudioPreviewPixmap(const Segment* s)
+{
+ getAudioPreviewData(s);
+ return m_audioSegmentPreviewMap[s];
+}
+
+void CompositionModelImpl::eventAdded(const Segment *s, Event *)
+{
+ // RG_DEBUG << "CompositionModelImpl::eventAdded()\n";
+ removePreviewCache(s);
+ emit needContentUpdate(computeSegmentRect(*s));
+}
+
+void CompositionModelImpl::eventRemoved(const Segment *s, Event *)
+{
+ // RG_DEBUG << "CompositionModelImpl::eventRemoved" << endl;
+ removePreviewCache(s);
+ emit needContentUpdate(computeSegmentRect(*s));
+}
+
+void CompositionModelImpl::appearanceChanged(const Segment *s)
+{
+ // RG_DEBUG << "CompositionModelImpl::appearanceChanged" << endl;
+ clearInCache(s, true);
+ emit needContentUpdate(computeSegmentRect(*s));
+}
+
+void CompositionModelImpl::endMarkerTimeChanged(const Segment *s, bool shorten)
+{
+ // RG_DEBUG << "CompositionModelImpl::endMarkerTimeChanged(" << shorten << ")" << endl;
+ clearInCache(s, true);
+ if (shorten) {
+ emit needContentUpdate(); // no longer know former segment dimension
+ } else {
+ emit needContentUpdate(computeSegmentRect(*s));
+ }
+}
+
+void CompositionModelImpl::makePreviewCache(const Segment *s)
+{
+ if (s->getType() == Segment::Internal) {
+ makeNotationPreviewDataCache(s);
+ } else {
+ makeAudioPreviewDataCache(s);
+ }
+}
+
+void CompositionModelImpl::removePreviewCache(const Segment *s)
+{
+ if (s->getType() == Segment::Internal) {
+ m_notationPreviewDataCache.remove(const_cast<Segment*>(s));
+ } else {
+ m_audioPreviewDataCache.remove(const_cast<Segment*>(s));
+ m_audioSegmentPreviewMap.erase(s);
+ }
+
+}
+
+void CompositionModelImpl::segmentAdded(const Composition *, Segment *s)
+{
+ std::cerr << "CompositionModelImpl::segmentAdded: segment " << s << " on track " << s->getTrack() << ": calling setTrackHeights" << std::endl;
+ setTrackHeights(s);
+
+ makePreviewCache(s);
+ s->addObserver(this);
+ emit needContentUpdate();
+}
+
+void CompositionModelImpl::segmentRemoved(const Composition *, Segment *s)
+{
+ setTrackHeights();
+
+ QRect r = computeSegmentRect(*s);
+
+ m_selectedSegments.erase(s);
+
+ clearInCache(s, true);
+ s->removeObserver(this);
+ m_recordingSegments.erase(s); // this could be a recording segment
+ emit needContentUpdate(r);
+}
+
+void CompositionModelImpl::segmentTrackChanged(const Composition *, Segment *s, TrackId tid)
+{
+ std::cerr << "CompositionModelImpl::segmentTrackChanged: segment " << s << " on track " << tid << ", calling setTrackHeights" << std::endl;
+
+ // we don't call setTrackHeights(s), because some of the tracks
+ // above s may have changed height as well (if s was moved off one
+ // of them)
+ if (setTrackHeights()) {
+ std::cerr << "... changed, updating" << std::endl;
+ emit needContentUpdate();
+ }
+}
+
+void CompositionModelImpl::segmentStartChanged(const Composition *, Segment *s, timeT)
+{
+// std::cerr << "CompositionModelImpl::segmentStartChanged: segment " << s << " on track " << s->getTrack() << ": calling setTrackHeights" << std::endl;
+ if (setTrackHeights(s)) emit needContentUpdate();
+}
+
+void CompositionModelImpl::segmentEndMarkerChanged(const Composition *, Segment *s, bool)
+{
+// std::cerr << "CompositionModelImpl::segmentEndMarkerChanged: segment " << s << " on track " << s->getTrack() << ": calling setTrackHeights" << std::endl;
+ if (setTrackHeights(s)) {
+// std::cerr << "... changed, updating" << std::endl;
+ emit needContentUpdate();
+ }
+}
+
+void CompositionModelImpl::segmentRepeatChanged(const Composition *, Segment *s, bool)
+{
+ clearInCache(s);
+ setTrackHeights(s);
+ emit needContentUpdate();
+}
+
+void CompositionModelImpl::endMarkerTimeChanged(const Composition *, bool)
+{
+ emit needSizeUpdate();
+}
+
+void CompositionModelImpl::setSelectionRect(const QRect& r)
+{
+ m_selectionRect = r.normalize();
+
+ m_previousTmpSelectedSegments = m_tmpSelectedSegments;
+ m_tmpSelectedSegments.clear();
+
+ const Composition::segmentcontainer& segments = m_composition.getSegments();
+ Composition::segmentcontainer::iterator segEnd = segments.end();
+
+ QRect updateRect = m_selectionRect;
+
+ for (Composition::segmentcontainer::iterator i = segments.begin();
+ i != segEnd; ++i) {
+
+ Segment* s = *i;
+ CompositionRect sr = computeSegmentRect(*s);
+ if (sr.intersects(m_selectionRect)) {
+ m_tmpSelectedSegments.insert(s);
+ updateRect |= sr;
+ }
+ }
+
+ updateRect = updateRect.normalize();
+
+ if (!updateRect.isNull() && !m_previousSelectionUpdateRect.isNull()) {
+
+ if (m_tmpSelectedSegments != m_previousTmpSelectedSegments)
+ emit needContentUpdate(updateRect | m_previousSelectionUpdateRect);
+
+ emit needArtifactsUpdate();
+ }
+
+
+ m_previousSelectionUpdateRect = updateRect;
+
+}
+
+void CompositionModelImpl::finalizeSelectionRect()
+{
+ const Composition::segmentcontainer& segments = m_composition.getSegments();
+ Composition::segmentcontainer::iterator segEnd = segments.end();
+
+ for (Composition::segmentcontainer::iterator i = segments.begin();
+ i != segEnd; ++i) {
+
+ Segment* s = *i;
+ CompositionRect sr = computeSegmentRect(*s);
+ if (sr.intersects(m_selectionRect)) {
+ setSelected(s);
+ }
+ }
+
+ m_previousSelectionUpdateRect = m_selectionRect = QRect();
+ m_tmpSelectedSegments.clear();
+}
+
+QRect CompositionModelImpl::getSelectionContentsRect()
+{
+ QRect selectionRect;
+
+ SegmentSelection sel = getSelectedSegments();
+ for (SegmentSelection::iterator i = sel.begin();
+ i != sel.end(); ++i) {
+
+ Segment* s = *i;
+ CompositionRect sr = computeSegmentRect(*s);
+ selectionRect |= sr;
+ }
+
+ return selectionRect;
+}
+
+void CompositionModelImpl::addRecordingItem(const CompositionItem& item)
+{
+ m_recordingSegments.insert(CompositionItemHelper::getSegment(item));
+ emit needContentUpdate();
+
+ RG_DEBUG << "CompositionModelImpl::addRecordingItem: now have "
+ << m_recordingSegments.size() << " recording items\n";
+}
+
+void CompositionModelImpl::removeRecordingItem(const CompositionItem &item)
+{
+ Segment* s = CompositionItemHelper::getSegment(item);
+
+ m_recordingSegments.erase(s);
+ clearInCache(s, true);
+
+ emit needContentUpdate();
+
+ RG_DEBUG << "CompositionModelImpl::removeRecordingItem: now have "
+ << m_recordingSegments.size() << " recording items\n";
+}
+
+void CompositionModelImpl::clearRecordingItems()
+{
+ for (recordingsegmentset::iterator i = m_recordingSegments.begin();
+ i != m_recordingSegments.end(); ++i)
+ clearInCache(*i, true);
+
+ m_recordingSegments.clear();
+
+ emit needContentUpdate();
+ RG_DEBUG << "CompositionModelImpl::clearRecordingItem\n";
+}
+
+bool CompositionModelImpl::isMoving(const Segment* sm) const
+{
+ itemcontainer::const_iterator movEnd = m_changingItems.end();
+
+ for (itemcontainer::const_iterator i = m_changingItems.begin(); i != movEnd; ++i) {
+ const CompositionItemImpl* ci = dynamic_cast<const CompositionItemImpl*>((_CompositionItem*)(*i));
+ const Segment* s = ci->getSegment();
+ if (sm == s)
+ return true;
+ }
+
+ return false;
+}
+
+bool CompositionModelImpl::isRecording(const Segment* s) const
+{
+ return m_recordingSegments.find(const_cast<Segment*>(s)) != m_recordingSegments.end();
+}
+
+CompositionModel::itemcontainer CompositionModelImpl::getItemsAt(const QPoint& point)
+{
+ itemcontainer res;
+
+ const Composition::segmentcontainer& segments = m_composition.getSegments();
+
+ for (Composition::segmentcontainer::iterator i = segments.begin();
+ i != segments.end(); ++i) {
+
+ Segment* s = *i;
+
+ CompositionRect sr = computeSegmentRect(*s);
+ if (sr.contains(point)) {
+ // RG_DEBUG << "CompositionModelImpl::getItemsAt() adding " << sr << " for segment " << s << endl;
+ CompositionItem item(new CompositionItemImpl(*s, sr));
+ unsigned int z = computeZForSegment(s);
+ // RG_DEBUG << "CompositionModelImpl::getItemsAt() z = " << z << endl;
+ item->setZ(z);
+ res.insert(item);
+ } else {
+ // RG_DEBUG << "CompositionModelImpl::getItemsAt() skiping " << sr << endl;
+ }
+
+ }
+
+ if (res.size() == 1) { // only one segment under click point
+ Segment* s = CompositionItemHelper::getSegment(*(res.begin()));
+ m_segmentOrderer.segmentClicked(s);
+ }
+
+ return res;
+}
+
+void CompositionModelImpl::setPointerPos(int xPos)
+{
+ m_pointerTimePos = grid().getRulerScale()->getTimeForX(xPos);
+
+ for (recordingsegmentset::iterator i = m_recordingSegments.begin();
+ i != m_recordingSegments.end(); ++i) {
+ emit needContentUpdate(computeSegmentRect(**i));
+ }
+}
+
+void CompositionModelImpl::setSelected(const CompositionItem& item, bool selected)
+{
+ const CompositionItemImpl* itemImpl = dynamic_cast<const CompositionItemImpl*>((_CompositionItem*)item);
+ if (itemImpl) {
+ Segment* segment = const_cast<Segment*>(itemImpl->getSegment());
+ setSelected(segment, selected);
+ }
+}
+
+void CompositionModelImpl::setSelected(const itemcontainer& items)
+{
+ for (itemcontainer::const_iterator i = items.begin(); i != items.end(); ++i) {
+ setSelected(*i);
+ }
+}
+
+void CompositionModelImpl::setSelected(const Segment* segment, bool selected)
+{
+ RG_DEBUG << "CompositionModelImpl::setSelected " << segment << " - " << selected << endl;
+ if (selected) {
+ if (!isSelected(segment))
+ m_selectedSegments.insert(const_cast<Segment*>(segment));
+ } else {
+ SegmentSelection::iterator i = m_selectedSegments.find(const_cast<Segment*>(segment));
+ if (i != m_selectedSegments.end())
+ m_selectedSegments.erase(i);
+ }
+ emit needContentUpdate();
+}
+
+void CompositionModelImpl::signalSelection()
+{
+ // RG_DEBUG << "CompositionModelImpl::signalSelection()\n";
+ emit selectedSegments(getSelectedSegments());
+}
+
+void CompositionModelImpl::signalContentChange()
+{
+ // RG_DEBUG << "CompositionModelImpl::signalContentChange" << endl;
+ emit needContentUpdate();
+}
+
+void CompositionModelImpl::clearSelected()
+{
+ RG_DEBUG << "CompositionModelImpl::clearSelected" << endl;
+ m_selectedSegments.clear();
+ emit needContentUpdate();
+}
+
+bool CompositionModelImpl::isSelected(const CompositionItem& ci) const
+{
+ const CompositionItemImpl* itemImpl = dynamic_cast<const CompositionItemImpl*>((_CompositionItem*)ci);
+ return itemImpl ? isSelected(itemImpl->getSegment()) : 0;
+}
+
+bool CompositionModelImpl::isSelected(const Segment* s) const
+{
+ return m_selectedSegments.find(const_cast<Segment*>(s)) != m_selectedSegments.end();
+}
+
+bool CompositionModelImpl::isTmpSelected(const Segment* s) const
+{
+ return m_tmpSelectedSegments.find(const_cast<Segment*>(s)) != m_tmpSelectedSegments.end();
+}
+
+bool CompositionModelImpl::wasTmpSelected(const Segment* s) const
+{
+ return m_previousTmpSelectedSegments.find(const_cast<Segment*>(s)) != m_previousTmpSelectedSegments.end();
+}
+
+void CompositionModelImpl::startChange(const CompositionItem& item, CompositionModel::ChangeType change)
+{
+ m_changeType = change;
+
+ itemcontainer::iterator i = m_changingItems.find(item);
+
+ // if an "identical" composition item has already been inserted, drop this one
+ if (i != m_changingItems.end()) {
+ RG_DEBUG << "CompositionModelImpl::startChange : item already in\n";
+ m_itemGC.push_back(item);
+ } else {
+ item->saveRect();
+ m_changingItems.insert(item);
+ }
+}
+
+void CompositionModelImpl::startChangeSelection(CompositionModel::ChangeType change)
+{
+ SegmentSelection::iterator i = m_selectedSegments.begin();
+ for (; i != m_selectedSegments.end(); ++i) {
+ Segment* s = *i;
+ CompositionRect sr = computeSegmentRect(*s);
+ startChange(CompositionItem(new CompositionItemImpl(*s, sr)), change);
+ }
+
+}
+
+void CompositionModelImpl::endChange()
+{
+ for (itemcontainer::const_iterator i = m_changingItems.begin(); i != m_changingItems.end(); ++i) {
+ delete *i;
+ }
+
+ m_changingItems.clear();
+
+ for (itemgc::iterator i = m_itemGC.begin(); i != m_itemGC.end(); ++i) {
+ delete *i;
+ }
+ m_itemGC.clear();
+ RG_DEBUG << "CompositionModelImpl::endChange\n";
+ emit needContentUpdate();
+}
+
+void CompositionModelImpl::setLength(int width)
+{
+ timeT endMarker = m_grid.snapX(width);
+ m_composition.setEndMarker(endMarker);
+}
+
+int CompositionModelImpl::getLength()
+{
+ timeT endMarker = m_composition.getEndMarker();
+ int w = int(nearbyint(m_grid.getRulerScale()->getWidthForDuration(0, endMarker)));
+ return w;
+}
+
+timeT CompositionModelImpl::getRepeatTimeAt(const QPoint& p, const CompositionItem& cItem)
+{
+ // timeT timeAtClick = m_grid.getRulerScale()->getTimeForX(p.x());
+
+ CompositionItemImpl* itemImpl = dynamic_cast<CompositionItemImpl*>((_CompositionItem*)cItem);
+
+ const Segment* s = itemImpl->getSegment();
+
+ timeT startTime = s->getStartTime();
+ timeT endTime = s->getEndMarkerTime();
+ timeT repeatInterval = endTime - startTime;
+
+ int rWidth = int(nearbyint(m_grid.getRulerScale()->getXForTime(repeatInterval)));
+
+ int count = (p.x() - int(itemImpl->rect().x())) / rWidth;
+ RG_DEBUG << "CompositionModelImpl::getRepeatTimeAt() : count = " << count << endl;
+
+ return count != 0 ? startTime + (count * (s->getEndMarkerTime() - s->getStartTime())) : 0;
+}
+
+bool CompositionModelImpl::setTrackHeights(Segment *s)
+{
+ bool heightsChanged = false;
+
+// std::cerr << "CompositionModelImpl::setTrackHeights" << std::endl;
+
+ for (Composition::trackcontainer::const_iterator i =
+ m_composition.getTracks().begin();
+ i != m_composition.getTracks().end(); ++i) {
+
+ if (s && i->first != s->getTrack()) continue;
+
+ int max = m_composition.getMaxContemporaneousSegmentsOnTrack(i->first);
+ if (max == 0) max = 1;
+
+// std::cerr << "for track " << i->first << ": height = " << max << ", old height = " << m_trackHeights[i->first] << std::endl;
+
+ if (max != m_trackHeights[i->first]) {
+ heightsChanged = true;
+ m_trackHeights[i->first] = max;
+ }
+
+ m_grid.setBinHeightMultiple(i->second->getPosition(), max);
+ }
+
+ if (heightsChanged) {
+// std::cerr << "CompositionModelImpl::setTrackHeights: heights have changed" << std::endl;
+ for (Composition::segmentcontainer::iterator i = m_composition.begin();
+ i != m_composition.end(); ++i) {
+ computeSegmentRect(**i);
+ }
+ }
+
+ return heightsChanged;
+}
+
+QPoint CompositionModelImpl::computeSegmentOrigin(const Segment& s)
+{
+ // Profiler profiler("CompositionModelImpl::computeSegmentOrigin", true);
+
+ int trackPosition = m_composition.getTrackPositionById(s.getTrack());
+ timeT startTime = s.getStartTime();
+
+ QPoint res;
+
+ res.setX(int(nearbyint(m_grid.getRulerScale()->getXForTime(startTime))));
+
+ res.setY(m_grid.getYBinCoordinate(trackPosition) +
+ m_composition.getSegmentVoiceIndex(&s) *
+ m_grid.getYSnap() + 1);
+
+ return res;
+}
+
+bool CompositionModelImpl::isCachedRectCurrent(const Segment& s, const CompositionRect& r, QPoint cachedSegmentOrigin, timeT cachedSegmentEndTime)
+{
+ return s.isRepeating() == r.isRepeating() &&
+ ((cachedSegmentOrigin.x() != r.x() && s.getEndMarkerTime() != cachedSegmentEndTime) ||
+ (cachedSegmentOrigin.x() == r.x() && s.getEndMarkerTime() == cachedSegmentEndTime));
+}
+
+void CompositionModelImpl::clearInCache(const Segment* s, bool clearPreview)
+{
+ if (s) {
+ m_segmentRectMap.erase(s);
+ m_segmentEndTimeMap.erase(s);
+ if (clearPreview)
+ removePreviewCache(s);
+ } else { // clear the whole cache
+ m_segmentRectMap.clear();
+ m_segmentEndTimeMap.clear();
+ if (clearPreview)
+ clearPreviewCache();
+ }
+}
+
+void CompositionModelImpl::putInCache(const Segment*s, const CompositionRect& cr)
+{
+ m_segmentRectMap[s] = cr;
+ m_segmentEndTimeMap[s] = s->getEndMarkerTime();
+}
+
+CompositionRect CompositionModelImpl::computeSegmentRect(const Segment& s, bool computeZ)
+{
+ // Profiler profiler("CompositionModelImpl::computeSegmentRect", true);
+
+ QPoint origin = computeSegmentOrigin(s);
+
+ bool isRecordingSegment = isRecording(&s);
+
+ if (!isRecordingSegment) {
+ timeT endTime = 0;
+
+ CompositionRect cachedCR = getFromCache(&s, endTime);
+ // don't cache repeating segments - it's just hopeless, because the segment's rect may have to be recomputed
+ // in other cases than just when the segment itself is moved,
+ // for instance if another segment is moved over it
+ if (!s.isRepeating() && cachedCR.isValid() && isCachedRectCurrent(s, cachedCR, origin, endTime)) {
+ // RG_DEBUG << "CompositionModelImpl::computeSegmentRect() : using cache for seg "
+ // << &s << " - cached rect repeating = " << cachedCR.isRepeating() << " - base width = "
+ // << cachedCR.getBaseWidth() << endl;
+
+ bool xChanged = origin.x() != cachedCR.x();
+ bool yChanged = origin.y() != cachedCR.y();
+
+ cachedCR.moveTopLeft(origin);
+
+ if (s.isRepeating() && (xChanged || yChanged)) { // update repeat marks
+
+ // this doesn't work in the general case (if there's another segment on the same track for instance),
+ // it's better to simply recompute all the marks
+ // CompositionRect::repeatmarks repeatMarks = cachedCR.getRepeatMarks();
+ // for(unsigned int i = 0; i < repeatMarks.size(); ++i) {
+ // repeatMarks[i] += deltaX;
+ // }
+ // cachedCR.setRepeatMarks(repeatMarks);
+ computeRepeatMarks(cachedCR, &s);
+ }
+ putInCache(&s, cachedCR);
+ return cachedCR;
+ }
+ }
+
+ timeT startTime = s.getStartTime();
+ timeT endTime = isRecordingSegment ? m_pointerTimePos /*s.getEndTime()*/ : s.getEndMarkerTime();
+
+
+ int h = m_grid.getYSnap() - 2;
+ int w;
+
+ RG_DEBUG << "CompositionModelImpl::computeSegmentRect: x " << origin.x() << ", y " << origin.y() << " startTime " << startTime << ", endTime " << endTime << endl;
+
+ if (s.isRepeating()) {
+ timeT repeatStart = endTime;
+ timeT repeatEnd = s.getRepeatEndTime();
+ w = int(nearbyint(m_grid.getRulerScale()->getWidthForDuration(startTime,
+ repeatEnd - startTime)));
+ // RG_DEBUG << "CompositionModelImpl::computeSegmentRect : s is repeating - repeatStart = "
+ // << repeatStart << " - repeatEnd : " << repeatEnd
+ // << " w = " << w << endl;
+ } else {
+ w = int(nearbyint(m_grid.getRulerScale()->getWidthForDuration(startTime, endTime - startTime)));
+ // RG_DEBUG << "CompositionModelImpl::computeSegmentRect : s is NOT repeating"
+ // << " w = " << w << " (x for time at start is " << m_grid.getRulerScale()->getXForTime(startTime) << ", end is " << m_grid.getRulerScale()->getXForTime(endTime) << ")" << endl;
+ }
+
+ CompositionRect cr(origin, QSize(w, h));
+ QString label = strtoqstr(s.getLabel());
+ if (s.getType() == Segment::Audio) {
+ static QRegExp re1("( *\\([^)]*\\))*$"); // (inserted) (copied) (etc)
+ static QRegExp re2("\\.[^.]+$"); // filename suffix
+ label.replace(re1, "").replace(re2, "");
+ }
+ cr.setLabel(label);
+
+ if (s.isRepeating()) {
+ computeRepeatMarks(cr, &s);
+ } else {
+ cr.setBaseWidth(cr.width());
+ }
+
+ putInCache(&s, cr);
+
+ return cr;
+}
+
+unsigned int CompositionModelImpl::computeZForSegment(const Rosegarden::Segment* s)
+{
+ return m_segmentOrderer.getZForSegment(s);
+}
+
+const CompositionRect& CompositionModelImpl::getFromCache(const Rosegarden::Segment* s, timeT& endTime)
+{
+ endTime = m_segmentEndTimeMap[s];
+ return m_segmentRectMap[s];
+}
+
+unsigned int CompositionModelImpl::getNbRows()
+{
+ return m_composition.getNbTracks();
+}
+
+const CompositionModel::rectcontainer& CompositionModelImpl::getRectanglesIn(const QRect& rect,
+ RectRanges* npData,
+ AudioPreviewDrawData* apData)
+{
+ // Profiler profiler("CompositionModelImpl::getRectanglesIn", true);
+
+ m_res.clear();
+
+ // RG_DEBUG << "CompositionModelImpl::getRectanglesIn: ruler scale is "
+ // << (dynamic_cast<SimpleRulerScale *>(m_grid.getRulerScale()))->getUnitsPerPixel() << endl;
+
+ const Composition::segmentcontainer& segments = m_composition.getSegments();
+ Composition::segmentcontainer::iterator segEnd = segments.end();
+
+ for (Composition::segmentcontainer::iterator i = segments.begin();
+ i != segEnd; ++i) {
+
+ // RG_DEBUG << "CompositionModelImpl::getRectanglesIn: Composition contains segment " << *i << " (" << (*i)->getStartTime() << "->" << (*i)->getEndTime() << ")"<< endl;
+
+ Segment* s = *i;
+
+ if (isMoving(s))
+ continue;
+
+ CompositionRect sr = computeSegmentRect(*s);
+ // RG_DEBUG << "CompositionModelImpl::getRectanglesIn: seg rect = " << sr << endl;
+
+ if (sr.intersects(rect)) {
+ bool tmpSelected = isTmpSelected(s),
+ pTmpSelected = wasTmpSelected(s);
+
+// RG_DEBUG << "CompositionModelImpl::getRectanglesIn: segment " << s
+// << " selected : " << isSelected(s) << " - tmpSelected : " << isTmpSelected(s) << endl;
+
+ if (isSelected(s) || isTmpSelected(s) || sr.intersects(m_selectionRect)) {
+ sr.setSelected(true);
+ }
+
+ if (pTmpSelected != tmpSelected)
+ sr.setNeedsFullUpdate(true);
+
+ bool isAudio = (s && s->getType() == Segment::Audio);
+
+ if (!isRecording(s)) {
+ QColor brushColor = GUIPalette::convertColour(m_composition.
+ getSegmentColourMap().getColourByIndex(s->getColourIndex()));
+ sr.setBrush(brushColor);
+ sr.setPen(CompositionColourCache::getInstance()->SegmentBorder);
+ } else {
+ // border is the same for both audio and MIDI
+ sr.setPen(CompositionColourCache::getInstance()->RecordingSegmentBorder);
+ // audio color
+ if (isAudio) {
+ sr.setBrush(CompositionColourCache::getInstance()->RecordingAudioSegmentBlock);
+ // MIDI/default color
+ } else {
+ sr.setBrush(CompositionColourCache::getInstance()->RecordingInternalSegmentBlock);
+ }
+ }
+
+ // Notation preview data
+ if (npData && s->getType() == Segment::Internal) {
+ makeNotationPreviewRects(npData, QPoint(0, sr.y()), s, rect);
+ // Audio preview data
+ } else if (apData && s->getType() == Segment::Audio) {
+ makeAudioPreviewRects(apData, s, sr, rect);
+ }
+
+ m_res.push_back(sr);
+ } else {
+ // RG_DEBUG << "CompositionModelImpl::getRectanglesIn: - segment out of rect\n";
+ }
+
+ }
+
+ // changing items
+
+ itemcontainer::iterator movEnd = m_changingItems.end();
+ for (itemcontainer::iterator i = m_changingItems.begin(); i != movEnd; ++i) {
+ CompositionRect sr((*i)->rect());
+ if (sr.intersects(rect)) {
+ Segment* s = CompositionItemHelper::getSegment(*i);
+ sr.setSelected(true);
+ QColor brushColor = GUIPalette::convertColour(m_composition.getSegmentColourMap().getColourByIndex(s->getColourIndex()));
+ sr.setBrush(brushColor);
+
+ sr.setPen(CompositionColourCache::getInstance()->SegmentBorder);
+
+ // Notation preview data
+ if (npData && s->getType() == Segment::Internal) {
+ makeNotationPreviewRectsMovingSegment(npData, sr.topLeft(), s, sr);
+ // Audio preview data
+ } else if (apData && s->getType() == Segment::Audio) {
+ makeAudioPreviewRects(apData, s, sr, rect);
+ }
+
+ m_res.push_back(sr);
+ }
+ }
+
+ return m_res;
+}
+
+CompositionModel::heightlist
+CompositionModelImpl::getTrackDividersIn(const QRect& rect)
+{
+ int top = m_grid.getYBin(rect.y());
+ int bottom = m_grid.getYBin(rect.y() + rect.height());
+
+// std::cerr << "CompositionModelImpl::getTrackDividersIn: rect "
+// << rect.x() << ", " << rect.y() << ", "
+// << rect.width() << "x" << rect.height() << ", top = " << top
+// << ", bottom = " << bottom << std::endl;
+
+ CompositionModel::heightlist list;
+
+ for (int pos = top; pos <= bottom; ++pos) {
+ int divider = m_grid.getYBinCoordinate(pos);
+ list.push_back(divider);
+// std::cerr << "divider at " << divider << std::endl;
+ }
+
+ return list;
+}
+
+CompositionModel::rectlist* CompositionModelImpl::getNotationPreviewData(const Segment* s)
+{
+ rectlist* npData = m_notationPreviewDataCache[const_cast<Segment*>(s)];
+
+ if (!npData) {
+ npData = makeNotationPreviewDataCache(s);
+ }
+
+ return npData;
+}
+
+CompositionModel::AudioPreviewData* CompositionModelImpl::getAudioPreviewData(const Segment* s)
+{
+ // Profiler profiler("CompositionModelImpl::getAudioPreviewData", true);
+ RG_DEBUG << "CompositionModelImpl::getAudioPreviewData\n";
+
+ AudioPreviewData* apData = m_audioPreviewDataCache[const_cast<Segment*>(s)];
+
+ if (!apData) {
+ apData = makeAudioPreviewDataCache(s);
+ }
+
+ RG_DEBUG << "CompositionModelImpl::getAudioPreviewData returning\n";
+ return apData;
+}
+
+CompositionModel::rectlist* CompositionModelImpl::makeNotationPreviewDataCache(const Segment *s)
+{
+ rectlist* npData = new rectlist();
+ updatePreviewCacheForNotationSegment(s, npData);
+ m_notationPreviewDataCache.insert(const_cast<Segment*>(s), npData);
+ return npData;
+}
+
+CompositionModel::AudioPreviewData* CompositionModelImpl::makeAudioPreviewDataCache(const Segment *s)
+{
+ RG_DEBUG << "CompositionModelImpl::makeAudioPreviewDataCache(" << s << ")" << endl;
+
+ AudioPreviewData* apData = new AudioPreviewData(false, 0); // 0 channels -> empty
+ updatePreviewCacheForAudioSegment(s, apData);
+ m_audioPreviewDataCache.insert(const_cast<Segment*>(s), apData);
+ return apData;
+}
+
+}
+#include "CompositionModelImpl.moc"
diff --git a/src/gui/editors/segment/segmentcanvas/CompositionModelImpl.h b/src/gui/editors/segment/segmentcanvas/CompositionModelImpl.h
new file mode 100644
index 0000000..6e1c9d6
--- /dev/null
+++ b/src/gui/editors/segment/segmentcanvas/CompositionModelImpl.h
@@ -0,0 +1,239 @@
+
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
+
+/*
+ Rosegarden
+ A MIDI and audio sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_COMPOSITIONMODELIMPL_H_
+#define _RG_COMPOSITIONMODELIMPL_H_
+
+#include "base/Selection.h"
+#include "base/SnapGrid.h"
+#include "CompositionModel.h"
+#include "CompositionRect.h"
+#include <map>
+#include "SegmentOrderer.h"
+#include <set>
+#include <qcolor.h>
+#include <qpoint.h>
+#include <qptrdict.h>
+#include <qrect.h>
+#include <vector>
+#include "base/Event.h"
+
+
+class RectRanges;
+class CompositionItem;
+class AudioPreviewDrawData;
+class AudioPreviewData;
+
+
+namespace Rosegarden
+{
+
+class Studio;
+class Segment;
+class RulerScale;
+class Event;
+class Composition;
+class AudioPreviewUpdater;
+class AudioPreviewThread;
+
+
+class CompositionModelImpl : public CompositionModel
+{
+ Q_OBJECT
+public:
+
+ CompositionModelImpl(Composition& compo,
+ Studio& studio,
+ RulerScale *rulerScale,
+ int vStep);
+
+ virtual ~CompositionModelImpl();
+
+ virtual unsigned int getNbRows();
+ virtual const rectcontainer& getRectanglesIn(const QRect& rect,
+ RectRanges* notationRects, AudioPreviewDrawData* audioRects);
+ virtual heightlist getTrackDividersIn(const QRect& rect);
+ virtual itemcontainer getItemsAt (const QPoint&);
+ virtual timeT getRepeatTimeAt (const QPoint&, const CompositionItem&);
+
+ virtual SnapGrid& grid() { return m_grid; }
+
+ virtual void setPointerPos(int xPos);
+ virtual void setSelected(const CompositionItem&, bool selected = true);
+ virtual bool isSelected(const CompositionItem&) const;
+ virtual void setSelected(const itemcontainer&);
+ virtual void clearSelected();
+ virtual bool haveSelection() const { return !m_selectedSegments.empty(); }
+ virtual bool haveMultipleSelection() const { return m_selectedSegments.size() > 1; }
+ virtual void signalSelection();
+ virtual void setSelectionRect(const QRect&);
+ virtual void finalizeSelectionRect();
+ virtual QRect getSelectionContentsRect();
+ virtual void signalContentChange();
+
+ virtual void addRecordingItem(const CompositionItem&);
+ virtual void removeRecordingItem(const CompositionItem &);
+ virtual void clearRecordingItems();
+ virtual bool haveRecordingItems() { return m_recordingSegments.size() > 0; }
+
+ virtual void startChange(const CompositionItem&, ChangeType change);
+ virtual void startChangeSelection(ChangeType change);
+ virtual itemcontainer& getChangingItems() { return m_changingItems; }
+ virtual void endChange();
+ virtual ChangeType getChangeType() { return m_changeType; }
+
+ virtual void setLength(int width);
+ virtual int getLength();
+
+ void setAudioPreviewThread(AudioPreviewThread *thread);
+ AudioPreviewThread* getAudioPreviewThread() { return m_audioPreviewThread; }
+
+ void clearPreviewCache();
+ void clearSegmentRectsCache(bool clearPreviews = false) { clearInCache(0, clearPreviews); }
+
+ rectlist* makeNotationPreviewDataCache(const Segment *s);
+ AudioPreviewData* makeAudioPreviewDataCache(const Segment *s);
+
+ CompositionRect computeSegmentRect(const Segment&, bool computeZ = false);
+ QColor computeSegmentPreviewColor(const Segment*);
+ QPoint computeSegmentOrigin(const Segment&);
+ void computeRepeatMarks(CompositionItem&);
+
+ SegmentSelection getSelectedSegments() { return m_selectedSegments; }
+ Composition& getComposition() { return m_composition; }
+ Studio& getStudio() { return m_studio; }
+
+
+ // CompositionObserver
+ virtual void segmentAdded(const Composition *, Segment *);
+ virtual void segmentRemoved(const Composition *, Segment *);
+ virtual void segmentRepeatChanged(const Composition *, Segment *, bool);
+ virtual void segmentStartChanged(const Composition *, Segment *, timeT);
+ virtual void segmentEndMarkerChanged(const Composition *, Segment *, bool);
+ virtual void segmentTrackChanged(const Composition *, Segment *, TrackId);
+ virtual void endMarkerTimeChanged(const Composition *, bool /*shorten*/);
+
+ // SegmentObserver
+ virtual void eventAdded(const Segment *, Event *);
+ virtual void eventRemoved(const Segment *, Event *);
+ virtual void appearanceChanged(const Segment *);
+ virtual void endMarkerTimeChanged(const Segment *, bool /*shorten*/);
+ virtual void segmentDeleted(const Segment*) { /* nothing to do - handled by CompositionObserver::segmentRemoved() */ };
+
+signals:
+ void selectedSegments(const SegmentSelection &);
+ void needSizeUpdate();
+
+public slots:
+ void slotAudioFileFinalized(Segment*);
+ void slotInstrumentParametersChanged(InstrumentId);
+
+protected slots:
+ void slotAudioPreviewComplete(AudioPreviewUpdater*);
+
+protected:
+ bool setTrackHeights(Segment *changed = 0); // true if something changed
+
+ void setSelected(const Segment*, bool selected = true);
+ bool isSelected(const Segment*) const;
+ bool isTmpSelected(const Segment*) const;
+ bool wasTmpSelected(const Segment*) const;
+ bool isMoving(const Segment*) const;
+ bool isRecording(const Segment*) const;
+
+ void computeRepeatMarks(CompositionRect& sr, const Segment* s);
+ unsigned int computeZForSegment(const Segment* s);
+
+ // segment preview stuff
+
+ void updatePreviewCacheForNotationSegment(const Segment* s, rectlist*);
+ void updatePreviewCacheForAudioSegment(const Segment* s, AudioPreviewData*);
+ rectlist* getNotationPreviewData(const Segment* s);
+ AudioPreviewData* getAudioPreviewData(const Segment* s);
+ PixmapArray getAudioPreviewPixmap(const Segment* s);
+ QRect postProcessAudioPreview(AudioPreviewData*, const Segment*);
+
+ void makePreviewCache(const Segment* s);
+ void removePreviewCache(const Segment* s);
+ void makeNotationPreviewRects(RectRanges* npData, QPoint basePoint, const Segment*, const QRect&);
+ void makeNotationPreviewRectsMovingSegment(RectRanges* npData, QPoint basePoint, const Segment*,
+ const QRect&);
+ void makeAudioPreviewRects(AudioPreviewDrawData* apRects, const Segment*,
+ const CompositionRect& segRect, const QRect& clipRect);
+
+ void clearInCache(const Segment*, bool clearPreviewCache = false);
+ void putInCache(const Segment*, const CompositionRect&);
+ const CompositionRect& getFromCache(const Segment*, timeT& endTime);
+ bool isCachedRectCurrent(const Segment& s, const CompositionRect& r,
+ QPoint segmentOrigin, timeT segmentEndTime);
+
+ //--------------- Data members ---------------------------------
+ Composition& m_composition;
+ Studio& m_studio;
+ SnapGrid m_grid;
+ SegmentSelection m_selectedSegments;
+ SegmentSelection m_tmpSelectedSegments;
+ SegmentSelection m_previousTmpSelectedSegments;
+
+ timeT m_pointerTimePos;
+
+ typedef std::set<Segment *> recordingsegmentset;
+ recordingsegmentset m_recordingSegments;
+
+ typedef std::vector<CompositionItem> itemgc;
+
+ AudioPreviewThread* m_audioPreviewThread;
+
+ typedef QPtrDict<rectlist> NotationPreviewDataCache;
+ typedef QPtrDict<AudioPreviewData> AudioPreviewDataCache;
+
+ NotationPreviewDataCache m_notationPreviewDataCache;
+ AudioPreviewDataCache m_audioPreviewDataCache;
+
+ rectcontainer m_res;
+ itemcontainer m_changingItems;
+ ChangeType m_changeType;
+ itemgc m_itemGC;
+
+ QRect m_selectionRect;
+ QRect m_previousSelectionUpdateRect;
+
+ std::map<const Segment*, CompositionRect> m_segmentRectMap;
+ std::map<const Segment*, timeT> m_segmentEndTimeMap;
+ std::map<const Segment*, PixmapArray> m_audioSegmentPreviewMap;
+ std::map<TrackId, int> m_trackHeights;
+
+ typedef std::map<const Segment*, AudioPreviewUpdater *>
+ AudioPreviewUpdaterMap;
+ AudioPreviewUpdaterMap m_audioPreviewUpdaterMap;
+
+ SegmentOrderer m_segmentOrderer;
+};
+
+
+
+}
+
+#endif
diff --git a/src/gui/editors/segment/segmentcanvas/CompositionRect.cpp b/src/gui/editors/segment/segmentcanvas/CompositionRect.cpp
new file mode 100644
index 0000000..9e34d71
--- /dev/null
+++ b/src/gui/editors/segment/segmentcanvas/CompositionRect.cpp
@@ -0,0 +1,42 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
+
+/*
+ Rosegarden
+ A MIDI and audio sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "CompositionRect.h"
+#include "base/ColourMap.h"
+
+#include <qbrush.h>
+#include <qcolor.h>
+#include <qpen.h>
+#include <qpoint.h>
+#include <qrect.h>
+#include <qsize.h>
+#include <qstring.h>
+
+
+namespace Rosegarden
+{
+ const QColor CompositionRect::DefaultPenColor = Qt::black;
+ const QColor CompositionRect::DefaultBrushColor = QColor(COLOUR_DEF_R, COLOUR_DEF_G, COLOUR_DEF_B);
+}
diff --git a/src/gui/editors/segment/segmentcanvas/CompositionRect.h b/src/gui/editors/segment/segmentcanvas/CompositionRect.h
new file mode 100644
index 0000000..3c3d2b6
--- /dev/null
+++ b/src/gui/editors/segment/segmentcanvas/CompositionRect.h
@@ -0,0 +1,108 @@
+
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
+
+/*
+ Rosegarden
+ A MIDI and audio sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_COMPOSITIONRECT_H_
+#define _RG_COMPOSITIONRECT_H_
+
+#include <qbrush.h>
+#include <qcolor.h>
+#include <qpen.h>
+#include <qrect.h>
+#include <qstring.h>
+#include <qvaluevector.h>
+
+
+class QSize;
+class QPoint;
+
+
+namespace Rosegarden
+{
+
+class CompositionRect : public QRect
+{
+public:
+ typedef QValueVector<int> repeatmarks;
+
+ friend bool operator<(const CompositionRect&, const CompositionRect&);
+
+ CompositionRect() : QRect(), m_selected(false),
+ m_needUpdate(false), m_brush(DefaultBrushColor), m_pen(DefaultPenColor) {};
+ CompositionRect(const QRect& r) : QRect(r), m_resized(false), m_selected(false),
+ m_needUpdate(false), m_brush(DefaultBrushColor), m_pen(DefaultPenColor), m_z(0) {};
+ CompositionRect(const QPoint & topLeft, const QPoint & bottomRight)
+ : QRect(topLeft, bottomRight), m_resized(false), m_selected(false),
+ m_needUpdate(false), m_brush(DefaultBrushColor), m_pen(DefaultPenColor), m_z(0) {};
+ CompositionRect(const QPoint & topLeft, const QSize & size)
+ : QRect(topLeft, size), m_resized(false), m_selected(false),
+ m_needUpdate(false), m_brush(DefaultBrushColor), m_pen(DefaultPenColor), m_z(0) {};
+ CompositionRect(int left, int top, int width, int height)
+ : QRect(left, top, width, height), m_resized(false), m_selected(false),
+ m_needUpdate(false), m_brush(DefaultBrushColor), m_pen(DefaultPenColor), m_z(0) {};
+
+ void setResized(bool s) { m_resized = s; }
+ bool isResized() const { return m_resized; }
+ void setSelected(bool s) { m_selected = s; }
+ bool isSelected() const { return m_selected; }
+ bool needsFullUpdate() const { return m_needUpdate; }
+ void setNeedsFullUpdate(bool s) { m_needUpdate = s; }
+
+ void setZ(int z) { m_z = z; }
+ int z() const { return m_z; }
+
+ // brush, pen draw info
+ void setBrush(QBrush b) { m_brush = b; }
+ QBrush getBrush() const { return m_brush; }
+ void setPen(QPen b) { m_pen = b; }
+ QPen getPen() const { return m_pen; }
+
+ // repeating segments
+ void setRepeatMarks(const repeatmarks& rm) { m_repeatMarks = rm; }
+ const repeatmarks& getRepeatMarks() const { return m_repeatMarks; }
+ bool isRepeating() const { return m_repeatMarks.size() > 0; }
+ int getBaseWidth() const { return m_baseWidth; }
+ void setBaseWidth(int bw) { m_baseWidth = bw; }
+ QString getLabel() const { return m_label; }
+ void setLabel(QString l) { m_label = l; }
+
+ static const QColor DefaultPenColor;
+ static const QColor DefaultBrushColor;
+
+protected:
+ bool m_resized;
+ bool m_selected;
+ bool m_needUpdate;
+ QBrush m_brush;
+ QPen m_pen;
+ repeatmarks m_repeatMarks;
+ int m_baseWidth;
+ QString m_label;
+ int m_z;
+};
+
+
+}
+
+#endif
diff --git a/src/gui/editors/segment/segmentcanvas/CompositionView.cpp b/src/gui/editors/segment/segmentcanvas/CompositionView.cpp
new file mode 100644
index 0000000..8e83a6b
--- /dev/null
+++ b/src/gui/editors/segment/segmentcanvas/CompositionView.cpp
@@ -0,0 +1,1591 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
+
+/*
+ Rosegarden
+ A MIDI and audio sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "CompositionView.h"
+
+#include "misc/Debug.h"
+#include "AudioPreviewThread.h"
+#include "base/RulerScale.h"
+#include "base/Segment.h"
+#include "base/Selection.h"
+#include "base/SnapGrid.h"
+#include "CompositionColourCache.h"
+#include "CompositionItemHelper.h"
+#include "CompositionItemImpl.h"
+#include "CompositionModel.h"
+#include "CompositionModelImpl.h"
+#include "CompositionRect.h"
+#include "AudioPreviewPainter.h"
+#include "document/RosegardenGUIDoc.h"
+#include "document/ConfigGroups.h"
+#include "gui/general/GUIPalette.h"
+#include "gui/general/RosegardenCanvasView.h"
+#include "gui/general/RosegardenScrollView.h"
+#include "SegmentSelector.h"
+#include "SegmentToolBox.h"
+#include "SegmentTool.h"
+#include <kmessagebox.h>
+#include <qbrush.h>
+#include <qcolor.h>
+#include <qevent.h>
+#include <qfont.h>
+#include <qfontmetrics.h>
+#include <qmemarray.h>
+#include <qpainter.h>
+#include <qpen.h>
+#include <qpixmap.h>
+#include <qpoint.h>
+#include <qrect.h>
+#include <qscrollbar.h>
+#include <qscrollview.h>
+#include <qsize.h>
+#include <qstring.h>
+#include <qwidget.h>
+#include <kapplication.h>
+#include <kconfig.h>
+#include <algorithm>
+
+
+namespace Rosegarden
+{
+
+class PreviewRect : public QRect {
+public:
+ PreviewRect(int left, int top, int width, int height) :
+ QRect(left, top, width, height) {};
+
+ PreviewRect(const QRect& r) :
+ QRect(r) {};
+
+ const QColor& getColor() const { return m_color; }
+ void setColor(QColor c) { m_color = c; }
+
+protected:
+ QColor m_color;
+};
+
+CompositionView::CompositionView(RosegardenGUIDoc* doc,
+ CompositionModel* model,
+ QWidget * parent, const char * name, WFlags f)
+#if KDE_VERSION >= KDE_MAKE_VERSION(3,2,0)
+ : RosegardenScrollView(parent, name, f | WNoAutoErase | WStaticContents),
+#else
+ :
+ RosegardenScrollView(parent, name, f | WRepaintNoErase | WResizeNoErase | WStaticContents),
+#endif
+ m_model(model),
+ m_currentItem(0),
+ m_tool(0),
+ m_toolBox(0),
+ m_showPreviews(false),
+ m_showSegmentLabels(true),
+ m_fineGrain(false),
+ m_pencilOverExisting(false),
+ m_minWidth(m_model->getLength()),
+ m_stepSize(0),
+ m_rectFill(0xF0, 0xF0, 0xF0),
+ m_selectedRectFill(0x00, 0x00, 0xF0),
+ m_pointerPos(0),
+ m_pointerColor(GUIPalette::getColour(GUIPalette::Pointer)),
+ m_pointerWidth(4),
+ m_pointerPen(QPen(m_pointerColor, m_pointerWidth)),
+ m_tmpRect(QRect(QPoint(0, 0), QPoint( -1, -1))),
+ m_tmpRectFill(CompositionRect::DefaultBrushColor),
+ m_trackDividerColor(GUIPalette::getColour(GUIPalette::TrackDivider)),
+ m_drawGuides(false),
+ m_guideColor(GUIPalette::getColour(GUIPalette::MovementGuide)),
+ m_topGuidePos(0),
+ m_foreGuidePos(0),
+ m_drawSelectionRect(false),
+ m_drawTextFloat(false),
+ m_segmentsDrawBuffer(visibleWidth(), visibleHeight()),
+ m_artifactsDrawBuffer(visibleWidth(), visibleHeight()),
+ m_segmentsDrawBufferRefresh(0, 0, visibleWidth(), visibleHeight()),
+ m_artifactsDrawBufferRefresh(0, 0, visibleWidth(), visibleHeight()),
+ m_lastBufferRefreshX(0),
+ m_lastBufferRefreshY(0),
+ m_lastPointerRefreshX(0),
+ m_contextHelpShown(false)
+{
+ if (doc) {
+ m_toolBox = new SegmentToolBox(this, doc);
+
+ connect(m_toolBox, SIGNAL(showContextHelp(const QString &)),
+ this, SLOT(slotToolHelpChanged(const QString &)));
+ }
+
+ setDragAutoScroll(true);
+ setBackgroundMode(NoBackground);
+ viewport()->setBackgroundMode(NoBackground);
+ viewport()->setPaletteBackgroundColor(GUIPalette::getColour(GUIPalette::SegmentCanvas));
+
+ slotUpdateSize();
+
+ QScrollBar* hsb = horizontalScrollBar();
+
+ // dynamically adjust content size when scrolling past current composition's end
+ connect(hsb, SIGNAL(nextLine()),
+ this, SLOT(scrollRight()));
+ connect(hsb, SIGNAL(prevLine()),
+ this, SLOT(scrollLeft()));
+
+ // connect(this, SIGNAL(contentsMoving(int, int)),
+ // this, SLOT(slotAllDrawBuffersNeedRefresh()));
+
+ // connect(this, SIGNAL(contentsMoving(int, int)),
+ // this, SLOT(slotContentsMoving(int, int)));
+
+ connect(model, SIGNAL(needContentUpdate()),
+ this, SLOT(slotUpdateSegmentsDrawBuffer()));
+ connect(model, SIGNAL(needContentUpdate(const QRect&)),
+ this, SLOT(slotUpdateSegmentsDrawBuffer(const QRect&)));
+ connect(model, SIGNAL(needArtifactsUpdate()),
+ this, SLOT(slotArtifactsDrawBufferNeedsRefresh()));
+ connect(model, SIGNAL(needSizeUpdate()),
+ this, SLOT(slotUpdateSize()));
+
+ if (doc) {
+ connect(doc, SIGNAL(docColoursChanged()),
+ this, SLOT(slotRefreshColourCache()));
+
+ // recording-related signals
+ connect(doc, SIGNAL(newMIDIRecordingSegment(Segment*)),
+ this, SLOT(slotNewMIDIRecordingSegment(Segment*)));
+ connect(doc, SIGNAL(newAudioRecordingSegment(Segment*)),
+ this, SLOT(slotNewAudioRecordingSegment(Segment*)));
+ // connect(doc, SIGNAL(recordMIDISegmentUpdated(Segment*, timeT)),
+ // this, SLOT(slotRecordMIDISegmentUpdated(Segment*, timeT)));
+ connect(doc, SIGNAL(stoppedAudioRecording()),
+ this, SLOT(slotStoppedRecording()));
+ connect(doc, SIGNAL(stoppedMIDIRecording()),
+ this, SLOT(slotStoppedRecording()));
+ connect(doc, SIGNAL(audioFileFinalized(Segment*)),
+ getModel(), SLOT(slotAudioFileFinalized(Segment*)));
+ }
+
+ CompositionModelImpl* cmi = dynamic_cast<CompositionModelImpl*>(model);
+ if (cmi) {
+ cmi->setAudioPreviewThread(&doc->getAudioPreviewThread());
+ }
+
+ if (doc) {
+ doc->getAudioPreviewThread().setEmptyQueueListener(this);
+ }
+
+ m_segmentsDrawBuffer.setOptimization(QPixmap::BestOptim);
+ m_artifactsDrawBuffer.setOptimization(QPixmap::BestOptim);
+
+ viewport()->setMouseTracking(true);
+}
+
+void CompositionView::endAudioPreviewGeneration()
+{
+ CompositionModelImpl* cmi = dynamic_cast<CompositionModelImpl*>(m_model);
+ if (cmi) {
+ cmi->setAudioPreviewThread(0);
+ }
+}
+
+void CompositionView::setBackgroundPixmap(const QPixmap &m)
+{
+ m_backgroundPixmap = m;
+ // viewport()->setErasePixmap(m_backgroundPixmap);
+}
+
+void CompositionView::initStepSize()
+{
+ QScrollBar* hsb = horizontalScrollBar();
+ m_stepSize = hsb->lineStep();
+}
+
+void CompositionView::slotUpdateSize()
+{
+ int vStep = getModel()->grid().getYSnap();
+ int height = std::max(getModel()->getNbRows() * vStep, (unsigned)visibleHeight());
+
+ RulerScale *ruler = grid().getRulerScale();
+
+ int minWidth = sizeHint().width();
+ int computedWidth = int(nearbyint(ruler->getTotalWidth()));
+
+ int width = std::max(computedWidth, minWidth);
+
+ resizeContents(width, height);
+}
+
+void CompositionView::scrollRight()
+{
+ RG_DEBUG << "CompositionView::scrollRight()\n";
+ if (m_stepSize == 0)
+ initStepSize();
+
+ if (horizontalScrollBar()->value() == horizontalScrollBar()->maxValue()) {
+
+ resizeContents(contentsWidth() + m_stepSize, contentsHeight());
+ setContentsPos(contentsX() + m_stepSize, contentsY());
+ getModel()->setLength(contentsWidth());
+ }
+
+}
+
+void CompositionView::scrollLeft()
+{
+ RG_DEBUG << "CompositionView::scrollLeft()\n";
+ if (m_stepSize == 0)
+ initStepSize();
+
+ int cWidth = contentsWidth();
+
+ if (horizontalScrollBar()->value() < cWidth && cWidth > m_minWidth) {
+ resizeContents(cWidth - m_stepSize, contentsHeight());
+ getModel()->setLength(contentsWidth());
+ }
+
+}
+
+void CompositionView::setSelectionRectPos(const QPoint& pos)
+{
+ m_selectionRect.setRect(pos.x(), pos.y(), 0, 0);
+ getModel()->setSelectionRect(m_selectionRect);
+}
+
+void CompositionView::setSelectionRectSize(int w, int h)
+{
+ m_selectionRect.setSize(QSize(w, h));
+ getModel()->setSelectionRect(m_selectionRect);
+}
+
+void CompositionView::setDrawSelectionRect(bool d)
+{
+ if (m_drawSelectionRect != d) {
+ m_drawSelectionRect = d;
+ slotArtifactsDrawBufferNeedsRefresh();
+ slotUpdateSegmentsDrawBuffer(m_selectionRect);
+ }
+}
+
+void CompositionView::clearSegmentRectsCache(bool clearPreviews)
+{
+ dynamic_cast<CompositionModelImpl*>(getModel())->clearSegmentRectsCache(clearPreviews);
+}
+
+SegmentSelection
+CompositionView::getSelectedSegments()
+{
+ return (dynamic_cast<CompositionModelImpl*>(m_model))->getSelectedSegments();
+}
+
+void CompositionView::updateSelectionContents()
+{
+ if (!haveSelection())
+ return ;
+
+
+ QRect selectionRect = getModel()->getSelectionContentsRect();
+ updateContents(selectionRect);
+}
+
+void CompositionView::slotContentsMoving(int x, int y)
+{
+ // qDebug("contents moving : x=%d", x);
+}
+
+void CompositionView::slotSetTool(const QString& toolName)
+{
+ RG_DEBUG << "CompositionView::slotSetTool(" << toolName << ")"
+ << this << "\n";
+
+ if (m_tool)
+ m_tool->stow();
+
+ m_toolContextHelp = "";
+
+ m_tool = m_toolBox->getTool(toolName);
+
+ if (m_tool)
+ m_tool->ready();
+ else {
+ KMessageBox::error(0, QString("CompositionView::slotSetTool() : unknown tool name %1").arg(toolName));
+ }
+}
+
+void CompositionView::slotSelectSegments(const SegmentSelection &segments)
+{
+ RG_DEBUG << "CompositionView::slotSelectSegments\n";
+
+ static QRect dummy;
+
+ getModel()->clearSelected();
+
+ for (SegmentSelection::iterator i = segments.begin(); i != segments.end(); ++i) {
+ getModel()->setSelected(CompositionItem(new CompositionItemImpl(**i, dummy)));
+ }
+ slotUpdateSegmentsDrawBuffer();
+}
+
+SegmentSelector*
+CompositionView::getSegmentSelectorTool()
+{
+ return dynamic_cast<SegmentSelector*>(getToolBox()->getTool(SegmentSelector::ToolName));
+}
+
+void CompositionView::slotSetSelectAdd(bool value)
+{
+ SegmentSelector* selTool = getSegmentSelectorTool();
+
+ if (!selTool)
+ return ;
+
+ selTool->setSegmentAdd(value);
+}
+
+void CompositionView::slotSetSelectCopy(bool value)
+{
+ SegmentSelector* selTool = getSegmentSelectorTool();
+
+ if (!selTool)
+ return ;
+
+ selTool->setSegmentCopy(value);
+}
+
+void CompositionView::slotShowSplitLine(int x, int y)
+{
+ m_splitLinePos.setX(x);
+ m_splitLinePos.setY(y);
+}
+
+void CompositionView::slotHideSplitLine()
+{
+ m_splitLinePos.setX( -1);
+ m_splitLinePos.setY( -1);
+}
+
+void CompositionView::slotExternalWheelEvent(QWheelEvent* e)
+{
+ e->accept();
+ wheelEvent(e);
+}
+
+CompositionItem CompositionView::getFirstItemAt(QPoint pos)
+{
+ CompositionModel::itemcontainer items = getModel()->getItemsAt(pos);
+
+ if (items.size()) {
+ // find topmost item
+ CompositionItem res = *(items.begin());
+
+ unsigned int maxZ = res->z();
+
+ CompositionModel::itemcontainer::iterator maxZItemPos = items.begin();
+
+ for (CompositionModel::itemcontainer::iterator i = items.begin();
+ i != items.end(); ++i) {
+ CompositionItem ic = *i;
+ if (ic->z() > maxZ) {
+ RG_DEBUG << k_funcinfo << "found new topmost at z=" << ic->z() << endl;
+ res = ic;
+ maxZ = ic->z();
+ maxZItemPos = i;
+ }
+ }
+
+ // get rid of the rest;
+ items.erase(maxZItemPos);
+ for (CompositionModel::itemcontainer::iterator i = items.begin();
+ i != items.end(); ++i)
+ delete *i;
+
+ return res;
+ } else {
+ RG_DEBUG << k_funcinfo << "no item under cursor\n";
+ }
+
+
+ return CompositionItem();
+}
+
+void CompositionView::setSnapGrain(bool fine)
+{
+ if (m_fineGrain) {
+ grid().setSnapTime(SnapGrid::NoSnap);
+ } else {
+ grid().setSnapTime(fine ? SnapGrid::SnapToBeat : SnapGrid::SnapToBar);
+ }
+}
+
+void CompositionView::slotUpdateSegmentsDrawBuffer()
+{
+ // RG_DEBUG << "CompositionView::slotUpdateSegmentsDrawBuffer()\n";
+ slotAllDrawBuffersNeedRefresh();
+ updateContents();
+}
+
+void CompositionView::slotUpdateSegmentsDrawBuffer(const QRect& rect)
+{
+ // RG_DEBUG << "CompositionView::slotUpdateSegmentsDrawBuffer() rect "
+ // << rect << " - valid : " << rect.isValid() << endl;
+
+ slotAllDrawBuffersNeedRefresh(rect);
+
+ if (rect.isValid()) {
+ updateContents(rect);
+ } else {
+ updateContents();
+ }
+}
+
+void CompositionView::slotRefreshColourCache()
+{
+ CompositionColourCache::getInstance()->init();
+ clearSegmentRectsCache();
+ slotUpdateSegmentsDrawBuffer();
+}
+
+void CompositionView::slotNewMIDIRecordingSegment(Segment* s)
+{
+ getModel()->addRecordingItem(CompositionItemHelper::makeCompositionItem(s));
+}
+
+void CompositionView::slotNewAudioRecordingSegment(Segment* s)
+{
+ getModel()->addRecordingItem(CompositionItemHelper::makeCompositionItem(s));
+}
+
+void CompositionView::slotStoppedRecording()
+{
+ getModel()->clearRecordingItems();
+}
+
+void CompositionView::resizeEvent(QResizeEvent* e)
+{
+ QScrollView::resizeEvent(e);
+ slotUpdateSize();
+
+ int w = std::max(m_segmentsDrawBuffer.width(), visibleWidth());
+ int h = std::max(m_segmentsDrawBuffer.height(), visibleHeight());
+
+ m_segmentsDrawBuffer.resize(w, h);
+ m_artifactsDrawBuffer.resize(w, h);
+ slotAllDrawBuffersNeedRefresh();
+ // RG_DEBUG << "CompositionView::resizeEvent() : drawBuffer size = " << m_segmentsDrawBuffer.size() << endl;
+}
+
+void CompositionView::viewportPaintEvent(QPaintEvent* e)
+{
+ QMemArray<QRect> rects = e->region().rects();
+
+ for (unsigned int i = 0; i < rects.size(); ++i) {
+ viewportPaintRect(rects[i]);
+ }
+}
+
+void CompositionView::viewportPaintRect(QRect r)
+{
+ QRect updateRect = r;
+
+ r &= viewport()->rect();
+ r.moveBy(contentsX(), contentsY());
+
+ // RG_DEBUG << "CompositionView::viewportPaintRect() r = " << r
+ // << " - moveBy " << contentsX() << "," << contentsY() << " - updateRect = " << updateRect
+ // << " - refresh " << m_segmentsDrawBufferRefresh << " artrefresh " << m_artifactsDrawBufferRefresh << endl;
+
+
+ bool scroll = false;
+ bool changed = checkScrollAndRefreshDrawBuffer(r, scroll);
+
+ if (changed || m_artifactsDrawBufferRefresh.isValid()) {
+
+ // r was modified by checkScrollAndRefreshDrawBuffer
+ QRect copyRect(r | m_artifactsDrawBufferRefresh);
+ copyRect.moveBy( -contentsX(), -contentsY());
+
+ // RG_DEBUG << "copying from segment to artifacts buffer: " << copyRect << endl;
+
+ bitBlt(&m_artifactsDrawBuffer,
+ copyRect.x(), copyRect.y(),
+ &m_segmentsDrawBuffer,
+ copyRect.x(), copyRect.y(), copyRect.width(), copyRect.height());
+ m_artifactsDrawBufferRefresh |= r;
+ }
+
+ if (m_artifactsDrawBufferRefresh.isValid()) {
+ refreshArtifactsDrawBuffer(m_artifactsDrawBufferRefresh);
+ m_artifactsDrawBufferRefresh = QRect();
+ }
+
+ if (scroll) {
+ bitBlt(viewport(), 0, 0,
+ &m_artifactsDrawBuffer, 0, 0,
+ m_artifactsDrawBuffer.width(), m_artifactsDrawBuffer.height());
+ } else {
+ bitBlt(viewport(), updateRect.x(), updateRect.y(),
+ &m_artifactsDrawBuffer, updateRect.x(), updateRect.y(),
+ updateRect.width(), updateRect.height());
+ }
+
+ // DEBUG
+
+ // QPainter pdebug(viewport());
+ // static QPen framePen(Qt::red, 1);
+ // pdebug.setPen(framePen);
+ // pdebug.drawRect(updateRect);
+
+}
+
+bool CompositionView::checkScrollAndRefreshDrawBuffer(QRect &rect, bool& scroll)
+{
+ bool all = false;
+ QRect refreshRect = m_segmentsDrawBufferRefresh;
+
+ int w = visibleWidth(), h = visibleHeight();
+ int cx = contentsX(), cy = contentsY();
+
+ scroll = (cx != m_lastBufferRefreshX || cy != m_lastBufferRefreshY);
+
+ if (scroll) {
+
+ // RG_DEBUG << "checkScrollAndRefreshDrawBuffer: scrolling by ("
+ // << cx - m_lastBufferRefreshX << "," << cy - m_lastBufferRefreshY << ")" << endl;
+
+ if (refreshRect.isValid()) {
+
+ // If we've scrolled and there was an existing refresh
+ // rect, we can't be sure whether the refresh rect
+ // predated or postdated the internal update of scroll
+ // location. Cut our losses and refresh everything.
+
+ refreshRect.setRect(cx, cy, w, h);
+
+ } else {
+
+ // No existing refresh rect: we only need to handle the
+ // scroll
+
+ if (cx != m_lastBufferRefreshX) {
+
+ int dx = m_lastBufferRefreshX - cx;
+
+ if (dx > -w && dx < w) {
+
+ QPainter cp(&m_segmentsDrawBuffer);
+ cp.drawPixmap(dx, 0, m_segmentsDrawBuffer);
+
+ if (dx < 0) {
+ refreshRect |= QRect(cx + w + dx, cy, -dx, h);
+ } else {
+ refreshRect |= QRect(cx, cy, dx, h);
+ }
+
+ } else {
+
+ refreshRect.setRect(cx, cy, w, h);
+ all = true;
+ }
+ }
+
+ if (cy != m_lastBufferRefreshY && !all) {
+
+ int dy = m_lastBufferRefreshY - cy;
+
+ if (dy > -h && dy < h) {
+
+ QPainter cp(&m_segmentsDrawBuffer);
+ cp.drawPixmap(0, dy, m_segmentsDrawBuffer);
+
+ if (dy < 0) {
+ refreshRect |= QRect(cx, cy + h + dy, w, -dy);
+ } else {
+ refreshRect |= QRect(cx, cy, w, dy);
+ }
+
+ } else {
+
+ refreshRect.setRect(cx, cy, w, h);
+ all = true;
+ }
+ }
+ }
+ }
+
+ bool needRefresh = false;
+
+ if (refreshRect.isValid()) {
+ needRefresh = true;
+ }
+
+ if (needRefresh)
+ refreshSegmentsDrawBuffer(refreshRect);
+
+ m_segmentsDrawBufferRefresh = QRect();
+ m_lastBufferRefreshX = cx;
+ m_lastBufferRefreshY = cy;
+
+ rect |= refreshRect;
+ if (scroll)
+ rect.setRect(cx, cy, w, h);
+ return needRefresh;
+}
+
+void CompositionView::refreshSegmentsDrawBuffer(const QRect& rect)
+{
+ // Profiler profiler("CompositionView::refreshDrawBuffer", true);
+ // RG_DEBUG << "CompositionView::refreshSegmentsDrawBuffer() r = "
+ // << rect << endl;
+
+ QPainter p(&m_segmentsDrawBuffer, viewport());
+ p.translate( -contentsX(), -contentsY());
+
+ if (!m_backgroundPixmap.isNull()) {
+ QPoint pp(rect.x() % m_backgroundPixmap.height(), rect.y() % m_backgroundPixmap.width());
+ p.drawTiledPixmap(rect, m_backgroundPixmap, pp);
+ } else {
+ p.eraseRect(rect);
+ }
+
+ drawArea(&p, rect);
+
+ // DEBUG - show what's updated
+ // QPen framePen(Qt::red, 1);
+ // p.setPen(framePen);
+ // p.drawRect(rect);
+
+ // m_segmentsDrawBufferNeedsRefresh = false;
+}
+
+void CompositionView::refreshArtifactsDrawBuffer(const QRect& rect)
+{
+ // RG_DEBUG << "CompositionView::refreshArtifactsDrawBuffer() r = "
+ // << rect << endl;
+
+ QPainter p;
+ p.begin(&m_artifactsDrawBuffer, viewport());
+ p.translate( -contentsX(), -contentsY());
+ // QRect r(contentsX(), contentsY(), m_artifactsDrawBuffer.width(), m_artifactsDrawBuffer.height());
+ drawAreaArtifacts(&p, rect);
+ p.end();
+
+ // m_artifactsDrawBufferNeedsRefresh = false;
+}
+
+void CompositionView::drawArea(QPainter *p, const QRect& clipRect)
+{
+ // Profiler profiler("CompositionView::drawArea", true);
+
+ // RG_DEBUG << "CompositionView::drawArea() clipRect = " << clipRect << endl;
+
+ //
+ // Fetch track dividing lines
+ //
+ CompositionModel::heightlist lineHeights = getModel()->getTrackDividersIn(clipRect);
+
+ if (!lineHeights.empty()) {
+
+ p->save();
+ QColor light = m_trackDividerColor.light();
+ p->setPen(light);
+
+ for (CompositionModel::heightlist::const_iterator hi = lineHeights.begin();
+ hi != lineHeights.end(); ++hi) {
+ int y = *hi;
+ if (y-1 >= clipRect.y()) {
+ p->drawLine(clipRect.x(), y-1,
+ clipRect.x() + clipRect.width() - 1, y-1);
+ }
+ if (y >= clipRect.y()) {
+ p->drawLine(clipRect.x(), y,
+ clipRect.x() + clipRect.width() - 1, y);
+ }
+ }
+
+ p->setPen(m_trackDividerColor);
+
+ for (CompositionModel::heightlist::const_iterator hi = lineHeights.begin();
+ hi != lineHeights.end(); ++hi) {
+ int y = *hi;
+ if (y-2 >= clipRect.y()) {
+ p->drawLine(clipRect.x(), y-2,
+ clipRect.x() + clipRect.width() - 1, y-2);
+ }
+ if (y+1 >= clipRect.y()) {
+ p->drawLine(clipRect.x(), y+1,
+ clipRect.x() + clipRect.width() - 1, y+1);
+ }
+ }
+
+ p->restore();
+ }
+
+ CompositionModel::AudioPreviewDrawData* audioPreviewData = 0;
+ CompositionModel::RectRanges* notationPreviewData = 0;
+
+ //
+ // Fetch previews
+ //
+ if (m_showPreviews) {
+ notationPreviewData = &m_notationPreviewRects;
+ m_notationPreviewRects.clear();
+ audioPreviewData = &m_audioPreviewRects;
+ m_audioPreviewRects.clear();
+ }
+
+ //
+ // Fetch segment rectangles to draw
+ //
+ const CompositionModel::rectcontainer& rects = getModel()->getRectanglesIn(clipRect,
+ notationPreviewData, audioPreviewData);
+ CompositionModel::rectcontainer::const_iterator i = rects.begin();
+ CompositionModel::rectcontainer::const_iterator end = rects.end();
+
+ //
+ // Draw Segment Rectangles
+ //
+ p->save();
+ for (; i != end; ++i) {
+ p->setBrush(i->getBrush());
+ p->setPen(i->getPen());
+
+ // RG_DEBUG << "CompositionView::drawArea : draw comp rect " << *i << endl;
+ drawCompRect(*i, p, clipRect);
+ }
+
+ p->restore();
+
+ if (rects.size() > 1) {
+ // RG_DEBUG << "CompositionView::drawArea : drawing intersections\n";
+ drawIntersections(rects, p, clipRect);
+ }
+
+ //
+ // Previews
+ //
+ if (m_showPreviews) {
+ p->save();
+
+ // draw audio previews
+ //
+ drawAreaAudioPreviews(p, clipRect);
+
+ // draw notation previews
+ //
+ CompositionModel::RectRanges::const_iterator npi = m_notationPreviewRects.begin();
+ CompositionModel::RectRanges::const_iterator npEnd = m_notationPreviewRects.end();
+
+ for (; npi != npEnd; ++npi) {
+ CompositionModel::RectRange interval = *npi;
+ p->save();
+ p->translate(interval.basePoint.x(), interval.basePoint.y());
+ // RG_DEBUG << "CompositionView::drawArea : translating to x = " << interval.basePoint.x() << endl;
+ for (; interval.range.first != interval.range.second; ++interval.range.first) {
+
+ const PreviewRect& pr = *(interval.range.first);
+ QColor defaultCol = CompositionColourCache::getInstance()->SegmentInternalPreview;
+ QColor col = interval.color.isValid() ? interval.color : defaultCol;
+ p->setBrush(col);
+ p->setPen(col);
+ // RG_DEBUG << "CompositionView::drawArea : drawing preview rect at x = " << pr.x() << endl;
+ p->drawRect(pr);
+ }
+ p->restore();
+ }
+
+ p->restore();
+ }
+
+ //
+ // Draw segment labels (they must be drawn over the preview rects)
+ //
+ if (m_showSegmentLabels) {
+ for (i = rects.begin(); i != end; ++i) {
+ drawCompRectLabel(*i, p, clipRect);
+ }
+ }
+
+ // drawAreaArtifacts(p, clipRect);
+
+}
+
+void CompositionView::drawAreaAudioPreviews(QPainter * p, const QRect& clipRect)
+{
+ CompositionModel::AudioPreviewDrawData::const_iterator api = m_audioPreviewRects.begin();
+ CompositionModel::AudioPreviewDrawData::const_iterator apEnd = m_audioPreviewRects.end();
+ QRect rectToFill, // rect to fill on canvas
+ localRect; // the rect of the tile to draw on the canvas
+ QPoint basePoint, // origin of segment rect
+ drawBasePoint; // origin of rect to fill on canvas
+ QRect r;
+ for (; api != apEnd; ++api) {
+ rectToFill = api->rect;
+ basePoint = api->basePoint;
+ rectToFill.moveTopLeft(basePoint);
+ rectToFill &= clipRect;
+ r = rectToFill;
+ drawBasePoint = rectToFill.topLeft();
+ rectToFill.moveBy( -basePoint.x(), -basePoint.y());
+ int firstPixmapIdx = (r.x() - basePoint.x()) / AudioPreviewPainter::tileWidth();
+ if (firstPixmapIdx >= api->pixmap.size()) {
+ // RG_DEBUG << "CompositionView::drawAreaAudioPreviews : WARNING - miscomputed pixmap array : r.x = "
+ // << r.x() << " - basePoint.x = " << basePoint.x() << " - firstPixmapIdx = " << firstPixmapIdx
+ // << endl;
+ continue;
+ }
+ int x = 0, idx = firstPixmapIdx;
+ // RG_DEBUG << "CompositionView::drawAreaAudioPreviews : clipRect = " << clipRect
+ // << " - firstPixmapIdx = " << firstPixmapIdx << endl;
+ while (x < clipRect.width()) {
+ int pixmapRectXOffset = idx * AudioPreviewPainter::tileWidth();
+ localRect.setRect(basePoint.x() + pixmapRectXOffset, basePoint.y(),
+ AudioPreviewPainter::tileWidth(), api->rect.height());
+ // RG_DEBUG << "CompositionView::drawAreaAudioPreviews : initial localRect = "
+ // << localRect << endl;
+ localRect &= r;
+ if (idx == firstPixmapIdx && api->resizeOffset != 0) {
+ // this segment is being resized from start, clip beginning of preview
+ localRect.moveBy(api->resizeOffset, 0);
+ }
+
+ // RG_DEBUG << "CompositionView::drawAreaAudioPreviews : localRect & clipRect = "
+ // << localRect << endl;
+ if (localRect.isEmpty()) {
+ // RG_DEBUG << "CompositionView::drawAreaAudioPreviews : localRect & clipRect is empty\n";
+ break;
+ }
+ localRect.moveBy( -(basePoint.x() + pixmapRectXOffset), -basePoint.y());
+
+ // RG_DEBUG << "CompositionView::drawAreaAudioPreviews : drawing pixmap "
+ // << idx << " at " << drawBasePoint << " - localRect = " << localRect
+ // << " - preResizeOrigin : " << api->preResizeOrigin << endl;
+
+ p->drawImage(drawBasePoint, api->pixmap[idx], localRect,
+ Qt::ColorOnly | Qt::ThresholdDither | Qt::AvoidDither);
+
+ ++idx;
+ if (idx >= api->pixmap.size())
+ break;
+ drawBasePoint.setX(drawBasePoint.x() + localRect.width());
+ x += localRect.width();
+ }
+ }
+}
+
+void CompositionView::drawAreaArtifacts(QPainter * p, const QRect& clipRect)
+{
+ //
+ // Playback Pointer
+ //
+ drawPointer(p, clipRect);
+
+ //
+ // Tmp rect (rect displayed while drawing a new segment)
+ //
+ if (m_tmpRect.isValid() && m_tmpRect.intersects(clipRect)) {
+ p->setBrush(m_tmpRectFill);
+ p->setPen(CompositionColourCache::getInstance()->SegmentBorder);
+ drawRect(m_tmpRect, p, clipRect);
+ }
+
+ //
+ // Tool guides (crosshairs)
+ //
+ if (m_drawGuides)
+ drawGuides(p, clipRect);
+
+ //
+ // Selection Rect
+ //
+ if (m_drawSelectionRect) {
+ drawRect(m_selectionRect, p, clipRect, false, 0, false);
+ }
+
+ //
+ // Floating Text
+ //
+ if (m_drawTextFloat)
+ drawTextFloat(p, clipRect);
+
+ //
+ // Split line
+ //
+ if (m_splitLinePos.x() > 0 && clipRect.contains(m_splitLinePos)) {
+ p->save();
+ p->setPen(m_guideColor);
+ p->drawLine(m_splitLinePos.x(), m_splitLinePos.y(),
+ m_splitLinePos.x(), m_splitLinePos.y() + getModel()->grid().getYSnap());
+ p->restore();
+ }
+}
+
+void CompositionView::drawGuides(QPainter * p, const QRect& /*clipRect*/)
+{
+ // no need to check for clipping, these guides are meant to follow the mouse cursor
+ QPoint guideOrig(m_topGuidePos, m_foreGuidePos);
+
+ p->save();
+ p->setPen(m_guideColor);
+ p->drawLine(guideOrig.x(), 0, guideOrig.x(), contentsHeight());
+ p->drawLine(0, guideOrig.y(), contentsWidth(), guideOrig.y());
+ p->restore();
+}
+
+void CompositionView::drawCompRect(const CompositionRect& r, QPainter *p, const QRect& clipRect,
+ int intersectLvl, bool fill)
+{
+ p->save();
+
+ QBrush brush = r.getBrush();
+
+ if (r.isRepeating()) {
+ QColor brushColor = brush.color();
+ brush.setColor(brushColor.light(150));
+ }
+
+ p->setBrush(brush);
+ p->setPen(r.getPen());
+ drawRect(r, p, clipRect, r.isSelected(), intersectLvl, fill);
+
+ if (r.isRepeating()) {
+
+ CompositionRect::repeatmarks repeatMarks = r.getRepeatMarks();
+
+ // RG_DEBUG << "CompositionView::drawCompRect() : drawing repeating rect " << r
+ // << " nb repeat marks = " << repeatMarks.size() << endl;
+
+ // draw 'start' rectangle with original brush
+ //
+ QRect startRect = r;
+ startRect.setWidth(repeatMarks[0] - r.x());
+ p->setBrush(r.getBrush());
+ drawRect(startRect, p, clipRect, r.isSelected(), intersectLvl, fill);
+
+
+ // now draw the 'repeat' marks
+ //
+ p->setPen(CompositionColourCache::getInstance()->RepeatSegmentBorder);
+ int penWidth = std::max(r.getPen().width(), 1u);
+
+ for (unsigned int i = 0; i < repeatMarks.size(); ++i) {
+ int pos = repeatMarks[i];
+ if (pos > clipRect.right())
+ break;
+
+ if (pos >= clipRect.left()) {
+ QPoint p1(pos, r.y() + penWidth),
+ p2(pos, r.y() + r.height() - penWidth - 1);
+
+ // RG_DEBUG << "CompositionView::drawCompRect() : drawing repeat mark at "
+ // << p1 << "-" << p2 << endl;
+ p->drawLine(p1, p2);
+ }
+
+ }
+
+ }
+
+ p->restore();
+}
+
+void CompositionView::drawCompRectLabel(const CompositionRect& r, QPainter *p, const QRect& clipRect)
+{
+ // draw segment label
+ //
+#ifdef NOT_DEFINED
+ if (!r.getLabel().isEmpty() /* && !r.isSelected() */)
+ {
+ p->save();
+ p->setPen(GUIPalette::getColour(GUIPalette::SegmentLabel));
+ p->setBrush(white);
+ QRect textRect(r);
+ textRect.setX(textRect.x() + 3);
+ QString label = " " + r.getLabel() + " ";
+ QRect textBoundingRect = p->boundingRect(textRect, Qt::AlignLeft | Qt::AlignVCenter, label);
+ p->drawRect(textBoundingRect & r);
+ p->drawText(textRect, Qt::AlignLeft | Qt::AlignVCenter, label);
+ p->restore();
+ }
+#else
+ if (!r.getLabel().isEmpty()) {
+
+ p->save();
+
+ QFont font;
+ font.setPixelSize(r.height() / 2.2);
+ font.setWeight(QFont::Bold);
+ font.setItalic(false);
+ p->setFont(font);
+
+ QRect labelRect = QRect
+ (r.x(),
+ r.y() + ((r.height() - p->fontMetrics().height()) / 2) + 1,
+ r.width(),
+ p->fontMetrics().height());
+
+ int x = labelRect.x() + p->fontMetrics().width('x');
+ int y = labelRect.y();
+
+ QBrush brush = r.getBrush();
+ QColor surroundColour = brush.color().light(110);
+
+ int h, s, v;
+ surroundColour.hsv(&h, &s, &v);
+ if (v < 150)
+ surroundColour.setHsv(h, s, 225);
+ p->setPen(surroundColour);
+
+ for (int i = 0; i < 9; ++i) {
+
+ if (i == 4)
+ continue;
+
+ int wx = x, wy = y;
+
+ if (i < 3)
+ --wx;
+ if (i > 5)
+ ++wx;
+ if (i % 3 == 0)
+ --wy;
+ if (i % 3 == 2)
+ ++wy;
+
+ labelRect.setX(wx);
+ labelRect.setY(wy);
+
+ p->drawText(labelRect,
+ Qt::AlignLeft | Qt::AlignTop,
+ r.getLabel());
+ }
+
+ labelRect.setX(x);
+ labelRect.setY(y);
+
+ p->setPen(GUIPalette::getColour
+ (GUIPalette::SegmentLabel));
+ p->drawText(labelRect,
+ Qt::AlignLeft | Qt::AlignVCenter, r.getLabel());
+ p->restore();
+ }
+#endif
+}
+
+void CompositionView::drawRect(const QRect& r, QPainter *p, const QRect& clipRect,
+ bool isSelected, int intersectLvl, bool fill)
+{
+ // RG_DEBUG << "CompositionView::drawRect : intersectLvl = " << intersectLvl
+ // << " - brush col = " << p->brush().color() << endl;
+
+ // RG_DEBUG << "CompositionView::drawRect " << r << " - xformed : " << p->xForm(r)
+ // << " - contents x = " << contentsX() << ", contents y = " << contentsY() << endl;
+
+ p->save();
+
+ QRect rect = r;
+
+ if (fill) {
+ if (isSelected) {
+ QColor fillColor = p->brush().color();
+ fillColor = fillColor.dark(200);
+ QBrush b = p->brush();
+ b.setColor(fillColor);
+ p->setBrush(b);
+ // RG_DEBUG << "CompositionView::drawRect : selected color : " << fillColor << endl;
+ }
+
+ if (intersectLvl > 0) {
+ QColor fillColor = p->brush().color();
+ fillColor = fillColor.dark((intersectLvl) * 105);
+ QBrush b = p->brush();
+ b.setColor(fillColor);
+ p->setBrush(b);
+ // RG_DEBUG << "CompositionView::drawRect : intersected color : " << fillColor << " isSelected : " << isSelected << endl;
+ }
+ } else {
+ p->setBrush(Qt::NoBrush);
+ }
+
+ // Paint using the small coordinates...
+ QRect intersection = rect.intersect(clipRect);
+
+ if (clipRect.contains(rect)) {
+ p->drawRect(rect);
+ } else {
+ // draw only what's necessary
+ if (!intersection.isEmpty() && fill)
+ p->fillRect(intersection, p->brush());
+
+ int rectTopY = rect.y();
+
+ if (rectTopY >= clipRect.y() &&
+ rectTopY <= (clipRect.y() + clipRect.height())) {
+ // to prevent overflow, in case the original rect is too wide
+ // the line would be drawn "backwards"
+ p->drawLine(intersection.topLeft(), intersection.topRight());
+ }
+
+ int rectBottomY = rect.y() + rect.height();
+ if (rectBottomY >= clipRect.y() &&
+ rectBottomY <= (clipRect.y() + clipRect.height()))
+ // to prevent overflow, in case the original rect is too wide
+ // the line would be drawn "backwards"
+ p->drawLine(intersection.bottomLeft(), intersection.bottomRight());
+
+ int rectLeftX = rect.x();
+ if (rectLeftX >= clipRect.x() &&
+ rectLeftX <= (clipRect.x() + clipRect.width()))
+ p->drawLine(rect.topLeft(), rect.bottomLeft());
+
+ unsigned int rectRightX = rect.x() + rect.width(); // make sure we don't overflow
+ if (rectRightX >= unsigned(clipRect.x()) &&
+ rectRightX <= unsigned(clipRect.x() + clipRect.width()))
+ p->drawLine(rect.topRight(), rect.bottomRight());
+
+ }
+
+ p->restore();
+}
+
+QColor CompositionView::mixBrushes(QBrush a, QBrush b)
+{
+ QColor ac = a.color(), bc = b.color();
+
+ int aR = ac.red(), aG = ac.green(), aB = ac.blue(),
+ bR = bc.red(), bG = bc.green(), bB = ac.blue();
+
+ ac.setRgb((aR + bR) / 2, (aG + bG) / 2, (aB + bB) / 2);
+
+ return ac;
+}
+
+void CompositionView::drawIntersections(const CompositionModel::rectcontainer& rects,
+ QPainter * p, const QRect& clipRect)
+{
+ if (! (rects.size() > 1))
+ return ;
+
+ CompositionModel::rectcontainer intersections;
+
+ CompositionModel::rectcontainer::const_iterator i = rects.begin(),
+ j = rects.begin();
+
+ for (; j != rects.end(); ++j) {
+
+ CompositionRect testRect = *j;
+ i = j;
+ ++i; // set i to pos after j
+
+ if (i == rects.end())
+ break;
+
+ for (; i != rects.end(); ++i) {
+ CompositionRect ri = testRect.intersect(*i);
+ if (!ri.isEmpty()) {
+ CompositionModel::rectcontainer::iterator t = std::find(intersections.begin(),
+ intersections.end(), ri);
+ if (t == intersections.end()) {
+ ri.setBrush(mixBrushes(testRect.getBrush(), i->getBrush()));
+ ri.setSelected(testRect.isSelected() || i->isSelected());
+ intersections.push_back(ri);
+ }
+
+ }
+ }
+ }
+
+ //
+ // draw this level of intersections then compute and draw further ones
+ //
+ int intersectionLvl = 1;
+
+ while (!intersections.empty()) {
+
+ for (CompositionModel::rectcontainer::iterator intIter = intersections.begin();
+ intIter != intersections.end(); ++intIter) {
+ CompositionRect r = *intIter;
+ drawCompRect(r, p, clipRect, intersectionLvl);
+ }
+
+ if (intersections.size() > 10)
+ break; // put a limit on how many intersections we can compute and draw - this grows exponentially
+
+ ++intersectionLvl;
+
+ CompositionModel::rectcontainer intersections2;
+
+ CompositionModel::rectcontainer::iterator i = intersections.begin(),
+ j = intersections.begin();
+
+ for (; j != intersections.end(); ++j) {
+
+ CompositionRect testRect = *j;
+ i = j;
+ ++i; // set i to pos after j
+
+ if (i == intersections.end())
+ break;
+
+ for (; i != intersections.end(); ++i) {
+ CompositionRect ri = testRect.intersect(*i);
+ if (!ri.isEmpty() && ri != *i) {
+ CompositionModel::rectcontainer::iterator t = std::find(intersections2.begin(),
+ intersections2.end(), ri);
+ if (t == intersections2.end())
+ ri.setBrush(mixBrushes(testRect.getBrush(), i->getBrush()));
+ intersections2.push_back(ri);
+ }
+ }
+ }
+
+ intersections = intersections2;
+ }
+
+}
+
+void CompositionView::drawPointer(QPainter *p, const QRect& clipRect)
+{
+ // RG_DEBUG << "CompositionView::drawPointer: clipRect "
+ // << clipRect.x() << "," << clipRect.y() << " " << clipRect.width()
+ // << "x" << clipRect.height() << " pointer pos is " << m_pointerPos << endl;
+
+ if (m_pointerPos >= clipRect.x() && m_pointerPos <= (clipRect.x() + clipRect.width())) {
+ p->save();
+ p->setPen(m_pointerPen);
+ p->drawLine(m_pointerPos, clipRect.y(), m_pointerPos, clipRect.y() + clipRect.height());
+ p->restore();
+ }
+
+}
+
+void CompositionView::drawTextFloat(QPainter *p, const QRect& clipRect)
+{
+ QFontMetrics metrics(p->fontMetrics());
+
+ QRect bound = p->boundingRect(0, 0, 300, metrics.height() + 6, AlignAuto, m_textFloatText);
+
+ p->save();
+
+ bound.setLeft(bound.left() - 2);
+ bound.setRight(bound.right() + 2);
+ bound.setTop(bound.top() - 2);
+ bound.setBottom(bound.bottom() + 2);
+
+ QPoint pos(m_textFloatPos);
+ if (pos.y() < 0 && getModel()) {
+ if (pos.y() + bound.height() < 0) {
+ pos.setY(pos.y() + getModel()->grid().getYSnap() * 3);
+ } else {
+ pos.setY(pos.y() + getModel()->grid().getYSnap() * 2);
+ }
+ }
+
+ bound.moveTopLeft(pos);
+
+ if (bound.intersects(clipRect)) {
+
+ p->setBrush(CompositionColourCache::getInstance()->RotaryFloatBackground);
+
+ drawRect(bound, p, clipRect, false, 0, true);
+
+ p->setPen(CompositionColourCache::getInstance()->RotaryFloatForeground);
+
+ p->drawText(pos.x() + 2, pos.y() + 3 + metrics.ascent(), m_textFloatText);
+
+ }
+
+ p->restore();
+}
+
+bool CompositionView::event(QEvent* e)
+{
+ if (e->type() == AudioPreviewThread::AudioPreviewQueueEmpty) {
+ RG_DEBUG << "CompositionView::event - AudioPreviewQueueEmpty\n";
+ slotSegmentsDrawBufferNeedsRefresh();
+ viewport()->update();
+ return true;
+ }
+
+ return RosegardenScrollView::event(e);
+}
+
+void CompositionView::enterEvent(QEvent *e)
+{
+ kapp->config()->setGroup(GeneralOptionsConfigGroup);
+ if (!kapp->config()->readBoolEntry("toolcontexthelp", true)) return;
+
+ emit showContextHelp(m_toolContextHelp);
+ m_contextHelpShown = true;
+}
+
+void CompositionView::leaveEvent(QEvent *e)
+{
+ emit showContextHelp("");
+ m_contextHelpShown = false;
+}
+
+void CompositionView::slotToolHelpChanged(const QString &text)
+{
+ if (m_toolContextHelp == text) return;
+ m_toolContextHelp = text;
+
+ kapp->config()->setGroup(GeneralOptionsConfigGroup);
+ if (!kapp->config()->readBoolEntry("toolcontexthelp", true)) return;
+
+ if (m_contextHelpShown) emit showContextHelp(text);
+}
+
+void CompositionView::contentsMousePressEvent(QMouseEvent* e)
+{
+ Qt::ButtonState bs = e->state();
+ slotSetSelectCopy((bs & Qt::ControlButton) != 0);
+ slotSetSelectAdd((bs & Qt::ShiftButton) != 0);
+ slotSetFineGrain((bs & Qt::ShiftButton) != 0);
+ slotSetPencilOverExisting((bs & Qt::AltButton + Qt::ControlButton) != 0);
+
+ switch (e->button()) {
+ case LeftButton:
+ case MidButton:
+ startAutoScroll();
+
+ if (m_tool)
+ m_tool->handleMouseButtonPress(e);
+ else
+ RG_DEBUG << "CompositionView::contentsMousePressEvent() :"
+ << this << " no tool\n";
+ break;
+ case RightButton:
+ if (m_tool)
+ m_tool->handleRightButtonPress(e);
+ else
+ RG_DEBUG << "CompositionView::contentsMousePressEvent() :"
+ << this << " no tool\n";
+ break;
+ default:
+ break;
+ }
+}
+
+void CompositionView::contentsMouseReleaseEvent(QMouseEvent* e)
+{
+ RG_DEBUG << "CompositionView::contentsMouseReleaseEvent()\n";
+
+ stopAutoScroll();
+
+ if (!m_tool)
+ return ;
+
+ if (e->button() == LeftButton ||
+ e->button() == MidButton )
+ m_tool->handleMouseButtonRelease(e);
+}
+
+void CompositionView::contentsMouseDoubleClickEvent(QMouseEvent* e)
+{
+ m_currentItem = getFirstItemAt(e->pos());
+
+ if (!m_currentItem) {
+ RG_DEBUG << "CompositionView::contentsMouseDoubleClickEvent - no currentItem\n";
+ RulerScale *ruler = grid().getRulerScale();
+ if (ruler) emit setPointerPosition(ruler->getTimeForX(e->pos().x()));
+ return ;
+ }
+
+ RG_DEBUG << "CompositionView::contentsMouseDoubleClickEvent - have currentItem\n";
+
+ CompositionItemImpl* itemImpl = dynamic_cast<CompositionItemImpl*>((_CompositionItem*)m_currentItem);
+
+ if (m_currentItem->isRepeating()) {
+ timeT time = getModel()->getRepeatTimeAt(e->pos(), m_currentItem);
+
+ RG_DEBUG << "editRepeat at time " << time << endl;
+ if (time > 0)
+ emit editRepeat(itemImpl->getSegment(), time);
+ else
+ emit editSegment(itemImpl->getSegment());
+
+ } else {
+
+ emit editSegment(itemImpl->getSegment());
+ }
+}
+
+void CompositionView::contentsMouseMoveEvent(QMouseEvent* e)
+{
+ if (!m_tool)
+ return ;
+
+ Qt::ButtonState bs = e->state();
+ slotSetFineGrain((bs & Qt::ShiftButton) != 0);
+ slotSetPencilOverExisting((bs & Qt::AltButton) != 0);
+
+ int follow = m_tool->handleMouseMove(e);
+ setScrollDirectionConstraint(follow);
+
+ if (follow != RosegardenCanvasView::NoFollow) {
+ doAutoScroll();
+
+ if (follow & RosegardenCanvasView::FollowHorizontal) {
+ slotScrollHorizSmallSteps(e->pos().x());
+
+ // enlarge composition if needed
+ if (horizontalScrollBar()->value() == horizontalScrollBar()->maxValue()) {
+ resizeContents(contentsWidth() + m_stepSize, contentsHeight());
+ setContentsPos(contentsX() + m_stepSize, contentsY());
+ getModel()->setLength(contentsWidth());
+ slotUpdateSize();
+ }
+ }
+
+ if (follow & RosegardenCanvasView::FollowVertical)
+ slotScrollVertSmallSteps(e->pos().y());
+ }
+}
+
+void CompositionView::releaseCurrentItem()
+{
+ m_currentItem = CompositionItem();
+}
+
+void CompositionView::setPointerPos(int pos)
+{
+ // RG_DEBUG << "CompositionView::setPointerPos(" << pos << ")\n";
+ int oldPos = m_pointerPos;
+ if (oldPos == pos)
+ return ;
+
+ m_pointerPos = pos;
+ getModel()->setPointerPos(pos);
+
+ // automagically grow contents width if pointer position goes beyond right end
+ //
+ if (pos >= (contentsWidth() - m_stepSize)) {
+ resizeContents(pos + m_stepSize, contentsHeight());
+ // grow composition too, if needed (it may not be the case if
+ if (getModel()->getLength() < contentsWidth())
+ getModel()->setLength(contentsWidth());
+ }
+
+
+ // interesting -- isAutoScrolling() never seems to return true?
+ // RG_DEBUG << "CompositionView::setPointerPos(" << pos << "), isAutoScrolling " << isAutoScrolling() << ", contentsX " << contentsX() << ", m_lastPointerRefreshX " << m_lastPointerRefreshX << ", contentsHeight " << contentsHeight() << endl;
+
+ if (contentsX() != m_lastPointerRefreshX) {
+ m_lastPointerRefreshX = contentsX();
+ // We'll need to shift the whole canvas anyway, so
+ slotArtifactsDrawBufferNeedsRefresh();
+ return ;
+ }
+
+ int deltaW = abs(m_pointerPos - oldPos);
+
+ if (deltaW <= m_pointerPen.width() * 2) { // use one rect instead of two separate ones
+
+ QRect updateRect
+ (std::min(m_pointerPos, oldPos) - m_pointerPen.width(), 0,
+ deltaW + m_pointerPen.width() * 2, contentsHeight());
+
+ slotArtifactsDrawBufferNeedsRefresh(updateRect);
+
+ } else {
+
+ slotArtifactsDrawBufferNeedsRefresh
+ (QRect(m_pointerPos - m_pointerPen.width(), 0,
+ m_pointerPen.width() * 2, contentsHeight()));
+
+ slotArtifactsDrawBufferNeedsRefresh
+ (QRect(oldPos - m_pointerPen.width(), 0,
+ m_pointerPen.width() * 2, contentsHeight()));
+ }
+}
+
+void CompositionView::setGuidesPos(int x, int y)
+{
+ m_topGuidePos = x;
+ m_foreGuidePos = y;
+ slotArtifactsDrawBufferNeedsRefresh();
+}
+
+void CompositionView::setGuidesPos(const QPoint& p)
+{
+ m_topGuidePos = p.x();
+ m_foreGuidePos = p.y();
+ slotArtifactsDrawBufferNeedsRefresh();
+}
+
+void CompositionView::setDrawGuides(bool d)
+{
+ m_drawGuides = d;
+ slotArtifactsDrawBufferNeedsRefresh();
+}
+
+void CompositionView::setTmpRect(const QRect& r)
+{
+ setTmpRect(r, m_tmpRectFill);
+}
+
+void CompositionView::setTmpRect(const QRect& r, const QColor &c)
+{
+ QRect pRect = m_tmpRect;
+ m_tmpRect = r;
+ m_tmpRectFill = c;
+ slotUpdateSegmentsDrawBuffer(m_tmpRect | pRect);
+}
+
+void CompositionView::setTextFloat(int x, int y, const QString &text)
+{
+ m_textFloatPos.setX(x);
+ m_textFloatPos.setY(y);
+ m_textFloatText = text;
+ m_drawTextFloat = true;
+ slotArtifactsDrawBufferNeedsRefresh();
+
+ // most of the time when the floating text is drawn
+ // we want to update a larger part of the view
+ // so don't update here
+ // QRect r = fontMetrics().boundingRect(x, y, 300, 40, AlignAuto, m_textFloatText);
+ // slotUpdateSegmentsDrawBuffer(r);
+
+
+ // rgapp->slotSetStatusMessage(text);
+}
+
+void CompositionView::slotSetFineGrain(bool value)
+{
+ m_fineGrain = value;
+}
+
+void CompositionView::slotSetPencilOverExisting(bool value)
+{
+ m_pencilOverExisting = value;
+}
+
+void
+CompositionView::slotTextFloatTimeout()
+{
+ hideTextFloat();
+ slotArtifactsDrawBufferNeedsRefresh();
+ // rgapp->slotSetStatusMessage(QString::null);
+}
+
+}
+#include "CompositionView.moc"
diff --git a/src/gui/editors/segment/segmentcanvas/CompositionView.h b/src/gui/editors/segment/segmentcanvas/CompositionView.h
new file mode 100644
index 0000000..ff0d440
--- /dev/null
+++ b/src/gui/editors/segment/segmentcanvas/CompositionView.h
@@ -0,0 +1,366 @@
+
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
+
+/*
+ Rosegarden
+ A MIDI and audio sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_COMPOSITIONVIEW_H_
+#define _RG_COMPOSITIONVIEW_H_
+
+#include "base/Selection.h"
+#include "CompositionModel.h"
+#include "CompositionItem.h"
+#include "gui/general/RosegardenScrollView.h"
+#include <qbrush.h>
+#include <qcolor.h>
+#include <qpen.h>
+#include <qpixmap.h>
+#include <qpoint.h>
+#include <qrect.h>
+#include <qstring.h>
+#include "base/Event.h"
+
+
+class QWidget;
+class QWheelEvent;
+class QResizeEvent;
+class QPaintEvent;
+class QPainter;
+class QMouseEvent;
+class QEvent;
+
+
+namespace Rosegarden
+{
+
+class SnapGrid;
+class SegmentToolBox;
+class SegmentTool;
+class SegmentSelector;
+class Segment;
+class RosegardenGUIDoc;
+class CompositionRect;
+
+
+class CompositionView : public RosegardenScrollView
+{
+ Q_OBJECT
+public:
+ CompositionView(RosegardenGUIDoc*, CompositionModel*,
+ QWidget * parent=0, const char* name=0, WFlags f=0);
+
+ void setPointerPos(int pos);
+ int getPointerPos() { return m_pointerPos; }
+
+ void setGuidesPos(int x, int y);
+ void setGuidesPos(const QPoint& p);
+ void setDrawGuides(bool d);
+
+ QRect getSelectionRect() const { return m_selectionRect; }
+ void setSelectionRectPos(const QPoint& pos);
+ void setSelectionRectSize(int w, int h);
+ void setDrawSelectionRect(bool d);
+
+ SnapGrid& grid() { return m_model->grid(); }
+
+ CompositionItem getFirstItemAt(QPoint pos);
+
+ SegmentToolBox* getToolBox() { return m_toolBox; }
+
+ CompositionModel* getModel() { return m_model; }
+
+ void setTmpRect(const QRect& r);
+ void setTmpRect(const QRect& r, const QColor &c);
+ const QRect& getTmpRect() const { return m_tmpRect; }
+
+ /**
+ * Set the snap resolution of the grid to something suitable.
+ *
+ * fineTool indicates whether the current tool is a fine-grain sort
+ * (such as the resize or move tools) or a coarse one (such as the
+ * segment creation pencil). If the user is requesting extra-fine
+ * resolution (through the setFineGrain method) that will also be
+ * taken into account.
+ */
+ void setSnapGrain(bool fine);
+
+ /**
+ * Find out whether the user is requesting extra-fine resolution
+ * (e.g. by holding Shift key). This is seldom necessary -- most
+ * client code will only need to query the snap grid that is
+ * adjusted appropriately by the view when interactions take
+ * place.
+ */
+ bool isFineGrain() const { return m_fineGrain; }
+
+ /**
+ * Find out whether the user is requesting to draw over an existing segment
+ * with the pencil, by holding the Ctrl key. This is used by the segment
+ * pencil to decide whether to abort or not if a user attempts to draw over
+ * an existing segment, and this is all necessary in order to avoid breaking
+ * the double-click-to-open behavior.
+ */
+ bool pencilOverExisting() const { return m_pencilOverExisting; }
+
+ /**
+ * Set whether the segment items contain previews or not
+ */
+ void setShowPreviews(bool previews) { m_showPreviews = previews; }
+
+ /**
+ * Return whether the segment items contain previews or not
+ */
+ bool isShowingPreviews() { return m_showPreviews; }
+
+ /**
+ * clear all seg rect cache
+ */
+ void clearSegmentRectsCache(bool clearPreviews = false);
+
+ /// Return the selected Segments if we're currently using a "Selector"
+ SegmentSelection getSelectedSegments();
+
+ bool haveSelection() const { return m_model->haveSelection(); }
+
+ void updateSelectionContents();
+
+ /**
+ * Set and hide a text float on this canvas - it can contain
+ * anything and can be left to timeout or you can hide it
+ * explicitly.
+ *
+ */
+ void setTextFloat(int x, int y, const QString &text);
+ void hideTextFloat() { m_drawTextFloat = false; }
+
+ void setShowSegmentLabels(bool b) { m_showSegmentLabels = b; }
+
+ void setBackgroundPixmap(const QPixmap &m);
+
+ void endAudioPreviewGeneration();
+
+public slots:
+ void scrollRight();
+ void scrollLeft();
+ void slotContentsMoving(int x, int y);
+
+ /// Set the current segment editing tool
+ void slotSetTool(const QString& toolName);
+
+ // This method only operates if we're of the "Selector"
+ // tool type - it's called from the View to enable it
+ // to automatically set the selection of Segments (say
+ // by Track).
+ //
+ void slotSelectSegments(const SegmentSelection &segment);
+
+ // These are sent from the top level app when it gets key
+ // depresses relating to selection add (usually SHIFT) and
+ // selection copy (usually CONTROL)
+ //
+ void slotSetSelectAdd(bool value);
+ void slotSetSelectCopy(bool value);
+
+ void slotSetFineGrain(bool value);
+ void slotSetPencilOverExisting(bool value);
+
+ // Show and hige the splitting line on a Segment
+ //
+ void slotShowSplitLine(int x, int y);
+ void slotHideSplitLine();
+
+ void slotExternalWheelEvent(QWheelEvent*);
+
+ // TextFloat timer
+ void slotTextFloatTimeout();
+
+ void slotUpdateSegmentsDrawBuffer();
+ void slotUpdateSegmentsDrawBuffer(const QRect&);
+
+ void slotRefreshColourCache();
+
+ void slotNewMIDIRecordingSegment(Segment*);
+ void slotNewAudioRecordingSegment(Segment*);
+ // no longer used, see RosegardenGUIDoc::insertRecordedMidi
+// void slotRecordMIDISegmentUpdated(Segment*, timeT updatedFrom);
+ void slotStoppedRecording();
+
+ void slotUpdateSize();
+
+signals:
+ void editSegment(Segment*); // use default editor
+ void editSegmentNotation(Segment*);
+ void editSegmentMatrix(Segment*);
+ void editSegmentAudio(Segment*);
+ void editSegmentEventList(Segment*);
+ void audioSegmentAutoSplit(Segment*);
+ void editRepeat(Segment*, timeT);
+
+ void setPointerPosition(timeT);
+
+ void showContextHelp(const QString &);
+
+protected:
+ virtual bool event(QEvent *);
+
+ virtual void contentsMousePressEvent(QMouseEvent*);
+ virtual void contentsMouseReleaseEvent(QMouseEvent*);
+ virtual void contentsMouseDoubleClickEvent(QMouseEvent*);
+ virtual void contentsMouseMoveEvent(QMouseEvent*);
+
+ virtual void viewportPaintEvent(QPaintEvent*);
+ virtual void resizeEvent(QResizeEvent*);
+
+ virtual void enterEvent(QEvent *);
+ virtual void leaveEvent(QEvent *);
+
+ virtual void viewportPaintRect(QRect);
+
+ /**
+ * if something changed, returns true and sets rect accordingly
+ * sets 'scroll' if some scrolling occurred
+ */
+ bool checkScrollAndRefreshDrawBuffer(QRect &, bool& scroll);
+ void refreshSegmentsDrawBuffer(const QRect&);
+ void refreshArtifactsDrawBuffer(const QRect&);
+ void drawArea(QPainter * p, const QRect& rect);
+ void drawAreaAudioPreviews(QPainter * p, const QRect& rect);
+ void drawAreaArtifacts(QPainter * p, const QRect& rect);
+ void drawRect(const QRect& rect, QPainter * p, const QRect& clipRect,
+ bool isSelected = false, int intersectLvl = 0, bool fill = true);
+ void drawCompRect(const CompositionRect& r, QPainter *p, const QRect& clipRect,
+ int intersectLvl = 0, bool fill = true);
+ void drawCompRectLabel(const CompositionRect& r, QPainter *p, const QRect& clipRect);
+ void drawIntersections(const CompositionModel::rectcontainer&, QPainter * p, const QRect& clipRect);
+
+ void drawPointer(QPainter * p, const QRect& clipRect);
+ void drawGuides(QPainter * p, const QRect& clipRect);
+ void drawTextFloat(QPainter * p, const QRect& clipRect);
+
+ void initStepSize();
+ void releaseCurrentItem();
+
+ static QColor mixBrushes(QBrush a, QBrush b);
+
+ SegmentSelector* getSegmentSelectorTool();
+
+protected slots:
+ void slotSegmentsDrawBufferNeedsRefresh() {
+ m_segmentsDrawBufferRefresh =
+ QRect(contentsX(), contentsY(), visibleWidth(), visibleHeight());
+ }
+
+ void slotSegmentsDrawBufferNeedsRefresh(QRect r) {
+ m_segmentsDrawBufferRefresh |=
+ (QRect(contentsX(), contentsY(), visibleWidth(), visibleHeight())
+ & r);
+ }
+
+ void slotArtifactsDrawBufferNeedsRefresh() {
+ m_artifactsDrawBufferRefresh =
+ QRect(contentsX(), contentsY(), visibleWidth(), visibleHeight());
+ updateContents();
+ }
+
+ void slotArtifactsDrawBufferNeedsRefresh(QRect r) {
+ m_artifactsDrawBufferRefresh |=
+ (QRect(contentsX(), contentsY(), visibleWidth(), visibleHeight())
+ & r);
+ updateContents(r);
+ }
+
+ void slotAllDrawBuffersNeedRefresh() {
+ slotSegmentsDrawBufferNeedsRefresh();
+ slotArtifactsDrawBufferNeedsRefresh();
+ }
+
+ void slotAllDrawBuffersNeedRefresh(QRect r) {
+ slotSegmentsDrawBufferNeedsRefresh(r);
+ slotArtifactsDrawBufferNeedsRefresh(r);
+ }
+
+ void slotToolHelpChanged(const QString &);
+
+protected:
+
+ //--------------- Data members ---------------------------------
+
+ CompositionModel* m_model;
+ CompositionItem m_currentItem;
+
+ SegmentTool* m_tool;
+ SegmentToolBox* m_toolBox;
+
+ bool m_showPreviews;
+ bool m_showSegmentLabels;
+ bool m_fineGrain;
+ bool m_pencilOverExisting;
+
+ int m_minWidth;
+
+ int m_stepSize;
+ QColor m_rectFill;
+ QColor m_selectedRectFill;
+
+ int m_pointerPos;
+ QColor m_pointerColor;
+ int m_pointerWidth;
+ QPen m_pointerPen;
+
+ QRect m_tmpRect;
+ QColor m_tmpRectFill;
+ QPoint m_splitLinePos;
+
+ QColor m_trackDividerColor;
+
+ bool m_drawGuides;
+ QColor m_guideColor;
+ int m_topGuidePos;
+ int m_foreGuidePos;
+
+ bool m_drawSelectionRect;
+ QRect m_selectionRect;
+
+ bool m_drawTextFloat;
+ QString m_textFloatText;
+ QPoint m_textFloatPos;
+
+ QPixmap m_segmentsDrawBuffer;
+ QPixmap m_artifactsDrawBuffer;
+ QRect m_segmentsDrawBufferRefresh;
+ QRect m_artifactsDrawBufferRefresh;
+ int m_lastBufferRefreshX;
+ int m_lastBufferRefreshY;
+ int m_lastPointerRefreshX;
+ QPixmap m_backgroundPixmap;
+
+ QString m_toolContextHelp;
+ bool m_contextHelpShown;
+
+ mutable CompositionModel::AudioPreviewDrawData m_audioPreviewRects;
+ mutable CompositionModel::RectRanges m_notationPreviewRects;
+};
+
+
+}
+
+#endif
diff --git a/src/gui/editors/segment/segmentcanvas/PreviewRect.cpp b/src/gui/editors/segment/segmentcanvas/PreviewRect.cpp
new file mode 100644
index 0000000..fa09644
--- /dev/null
+++ b/src/gui/editors/segment/segmentcanvas/PreviewRect.cpp
@@ -0,0 +1,34 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
+
+/*
+ Rosegarden
+ A MIDI and audio sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "PreviewRect.h"
+
+#include <qcolor.h>
+#include <qrect.h>
+
+
+namespace Rosegarden
+{
+}
diff --git a/src/gui/editors/segment/segmentcanvas/PreviewRect.h b/src/gui/editors/segment/segmentcanvas/PreviewRect.h
new file mode 100644
index 0000000..59f3113
--- /dev/null
+++ b/src/gui/editors/segment/segmentcanvas/PreviewRect.h
@@ -0,0 +1,62 @@
+
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
+
+/*
+ Rosegarden
+ A MIDI and audio sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_PREVIEWRECT_H_
+#define _RG_PREVIEWRECT_H_
+
+#include <qcolor.h>
+#include <qrect.h>
+#include <vector>
+
+
+
+
+namespace Rosegarden
+{
+
+
+
+class PreviewRect : public QRect {
+public:
+ PreviewRect(int left, int top, int width, int height) :
+ QRect(left, top, width, height) {};
+
+ PreviewRect(const QRect& r) :
+ QRect(r) {};
+
+ const QColor& getColor() const { return m_color; }
+ void setColor(QColor c) { m_color = c; }
+
+protected:
+ QColor m_color;
+};
+
+typedef std::vector<QImage> PixmapArray;
+
+
+
+}
+
+#endif
diff --git a/src/gui/editors/segment/segmentcanvas/SegmentEraser.cpp b/src/gui/editors/segment/segmentcanvas/SegmentEraser.cpp
new file mode 100644
index 0000000..3d1e26f
--- /dev/null
+++ b/src/gui/editors/segment/segmentcanvas/SegmentEraser.cpp
@@ -0,0 +1,88 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
+
+/*
+ Rosegarden
+ A MIDI and audio sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "SegmentEraser.h"
+
+#include "misc/Debug.h"
+#include "commands/segment/SegmentEraseCommand.h"
+#include "CompositionView.h"
+#include "CompositionItemImpl.h"
+#include "document/RosegardenGUIDoc.h"
+#include "gui/general/BaseTool.h"
+#include "gui/general/RosegardenCanvasView.h"
+#include "SegmentTool.h"
+#include <kcommand.h>
+#include <qpoint.h>
+#include <qstring.h>
+#include <klocale.h>
+
+
+namespace Rosegarden
+{
+
+SegmentEraser::SegmentEraser(CompositionView *c, RosegardenGUIDoc *d)
+ : SegmentTool(c, d)
+{
+ RG_DEBUG << "SegmentEraser()\n";
+}
+
+void SegmentEraser::ready()
+{
+ m_canvas->viewport()->setCursor(Qt::pointingHandCursor);
+ setBasicContextHelp();
+}
+
+void SegmentEraser::handleMouseButtonPress(QMouseEvent *e)
+{
+ setCurrentItem(m_canvas->getFirstItemAt(e->pos()));
+}
+
+void SegmentEraser::handleMouseButtonRelease(QMouseEvent*)
+{
+ if (m_currentItem) {
+ // no need to test the result, we know it's good (see handleMouseButtonPress)
+ CompositionItemImpl* item = dynamic_cast<CompositionItemImpl*>((_CompositionItem*)m_currentItem);
+
+ addCommandToHistory(new SegmentEraseCommand(item->getSegment()));
+ }
+
+ setCurrentItem(CompositionItem());
+}
+
+int SegmentEraser::handleMouseMove(QMouseEvent*)
+{
+ setBasicContextHelp();
+ return RosegardenCanvasView::NoFollow;
+}
+
+void SegmentEraser::setBasicContextHelp()
+{
+ setContextHelp(i18n("Click on a segment to delete it"));
+}
+
+const QString SegmentEraser::ToolName = "segmenteraser";
+
+}
+#include "SegmentEraser.moc"
diff --git a/src/gui/editors/segment/segmentcanvas/SegmentEraser.h b/src/gui/editors/segment/segmentcanvas/SegmentEraser.h
new file mode 100644
index 0000000..f428c28
--- /dev/null
+++ b/src/gui/editors/segment/segmentcanvas/SegmentEraser.h
@@ -0,0 +1,67 @@
+
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
+
+/*
+ Rosegarden
+ A MIDI and audio sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_SEGMENTERASER_H_
+#define _RG_SEGMENTERASER_H_
+
+#include "SegmentTool.h"
+#include <qstring.h>
+
+
+class QMouseEvent;
+
+
+namespace Rosegarden
+{
+
+class RosegardenGUIDoc;
+class CompositionView;
+
+
+class SegmentEraser : public SegmentTool
+{
+ Q_OBJECT
+
+ friend class SegmentToolBox;
+
+public:
+
+ virtual void ready();
+
+ virtual void handleMouseButtonPress(QMouseEvent*);
+ virtual void handleMouseButtonRelease(QMouseEvent*);
+ virtual int handleMouseMove(QMouseEvent*);
+
+ static const QString ToolName;
+
+protected:
+ SegmentEraser(CompositionView*, RosegardenGUIDoc*);
+ void setBasicContextHelp();
+};
+
+
+}
+
+#endif
diff --git a/src/gui/editors/segment/segmentcanvas/SegmentItemPreview.cpp b/src/gui/editors/segment/segmentcanvas/SegmentItemPreview.cpp
new file mode 100644
index 0000000..f0c4598
--- /dev/null
+++ b/src/gui/editors/segment/segmentcanvas/SegmentItemPreview.cpp
@@ -0,0 +1,37 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
+
+/*
+ Rosegarden
+ A MIDI and audio sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "SegmentItemPreview.h"
+
+#include "base/RulerScale.h"
+#include "base/Segment.h"
+#include <qpainter.h>
+#include <qrect.h>
+#include <qwmatrix.h>
+
+
+namespace Rosegarden
+{
+}
diff --git a/src/gui/editors/segment/segmentcanvas/SegmentItemPreview.h b/src/gui/editors/segment/segmentcanvas/SegmentItemPreview.h
new file mode 100644
index 0000000..e190a5c
--- /dev/null
+++ b/src/gui/editors/segment/segmentcanvas/SegmentItemPreview.h
@@ -0,0 +1,91 @@
+
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
+
+/*
+ Rosegarden
+ A MIDI and audio sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_SEGMENTITEMPREVIEW_H_
+#define _RG_SEGMENTITEMPREVIEW_H_
+
+#include <qrect.h>
+
+
+class QWMatrix;
+class QPainter;
+
+
+namespace Rosegarden
+{
+
+class Segment;
+class RulerScale;
+
+
+//////////////////////////////////////////////////////////////////////
+class SegmentItemPreview
+{
+public:
+ SegmentItemPreview(Segment& parent,
+ RulerScale* scale);
+ virtual ~SegmentItemPreview();
+
+ enum PreviewState {
+ PreviewChanged,
+ PreviewCalculating,
+ PreviewCurrent
+ };
+
+ virtual void drawShape(QPainter&) = 0;
+
+ PreviewState getPreviewState() const { return m_previewState; }
+
+ /**
+ * Sets whether the preview shape shown in the segment needs
+ * to be refreshed
+ */
+ void setPreviewCurrent(bool c)
+ { m_previewState = (c ? PreviewCurrent : PreviewChanged); }
+
+ /**
+ * Clears out the preview entirely so that it will be regenerated
+ * next time
+ */
+ virtual void clearPreview() = 0;
+
+ QRect rect();
+
+protected:
+ virtual void updatePreview(const QWMatrix &matrix) = 0;
+
+ //--------------- Data members ---------------------------------
+
+ Segment *m_segment;
+ RulerScale *m_rulerScale;
+
+ PreviewState m_previewState;
+};
+
+
+
+}
+
+#endif
diff --git a/src/gui/editors/segment/segmentcanvas/SegmentJoiner.cpp b/src/gui/editors/segment/segmentcanvas/SegmentJoiner.cpp
new file mode 100644
index 0000000..5129202
--- /dev/null
+++ b/src/gui/editors/segment/segmentcanvas/SegmentJoiner.cpp
@@ -0,0 +1,73 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
+
+/*
+ Rosegarden
+ A MIDI and audio sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "SegmentJoiner.h"
+
+#include "misc/Debug.h"
+#include "CompositionView.h"
+#include "document/RosegardenGUIDoc.h"
+#include "gui/general/BaseTool.h"
+#include "gui/general/RosegardenCanvasView.h"
+#include "SegmentTool.h"
+#include <kcommand.h>
+#include <qpoint.h>
+#include <qstring.h>
+#include <klocale.h>
+
+
+namespace Rosegarden
+{
+
+SegmentJoiner::SegmentJoiner(CompositionView *c, RosegardenGUIDoc *d)
+ : SegmentTool(c, d)
+{
+ RG_DEBUG << "SegmentJoiner() - not implemented\n";
+}
+
+SegmentJoiner::~SegmentJoiner()
+{}
+
+void
+SegmentJoiner::handleMouseButtonPress(QMouseEvent*)
+{}
+
+void
+SegmentJoiner::handleMouseButtonRelease(QMouseEvent*)
+{}
+
+int
+SegmentJoiner::handleMouseMove(QMouseEvent*)
+{
+ return RosegardenCanvasView::NoFollow;
+}
+
+void
+SegmentJoiner::contentsMouseDoubleClickEvent(QMouseEvent*)
+{}
+
+const QString SegmentJoiner::ToolName = "segmentjoiner";
+
+}
+#include "SegmentJoiner.moc"
diff --git a/src/gui/editors/segment/segmentcanvas/SegmentJoiner.h b/src/gui/editors/segment/segmentcanvas/SegmentJoiner.h
new file mode 100644
index 0000000..2c83a26
--- /dev/null
+++ b/src/gui/editors/segment/segmentcanvas/SegmentJoiner.h
@@ -0,0 +1,70 @@
+
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
+
+/*
+ Rosegarden
+ A MIDI and audio sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_SEGMENTJOINER_H_
+#define _RG_SEGMENTJOINER_H_
+
+#include "SegmentTool.h"
+#include <qstring.h>
+
+
+class QMouseEvent;
+
+
+namespace Rosegarden
+{
+
+class RosegardenGUIDoc;
+class CompositionView;
+
+
+class SegmentJoiner : public SegmentTool
+{
+ Q_OBJECT
+
+ friend class SegmentToolBox;
+
+public:
+
+ virtual ~SegmentJoiner();
+
+ virtual void handleMouseButtonPress(QMouseEvent*);
+ virtual void handleMouseButtonRelease(QMouseEvent*);
+ virtual int handleMouseMove(QMouseEvent*);
+
+ // don't do double clicks
+ virtual void contentsMouseDoubleClickEvent(QMouseEvent*);
+
+ static const QString ToolName;
+
+protected:
+ SegmentJoiner(CompositionView*, RosegardenGUIDoc*);
+};
+
+
+
+}
+
+#endif
diff --git a/src/gui/editors/segment/segmentcanvas/SegmentMover.cpp b/src/gui/editors/segment/segmentcanvas/SegmentMover.cpp
new file mode 100644
index 0000000..a3d2a59
--- /dev/null
+++ b/src/gui/editors/segment/segmentcanvas/SegmentMover.cpp
@@ -0,0 +1,348 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
+
+/*
+ Rosegarden
+ A MIDI and audio sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "SegmentMover.h"
+
+#include "base/Event.h"
+#include <klocale.h>
+#include "misc/Debug.h"
+#include "base/Composition.h"
+#include "base/RealTime.h"
+#include "base/Track.h"
+#include "base/SnapGrid.h"
+#include "commands/segment/SegmentReconfigureCommand.h"
+#include "CompositionItemHelper.h"
+#include "CompositionModel.h"
+#include "CompositionView.h"
+#include "document/RosegardenGUIDoc.h"
+#include "gui/general/BaseTool.h"
+#include "gui/general/RosegardenCanvasView.h"
+#include "SegmentTool.h"
+#include "SegmentToolBox.h"
+#include "SegmentSelector.h"
+#include <kcommand.h>
+#include <qcursor.h>
+#include <qevent.h>
+#include <qpoint.h>
+#include <qrect.h>
+#include <qstring.h>
+#include <klocale.h>
+
+
+namespace Rosegarden
+{
+
+SegmentMover::SegmentMover(CompositionView *c, RosegardenGUIDoc *d)
+ : SegmentTool(c, d)
+{
+ RG_DEBUG << "SegmentMover()\n";
+}
+
+void SegmentMover::ready()
+{
+ m_canvas->viewport()->setCursor(Qt::sizeAllCursor);
+ connect(m_canvas, SIGNAL(contentsMoving (int, int)),
+ this, SLOT(slotCanvasScrolled(int, int)));
+ setBasicContextHelp();
+}
+
+void SegmentMover::stow()
+{
+ disconnect(m_canvas, SIGNAL(contentsMoving (int, int)),
+ this, SLOT(slotCanvasScrolled(int, int)));
+}
+
+void SegmentMover::slotCanvasScrolled(int newX, int newY)
+{
+ QMouseEvent tmpEvent(QEvent::MouseMove,
+ m_canvas->viewport()->mapFromGlobal(QCursor::pos()) + QPoint(newX, newY),
+ Qt::NoButton, Qt::NoButton);
+ handleMouseMove(&tmpEvent);
+}
+
+void SegmentMover::handleMouseButtonPress(QMouseEvent *e)
+{
+ CompositionItem item = m_canvas->getFirstItemAt(e->pos());
+ SegmentSelector* selector = dynamic_cast<SegmentSelector*>
+ (getToolBox()->getTool("segmentselector"));
+
+ // #1027303: Segment move issue
+ // Clear selection if we're clicking on an item that's not in it
+ // and we're not in add mode
+
+ if (selector && item &&
+ !m_canvas->getModel()->isSelected(item) && !selector->isSegmentAdding()) {
+ m_canvas->getModel()->clearSelected();
+ m_canvas->getModel()->signalSelection();
+ m_canvas->updateContents();
+ }
+
+ if (item) {
+
+ setCurrentItem(item);
+ m_clickPoint = e->pos();
+ Segment* s = CompositionItemHelper::getSegment(m_currentItem);
+
+ int x = int(m_canvas->grid().getRulerScale()->getXForTime(s->getStartTime()));
+ int y = int(m_canvas->grid().getYBinCoordinate(s->getTrack()));
+
+ m_canvas->setGuidesPos(x, y);
+ m_canvas->setDrawGuides(true);
+
+ if (m_canvas->getModel()->haveSelection()) {
+ RG_DEBUG << "SegmentMover::handleMouseButtonPress() : haveSelection\n";
+ // startChange on all selected segments
+ m_canvas->getModel()->startChangeSelection(CompositionModel::ChangeMove);
+
+
+ CompositionModel::itemcontainer& changingItems = m_canvas->getModel()->getChangingItems();
+ // set m_currentItem to its "sibling" among selected (now moving) items
+ setCurrentItem(CompositionItemHelper::findSiblingCompositionItem(changingItems, m_currentItem));
+
+ } else {
+ RG_DEBUG << "SegmentMover::handleMouseButtonPress() : no selection\n";
+ m_canvas->getModel()->startChange(item, CompositionModel::ChangeMove);
+ }
+
+ m_canvas->updateContents();
+
+ m_passedInertiaEdge = false;
+
+ } else {
+
+ // check for addmode - clear the selection if not
+ RG_DEBUG << "SegmentMover::handleMouseButtonPress() : clear selection\n";
+ m_canvas->getModel()->clearSelected();
+ m_canvas->getModel()->signalSelection();
+ m_canvas->updateContents();
+ }
+
+}
+
+void SegmentMover::handleMouseButtonRelease(QMouseEvent *e)
+{
+ Composition &comp = m_doc->getComposition();
+
+ int startDragTrackPos = m_canvas->grid().getYBin(m_clickPoint.y());
+ int currentTrackPos = m_canvas->grid().getYBin(e->pos().y());
+ int trackDiff = currentTrackPos - startDragTrackPos;
+
+ if (m_currentItem) {
+
+ if (changeMade()) {
+
+ CompositionModel::itemcontainer& changingItems = m_canvas->getModel()->getChangingItems();
+
+ SegmentReconfigureCommand *command =
+ new SegmentReconfigureCommand
+ (changingItems.size() == 1 ? i18n("Move Segment") : i18n("Move Segments"));
+
+
+ CompositionModel::itemcontainer::iterator it;
+
+ for (it = changingItems.begin();
+ it != changingItems.end();
+ it++) {
+
+ CompositionItem item = *it;
+
+ Segment* segment = CompositionItemHelper::getSegment(item);
+
+ TrackId origTrackId = segment->getTrack();
+ int trackPos = comp.getTrackPositionById(origTrackId);
+ trackPos += trackDiff;
+
+ if (trackPos < 0) {
+ trackPos = 0;
+ } else if (trackPos >= comp.getNbTracks()) {
+ trackPos = comp.getNbTracks() - 1;
+ }
+
+ Track *newTrack = comp.getTrackByPosition(trackPos);
+ int newTrackId = origTrackId;
+ if (newTrack) newTrackId = newTrack->getId();
+
+ timeT newStartTime = CompositionItemHelper::getStartTime(item, m_canvas->grid());
+
+ // We absolutely don't want to snap the end time
+ // to the grid. We want it to remain exactly the same
+ // as it was, but relative to the new start time.
+ timeT newEndTime = newStartTime + segment->getEndMarkerTime()
+ - segment->getStartTime();
+
+ command->addSegment(segment,
+ newStartTime,
+ newEndTime,
+ newTrackId);
+ }
+
+ addCommandToHistory(command);
+ }
+
+ m_canvas->hideTextFloat();
+ m_canvas->setDrawGuides(false);
+ m_canvas->getModel()->endChange();
+ m_canvas->slotUpdateSegmentsDrawBuffer();
+
+ }
+
+ setChangeMade(false);
+ m_currentItem = CompositionItem();
+
+ setBasicContextHelp();
+}
+
+int SegmentMover::handleMouseMove(QMouseEvent *e)
+{
+ m_canvas->setSnapGrain(true);
+
+ Composition &comp = m_doc->getComposition();
+
+ if (!m_currentItem) {
+ setBasicContextHelp();
+ return RosegardenCanvasView::NoFollow;
+ }
+
+ if (!m_canvas->isFineGrain()) {
+ setContextHelp(i18n("Hold Shift to avoid snapping to beat grid"));
+ } else {
+ clearContextHelp();
+ }
+
+ CompositionModel::itemcontainer& changingItems = m_canvas->getModel()->getChangingItems();
+
+ // RG_DEBUG << "SegmentMover::handleMouseMove : nb changingItems = "
+ // << changingItems.size() << endl;
+
+ CompositionModel::itemcontainer::iterator it;
+ int guideX = 0;
+ int guideY = 0;
+ QRect updateRect;
+
+ for (it = changingItems.begin();
+ it != changingItems.end();
+ it++) {
+ // it->second->showRepeatRect(false);
+
+ int dx = e->pos().x() - m_clickPoint.x(),
+ dy = e->pos().y() - m_clickPoint.y();
+
+ const int inertiaDistance = m_canvas->grid().getYSnap() / 3;
+ if (!m_passedInertiaEdge &&
+ (dx < inertiaDistance && dx > -inertiaDistance) &&
+ (dy < inertiaDistance && dy > -inertiaDistance)) {
+ return RosegardenCanvasView::NoFollow;
+ } else {
+ m_passedInertiaEdge = true;
+ }
+
+ timeT newStartTime = m_canvas->grid().snapX((*it)->savedRect().x() + dx);
+
+ int newX = int(m_canvas->grid().getRulerScale()->getXForTime(newStartTime));
+
+ int startDragTrackPos = m_canvas->grid().getYBin(m_clickPoint.y());
+ int currentTrackPos = m_canvas->grid().getYBin(e->pos().y());
+ int trackDiff = currentTrackPos - startDragTrackPos;
+ int trackPos = m_canvas->grid().getYBin((*it)->savedRect().y());
+
+// std::cerr << "segment " << *it << ": mouse started at track " << startDragTrackPos << ", is now at " << currentTrackPos << ", trackPos from " << trackPos << " to ";
+
+ trackPos += trackDiff;
+
+// std::cerr << trackPos << std::endl;
+
+ if (trackPos < 0) {
+ trackPos = 0;
+ } else if (trackPos >= comp.getNbTracks()) {
+ trackPos = comp.getNbTracks() - 1;
+ }
+/*!!!
+ int newY = m_canvas->grid().snapY((*it)->savedRect().y() + dy);
+ // Make sure we don't set a non-existing track
+ if (newY < 0) {
+ newY = 0;
+ }
+ int trackPos = m_canvas->grid().getYBin(newY);
+
+ // RG_DEBUG << "SegmentMover::handleMouseMove: orig y "
+ // << (*it)->savedRect().y()
+ // << ", dy " << dy << ", newY " << newY
+ // << ", track " << track << endl;
+
+ // Make sure we don't set a non-existing track (c'td)
+ // TODO: make this suck less. Either the tool should
+ // not allow it in the first place, or we automatically
+ // create new tracks - might make undo very tricky though
+ //
+ if (trackPos >= comp.getNbTracks())
+ trackPos = comp.getNbTracks() - 1;
+*/
+ int newY = m_canvas->grid().getYBinCoordinate(trackPos);
+
+ // RG_DEBUG << "SegmentMover::handleMouseMove: moving to "
+ // << newX << "," << newY << endl;
+
+ updateRect |= (*it)->rect();
+ (*it)->moveTo(newX, newY);
+ updateRect |= (*it)->rect();
+ setChangeMade(true);
+ }
+
+ if (changeMade())
+ m_canvas->getModel()->signalContentChange();
+
+ guideX = m_currentItem->rect().x();
+ guideY = m_currentItem->rect().y();
+
+ m_canvas->setGuidesPos(guideX, guideY);
+
+ timeT currentItemStartTime = m_canvas->grid().snapX(m_currentItem->rect().x());
+
+ RealTime time = comp.getElapsedRealTime(currentItemStartTime);
+ QString ms;
+ ms.sprintf("%03d", time.msec());
+
+ int bar, beat, fraction, remainder;
+ comp.getMusicalTimeForAbsoluteTime(currentItemStartTime, bar, beat, fraction, remainder);
+
+ QString posString = QString("%1.%2s (%3, %4, %5)")
+ .arg(time.sec).arg(ms)
+ .arg(bar + 1).arg(beat).arg(fraction);
+
+ m_canvas->setTextFloat(guideX + 10, guideY - 30, posString);
+ m_canvas->updateContents();
+
+ return RosegardenCanvasView::FollowHorizontal | RosegardenCanvasView::FollowVertical;
+}
+
+void SegmentMover::setBasicContextHelp()
+{
+ setContextHelp(i18n("Click and drag to move a segment"));
+}
+
+const QString SegmentMover::ToolName = "segmentmover";
+
+}
+#include "SegmentMover.moc"
diff --git a/src/gui/editors/segment/segmentcanvas/SegmentMover.h b/src/gui/editors/segment/segmentcanvas/SegmentMover.h
new file mode 100644
index 0000000..776189e
--- /dev/null
+++ b/src/gui/editors/segment/segmentcanvas/SegmentMover.h
@@ -0,0 +1,78 @@
+
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
+
+/*
+ Rosegarden
+ A MIDI and audio sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_SEGMENTMOVER_H_
+#define _RG_SEGMENTMOVER_H_
+
+#include "SegmentTool.h"
+#include <qpoint.h>
+#include <qstring.h>
+
+
+class QMouseEvent;
+
+
+namespace Rosegarden
+{
+
+class RosegardenGUIDoc;
+class CompositionView;
+
+
+class SegmentMover : public SegmentTool
+{
+ Q_OBJECT
+
+ friend class SegmentToolBox;
+
+public:
+
+ virtual void ready();
+ virtual void stow();
+
+ virtual void handleMouseButtonPress(QMouseEvent*);
+ virtual void handleMouseButtonRelease(QMouseEvent*);
+ virtual int handleMouseMove(QMouseEvent*);
+
+ static const QString ToolName;
+
+protected slots:
+ void slotCanvasScrolled(int newX, int newY);
+
+protected:
+ SegmentMover(CompositionView*, RosegardenGUIDoc*);
+
+ void setBasicContextHelp();
+
+ //--------------- Data members ---------------------------------
+
+ QPoint m_clickPoint;
+ bool m_passedInertiaEdge;
+};
+
+
+}
+
+#endif
diff --git a/src/gui/editors/segment/segmentcanvas/SegmentOrderer.cpp b/src/gui/editors/segment/segmentcanvas/SegmentOrderer.cpp
new file mode 100644
index 0000000..4262eb9
--- /dev/null
+++ b/src/gui/editors/segment/segmentcanvas/SegmentOrderer.cpp
@@ -0,0 +1,48 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
+
+/*
+ Rosegarden
+ A MIDI and audio sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "SegmentOrderer.h"
+
+#include "misc/Debug.h"
+#include "base/Composition.h"
+#include "base/Segment.h"
+#include <klocale.h>
+
+
+namespace Rosegarden
+{
+
+void SegmentOrderer::segmentClicked(const Segment* s)
+{
+ m_segmentZs[s] = ++m_currentMaxZ;
+ RG_DEBUG << "SegmentOrderer::segmentClicked() s = " << s << " - max Z = " << m_currentMaxZ << endl;
+}
+
+unsigned int SegmentOrderer::getZForSegment(const Rosegarden::Segment* s)
+{
+ return m_segmentZs[s];
+}
+
+}
diff --git a/src/gui/editors/segment/segmentcanvas/SegmentOrderer.h b/src/gui/editors/segment/segmentcanvas/SegmentOrderer.h
new file mode 100644
index 0000000..f4b3d9a
--- /dev/null
+++ b/src/gui/editors/segment/segmentcanvas/SegmentOrderer.h
@@ -0,0 +1,59 @@
+
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
+
+/*
+ Rosegarden
+ A MIDI and audio sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_SEGMENTORDERER_H_
+#define _RG_SEGMENTORDERER_H_
+
+#include "base/Composition.h"
+#include <map>
+
+
+
+
+namespace Rosegarden
+{
+
+class Segment;
+
+
+class SegmentOrderer : public CompositionObserver {
+public:
+ SegmentOrderer() : m_currentMaxZ(0) {};
+
+ unsigned int getZForSegment(const Segment*);
+
+ void segmentClicked(const Segment *);
+
+protected:
+
+ //--------------- Data members ---------------------------------
+ std::map<const Segment*, unsigned int> m_segmentZs;
+ unsigned int m_currentMaxZ;
+};
+
+
+}
+
+#endif
diff --git a/src/gui/editors/segment/segmentcanvas/SegmentPencil.cpp b/src/gui/editors/segment/segmentcanvas/SegmentPencil.cpp
new file mode 100644
index 0000000..68ca020
--- /dev/null
+++ b/src/gui/editors/segment/segmentcanvas/SegmentPencil.cpp
@@ -0,0 +1,295 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
+
+/*
+ Rosegarden
+ A MIDI and audio sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "SegmentPencil.h"
+
+#include "misc/Debug.h"
+#include "misc/Strings.h"
+#include "gui/general/ClefIndex.h"
+#include "base/NotationTypes.h"
+#include "base/Segment.h"
+#include "base/SnapGrid.h"
+#include "base/Track.h"
+#include "commands/segment/SegmentInsertCommand.h"
+#include "CompositionItemHelper.h"
+#include "CompositionView.h"
+#include "document/RosegardenGUIDoc.h"
+#include "gui/general/BaseTool.h"
+#include "gui/general/GUIPalette.h"
+#include "gui/general/RosegardenCanvasView.h"
+#include "SegmentTool.h"
+#include <kcommand.h>
+#include <klocale.h>
+#include <qcursor.h>
+#include <qevent.h>
+#include <qpoint.h>
+#include <qrect.h>
+#include <qstring.h>
+
+
+namespace Rosegarden
+{
+
+SegmentPencil::SegmentPencil(CompositionView *c, RosegardenGUIDoc *d)
+ : SegmentTool(c, d),
+ m_newRect(false),
+ m_track(0),
+ m_startTime(0),
+ m_endTime(0)
+{
+ RG_DEBUG << "SegmentPencil()\n";
+}
+
+void SegmentPencil::ready()
+{
+ m_canvas->viewport()->setCursor(Qt::ibeamCursor);
+ connect(m_canvas, SIGNAL(contentsMoving (int, int)),
+ this, SLOT(slotCanvasScrolled(int, int)));
+ setContextHelpFor(QPoint(0, 0));
+}
+
+void SegmentPencil::stow()
+{
+ disconnect(m_canvas, SIGNAL(contentsMoving (int, int)),
+ this, SLOT(slotCanvasScrolled(int, int)));
+}
+
+void SegmentPencil::slotCanvasScrolled(int newX, int newY)
+{
+ QMouseEvent tmpEvent(QEvent::MouseMove,
+ m_canvas->viewport()->mapFromGlobal(QCursor::pos()) + QPoint(newX, newY),
+ Qt::NoButton, Qt::NoButton);
+ handleMouseMove(&tmpEvent);
+}
+
+void SegmentPencil::handleMouseButtonPress(QMouseEvent *e)
+{
+ if (e->button() == RightButton)
+ return;
+
+ // is user holding Ctrl+Alt? (ugly, but we are running short on available
+ // modifiers; Alt is grabbed by the window manager, and right clicking, my
+ // (dmm) original idea, is grabbed by the context menu, so let's see how
+ // this goes over
+ bool pencilAnyway = (m_canvas->pencilOverExisting());
+
+ m_newRect = false;
+
+ // Check if mouse click was on a rect
+ //
+ CompositionItem item = m_canvas->getFirstItemAt(e->pos());
+
+ // If user clicked a rect, and pencilAnyway is false, then there's nothing
+ // left to do here
+ if (item) {
+ delete item;
+ if (!pencilAnyway) return ;
+ }
+
+ // make new item
+ //
+ m_canvas->setSnapGrain(false);
+
+ int trackPosition = m_canvas->grid().getYBin(e->pos().y());
+
+ // Don't do anything if the user clicked beyond the track buttons
+ //
+ if (trackPosition >= m_doc->getComposition().getNbTracks())
+ return ;
+
+ Track *t = m_doc->getComposition().getTrackByPosition(trackPosition);
+ if (!t)
+ return ;
+
+ TrackId trackId = t->getId();
+
+ timeT time = int(nearbyint(m_canvas->grid().snapX(e->pos().x(), SnapGrid::SnapLeft)));
+ timeT duration = int(nearbyint(m_canvas->grid().getSnapTime(double(e->pos().x()))));
+ if (duration == 0)
+ duration = Note(Note::Shortest).getDuration();
+
+ int multiple = m_doc->getComposition()
+ .getMaxContemporaneousSegmentsOnTrack(trackId);
+ if (multiple < 1) multiple = 1;
+
+ QRect tmpRect;
+ tmpRect.setX(int(nearbyint(m_canvas->grid().getRulerScale()->getXForTime(time))));
+ tmpRect.setY(m_canvas->grid().getYBinCoordinate(trackPosition) + 1);
+ tmpRect.setHeight(m_canvas->grid().getYSnap() * multiple - 2);
+ tmpRect.setWidth(int(nearbyint(m_canvas->grid().getRulerScale()->getWidthForDuration(time, duration))));
+
+ m_canvas->setTmpRect(tmpRect,
+ GUIPalette::convertColour
+ (m_doc->getComposition().getSegmentColourMap().
+ getColourByIndex(t->getColor())));
+
+ m_newRect = true;
+ m_origPos = e->pos();
+
+ m_canvas->updateContents(tmpRect);
+}
+
+void SegmentPencil::handleMouseButtonRelease(QMouseEvent* e)
+{
+ if (e->button() == RightButton)
+ return ;
+
+ setContextHelpFor(e->pos());
+
+ if (m_newRect) {
+
+ QRect tmpRect = m_canvas->getTmpRect();
+
+ int trackPosition = m_canvas->grid().getYBin(tmpRect.y());
+ Track *track = m_doc->getComposition().getTrackByPosition(trackPosition);
+ timeT startTime = int(nearbyint(m_canvas->grid().getRulerScale()->getTimeForX(tmpRect.x()))),
+ endTime = int(nearbyint(m_canvas->grid().getRulerScale()->getTimeForX(tmpRect.x() + tmpRect.width())));
+
+ // RG_DEBUG << "SegmentPencil::handleMouseButtonRelease() : new segment with track id "
+ // << track->getId() << endl;
+
+ SegmentInsertCommand *command =
+ new SegmentInsertCommand(m_doc, track->getId(),
+ startTime, endTime);
+
+ m_newRect = false;
+
+ addCommandToHistory(command);
+
+ // add the SegmentItem by hand, instead of allowing the usual
+ // update mechanism to spot it. This way we can select the
+ // segment as we add it; otherwise we'd have no way to know
+ // that the segment was created by this tool rather than by
+ // e.g. a simple file load
+
+ Segment *segment = command->getSegment();
+
+ // add a clef to the start of the segment (tracks initialize to a
+ // default of 0 for this property, so treble will be the default if it
+ // is not specified elsewhere)
+ segment->insert(clefIndexToClef(track->getClef()).getAsEvent
+ (segment->getStartTime()));
+ segment->setTranspose(track->getTranspose());
+ segment->setColourIndex(track->getColor());
+ segment->setLowestPlayable(track->getLowestPlayable());
+ segment->setHighestPlayable(track->getHighestPlayable());
+
+ std::string label = qstrtostr(track->getPresetLabel());
+ if (label != "") {
+ segment->setLabel(qstrtostr(track->getPresetLabel()));
+ }
+
+ CompositionItem item = CompositionItemHelper::makeCompositionItem(segment);
+ m_canvas->getModel()->clearSelected();
+ m_canvas->getModel()->setSelected(item);
+ m_canvas->getModel()->signalSelection();
+ m_canvas->setTmpRect(QRect());
+ m_canvas->slotUpdateSegmentsDrawBuffer();
+
+ } else {
+
+ m_newRect = false;
+ }
+}
+
+int SegmentPencil::handleMouseMove(QMouseEvent *e)
+{
+ if (!m_newRect) {
+ setContextHelpFor(e->pos());
+ return RosegardenCanvasView::NoFollow;
+ }
+
+ if (!m_canvas->isFineGrain()) {
+ setContextHelp(i18n("Hold Shift to avoid snapping to bar lines"));
+ } else {
+ clearContextHelp();
+ }
+
+ QRect tmpRect = m_canvas->getTmpRect();
+ QRect oldTmpRect = tmpRect;
+
+ m_canvas->setSnapGrain(false);
+
+ SnapGrid::SnapDirection direction = SnapGrid::SnapRight;
+ if (e->pos().x() <= m_origPos.x())
+ direction = SnapGrid::SnapLeft;
+
+ timeT snap = int(nearbyint(m_canvas->grid().getSnapTime(double(e->pos().x()))));
+ if (snap == 0)
+ snap = Note(Note::Shortest).getDuration();
+
+ timeT time = int(nearbyint(m_canvas->grid().snapX(e->pos().x(), direction)));
+
+ timeT startTime = int(nearbyint(m_canvas->grid().getRulerScale()->getTimeForX(tmpRect.x())));
+ timeT endTime = int(nearbyint(m_canvas->grid().getRulerScale()->getTimeForX(tmpRect.x() + tmpRect.width())));
+
+ if (direction == SnapGrid::SnapRight) {
+
+ if (time >= startTime) {
+ if ((time - startTime) < snap) {
+ time = startTime + snap;
+ }
+ } else {
+ if ((startTime - time) < snap) {
+ time = startTime - snap;
+ }
+ }
+
+ int w = int(nearbyint(m_canvas->grid().getRulerScale()->getWidthForDuration(startTime, time - startTime)));
+ tmpRect.setWidth(w);
+
+ } else { // SnapGrid::SnapLeft
+
+ // time += std::max(endTime - startTime, timeT(0));
+ tmpRect.setX(int(m_canvas->grid().getRulerScale()->getXForTime(time)));
+
+ }
+
+ m_canvas->setTmpRect(tmpRect);
+ return RosegardenCanvasView::FollowHorizontal;
+}
+
+void SegmentPencil::setContextHelpFor(QPoint p)
+{
+ int trackPosition = m_canvas->grid().getYBin(p.y());
+
+ if (trackPosition < m_doc->getComposition().getNbTracks()) {
+ Track *t = m_doc->getComposition().getTrackByPosition(trackPosition);
+ if (t) {
+ InstrumentId id = t->getInstrument();
+ if (id >= AudioInstrumentBase && id < MidiInstrumentBase) {
+ setContextHelp(i18n("Record or drop audio here"));
+ return;
+ }
+ }
+ }
+
+ setContextHelp(i18n("Click and drag to draw an empty segment. Control+Alt click and drag to draw in overlap mode."));
+}
+
+const QString SegmentPencil::ToolName = "segmentpencil";
+
+}
+#include "SegmentPencil.moc"
diff --git a/src/gui/editors/segment/segmentcanvas/SegmentPencil.h b/src/gui/editors/segment/segmentcanvas/SegmentPencil.h
new file mode 100644
index 0000000..8b55917
--- /dev/null
+++ b/src/gui/editors/segment/segmentcanvas/SegmentPencil.h
@@ -0,0 +1,83 @@
+
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
+
+/*
+ Rosegarden
+ A MIDI and audio sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_SEGMENTPENCIL_H_
+#define _RG_SEGMENTPENCIL_H_
+
+#include "base/Track.h"
+#include "SegmentTool.h"
+#include <qstring.h>
+#include "base/Event.h"
+
+
+class QMouseEvent;
+
+
+namespace Rosegarden
+{
+
+class RosegardenGUIDoc;
+class CompositionView;
+
+
+//////////////////////////////
+
+class SegmentPencil : public SegmentTool
+{
+ Q_OBJECT
+
+ friend class SegmentToolBox;
+ friend class SegmentSelector;
+
+public:
+
+ virtual void ready();
+ virtual void stow();
+
+ virtual void handleMouseButtonPress(QMouseEvent*);
+ virtual void handleMouseButtonRelease(QMouseEvent*);
+ virtual int handleMouseMove(QMouseEvent*);
+
+ static const QString ToolName;
+
+protected slots:
+ void slotCanvasScrolled(int newX, int newY);
+
+protected:
+ SegmentPencil(CompositionView*, RosegardenGUIDoc*);
+ void setContextHelpFor(QPoint p);
+
+ //--------------- Data members ---------------------------------
+
+ bool m_newRect;
+ TrackId m_track;
+ timeT m_startTime;
+ timeT m_endTime;
+};
+
+
+}
+
+#endif
diff --git a/src/gui/editors/segment/segmentcanvas/SegmentResizer.cpp b/src/gui/editors/segment/segmentcanvas/SegmentResizer.cpp
new file mode 100644
index 0000000..6ae7433
--- /dev/null
+++ b/src/gui/editors/segment/segmentcanvas/SegmentResizer.cpp
@@ -0,0 +1,393 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
+
+/*
+ Rosegarden
+ A MIDI and audio sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "SegmentResizer.h"
+
+#include "base/Event.h"
+#include <klocale.h>
+#include "misc/Debug.h"
+#include "base/Composition.h"
+#include "base/NotationTypes.h"
+#include "base/Segment.h"
+#include "base/Track.h"
+#include "base/SnapGrid.h"
+#include "commands/segment/AudioSegmentResizeFromStartCommand.h"
+#include "commands/segment/AudioSegmentRescaleCommand.h"
+#include "commands/segment/SegmentRescaleCommand.h"
+#include "commands/segment/SegmentReconfigureCommand.h"
+#include "commands/segment/SegmentResizeFromStartCommand.h"
+#include "CompositionItemHelper.h"
+#include "CompositionModel.h"
+#include "CompositionView.h"
+#include "document/RosegardenGUIDoc.h"
+#include "gui/general/BaseTool.h"
+#include "gui/application/RosegardenGUIApp.h"
+#include "gui/general/RosegardenCanvasView.h"
+#include "gui/widgets/ProgressDialog.h"
+#include "SegmentTool.h"
+#include <kcommand.h>
+#include <kmessagebox.h>
+#include <qcursor.h>
+#include <qevent.h>
+#include <qpoint.h>
+#include <qrect.h>
+#include <qstring.h>
+
+
+namespace Rosegarden
+{
+
+SegmentResizer::SegmentResizer(CompositionView *c, RosegardenGUIDoc *d,
+ int edgeThreshold)
+ : SegmentTool(c, d),
+ m_edgeThreshold(edgeThreshold)
+{
+ RG_DEBUG << "SegmentResizer()\n";
+}
+
+void SegmentResizer::ready()
+{
+ m_canvas->viewport()->setCursor(Qt::sizeHorCursor);
+ connect(m_canvas, SIGNAL(contentsMoving (int, int)),
+ this, SLOT(slotCanvasScrolled(int, int)));
+ setBasicContextHelp(false);
+}
+
+void SegmentResizer::stow()
+{
+ disconnect(m_canvas, SIGNAL(contentsMoving (int, int)),
+ this, SLOT(slotCanvasScrolled(int, int)));
+}
+
+void SegmentResizer::slotCanvasScrolled(int newX, int newY)
+{
+ QMouseEvent tmpEvent(QEvent::MouseMove,
+ m_canvas->viewport()->mapFromGlobal(QCursor::pos()) + QPoint(newX, newY),
+ Qt::NoButton, Qt::NoButton);
+ handleMouseMove(&tmpEvent);
+}
+
+void SegmentResizer::handleMouseButtonPress(QMouseEvent *e)
+{
+ RG_DEBUG << "SegmentResizer::handleMouseButtonPress" << endl;
+ m_canvas->getModel()->clearSelected();
+
+ CompositionItem item = m_canvas->getFirstItemAt(e->pos());
+
+ if (item) {
+ RG_DEBUG << "SegmentResizer::handleMouseButtonPress - got item" << endl;
+ setCurrentItem(item);
+
+ // Are we resizing from start or end?
+ if (item->rect().x() + item->rect().width() / 2 > e->pos().x()) {
+ m_resizeStart = true;
+ } else {
+ m_resizeStart = false;
+ }
+
+ m_canvas->getModel()->startChange(item, m_resizeStart ? CompositionModel::ChangeResizeFromStart : CompositionModel::ChangeResizeFromEnd);
+
+ }
+}
+
+void SegmentResizer::handleMouseButtonRelease(QMouseEvent *e)
+{
+ RG_DEBUG << "SegmentResizer::handleMouseButtonRelease" << endl;
+
+ bool rescale = (e->state() & Qt::ControlButton);
+
+ if (m_currentItem) {
+
+ Segment* segment = CompositionItemHelper::getSegment(m_currentItem);
+
+ // We only want to snap the end that we were actually resizing.
+
+ timeT oldStartTime, oldEndTime;
+
+ oldStartTime = segment->getStartTime();
+ oldEndTime = segment->getEndMarkerTime();
+
+ timeT newStartTime, newEndTime;
+
+ if (m_resizeStart) {
+ newStartTime = CompositionItemHelper::getStartTime
+ (m_currentItem, m_canvas->grid());
+ newEndTime = oldEndTime;
+ } else {
+ newEndTime = CompositionItemHelper::getEndTime
+ (m_currentItem, m_canvas->grid());
+ newStartTime = oldStartTime;
+ }
+
+ if (changeMade()) {
+
+ if (newStartTime > newEndTime) std::swap(newStartTime, newEndTime);
+
+ if (rescale) {
+
+ if (segment->getType() == Segment::Audio) {
+
+ try {
+ m_doc->getAudioFileManager().testAudioPath();
+ } catch (AudioFileManager::BadAudioPathException) {
+ if (KMessageBox::warningContinueCancel
+ (0,
+ i18n("The audio file path does not exist or is not writable.\nYou must set the audio file path to a valid directory in Document Properties before rescaling an audio file.\nWould you like to set it now?"),
+ i18n("Warning"),
+ i18n("Set audio file path")) == KMessageBox::Continue) {
+ RosegardenGUIApp::self()->slotOpenAudioPathSettings();
+ }
+ }
+
+ float ratio = float(newEndTime - newStartTime) /
+ float(oldEndTime - oldStartTime);
+
+ AudioSegmentRescaleCommand *command =
+ new AudioSegmentRescaleCommand(m_doc, segment, ratio,
+ newStartTime, newEndTime);
+
+ ProgressDialog progressDlg
+ (i18n("Rescaling audio file..."), 100, 0);
+ progressDlg.setAutoClose(false);
+ progressDlg.setAutoReset(false);
+ progressDlg.show();
+ command->connectProgressDialog(&progressDlg);
+
+ addCommandToHistory(command);
+
+ progressDlg.setLabel(i18n("Generating audio preview..."));
+ command->disconnectProgressDialog(&progressDlg);
+ connect(&m_doc->getAudioFileManager(), SIGNAL(setProgress(int)),
+ progressDlg.progressBar(), SLOT(setValue(int)));
+ connect(&progressDlg, SIGNAL(cancelClicked()),
+ &m_doc->getAudioFileManager(), SLOT(slotStopPreview()));
+
+ int fid = command->getNewAudioFileId();
+ if (fid >= 0) {
+ RosegardenGUIApp::self()->slotAddAudioFile(fid);
+ m_doc->getAudioFileManager().generatePreview(fid);
+ }
+
+ } else {
+
+ SegmentRescaleCommand *command =
+ new SegmentRescaleCommand(segment,
+ newEndTime - newStartTime,
+ oldEndTime - oldStartTime,
+ newStartTime);
+ addCommandToHistory(command);
+ }
+ } else {
+
+ if (m_resizeStart) {
+
+ if (segment->getType() == Segment::Audio) {
+ addCommandToHistory(new AudioSegmentResizeFromStartCommand
+ (segment, newStartTime));
+ } else {
+ addCommandToHistory(new SegmentResizeFromStartCommand
+ (segment, newStartTime));
+ }
+
+ } else {
+
+ SegmentReconfigureCommand *command =
+ new SegmentReconfigureCommand("Resize Segment");
+
+ int trackPos = CompositionItemHelper::getTrackPos
+ (m_currentItem, m_canvas->grid());
+
+ Composition &comp = m_doc->getComposition();
+ Track *track = comp.getTrackByPosition(trackPos);
+
+ command->addSegment(segment,
+ newStartTime,
+ newEndTime,
+ track->getId());
+ addCommandToHistory(command);
+ }
+ }
+ }
+ }
+
+ m_canvas->getModel()->endChange();
+ m_canvas->updateContents();
+ setChangeMade(false);
+ m_currentItem = CompositionItem();
+ setBasicContextHelp();
+}
+
+int SegmentResizer::handleMouseMove(QMouseEvent *e)
+{
+ // RG_DEBUG << "SegmentResizer::handleMouseMove" << endl;
+
+ bool rescale = (e->state() & Qt::ControlButton);
+
+ if (!m_currentItem) {
+ setBasicContextHelp(rescale);
+ return RosegardenCanvasView::NoFollow;
+ }
+
+ if (rescale) {
+ if (!m_canvas->isFineGrain()) {
+ setContextHelp(i18n("Hold Shift to avoid snapping to beat grid"));
+ } else {
+ clearContextHelp();
+ }
+ } else {
+ if (!m_canvas->isFineGrain()) {
+ setContextHelp(i18n("Hold Shift to avoid snapping to beat grid; hold Ctrl as well to rescale contents"));
+ } else {
+ setContextHelp("Hold Ctrl to rescale contents");
+ }
+ }
+
+ Segment* segment = CompositionItemHelper::getSegment(m_currentItem);
+
+ // Don't allow Audio segments to resize yet
+ //
+ /*!!!
+ if (segment->getType() == Segment::Audio)
+ {
+ m_currentItem = CompositionItem();
+ KMessageBox::information(m_canvas,
+ i18n("You can't yet resize an audio segment!"));
+ return RosegardenCanvasView::NoFollow;
+ }
+ */
+
+ QRect oldRect = m_currentItem->rect();
+
+ m_canvas->setSnapGrain(true);
+
+ timeT time = m_canvas->grid().snapX(e->pos().x());
+ timeT snap = m_canvas->grid().getSnapTime(double(e->pos().x()));
+ if (snap == 0)
+ snap = Note(Note::Shortest).getDuration();
+
+ // We only want to snap the end that we were actually resizing.
+
+ timeT itemStartTime, itemEndTime;
+
+ if (m_resizeStart) {
+ itemStartTime = CompositionItemHelper::getStartTime
+ (m_currentItem, m_canvas->grid());
+ itemEndTime = segment->getEndMarkerTime();
+ } else {
+ itemEndTime = CompositionItemHelper::getEndTime
+ (m_currentItem, m_canvas->grid());
+ itemStartTime = segment->getStartTime();
+ }
+
+ timeT duration = 0;
+
+ if (m_resizeStart) {
+
+ duration = itemEndTime - time;
+ // RG_DEBUG << "SegmentResizer::handleMouseMove() resize start : duration = "
+ // << duration << " - snap = " << snap
+ // << " - itemEndTime : " << itemEndTime
+ // << " - time : " << time
+ // << endl;
+
+ timeT newStartTime = 0;
+
+ if ((duration > 0 && duration < snap) ||
+ (duration < 0 && duration > -snap)) {
+
+ newStartTime = itemEndTime - (duration < 0 ? -snap : snap);
+
+ } else {
+
+ newStartTime = itemEndTime - duration;
+
+ }
+
+ CompositionItemHelper::setStartTime(m_currentItem,
+ newStartTime,
+ m_canvas->grid());
+ } else { // resize end
+
+ duration = time - itemStartTime;
+
+ timeT newEndTime = 0;
+
+ // RG_DEBUG << "SegmentResizer::handleMouseMove() resize end : duration = "
+ // << duration << " - snap = " << snap
+ // << " - itemEndTime : " << itemEndTime
+ // << " - time : " << time
+ // << endl;
+
+ if ((duration > 0 && duration < snap) ||
+ (duration < 0 && duration > -snap)) {
+
+ newEndTime = (duration < 0 ? -snap : snap) + itemStartTime;
+
+ } else {
+
+ newEndTime = duration + itemStartTime;
+
+ }
+
+ CompositionItemHelper::setEndTime(m_currentItem,
+ newEndTime,
+ m_canvas->grid());
+ }
+
+ if (duration != 0)
+ setChangeMade(true);
+
+ m_canvas->slotUpdateSegmentsDrawBuffer(m_currentItem->rect() | oldRect);
+
+ return RosegardenCanvasView::FollowHorizontal;
+}
+
+bool SegmentResizer::cursorIsCloseEnoughToEdge(const CompositionItem& p, const QPoint &coord,
+ int edgeThreshold, bool &start)
+{
+ if (abs(p->rect().x() + p->rect().width() - coord.x()) < edgeThreshold) {
+ start = false;
+ return true;
+ } else if (abs(p->rect().x() - coord.x()) < edgeThreshold) {
+ start = true;
+ return true;
+ } else {
+ return false;
+ }
+}
+
+void SegmentResizer::setBasicContextHelp(bool ctrlPressed)
+{
+ if (ctrlPressed) {
+ setContextHelp(i18n("Click and drag to resize a segment; hold Ctrl as well to rescale its contents"));
+ } else {
+ setContextHelp(i18n("Click and drag to rescale segment"));
+ }
+}
+
+const QString SegmentResizer::ToolName = "segmentresizer";
+
+}
+#include "SegmentResizer.moc"
diff --git a/src/gui/editors/segment/segmentcanvas/SegmentResizer.h b/src/gui/editors/segment/segmentcanvas/SegmentResizer.h
new file mode 100644
index 0000000..9d54573
--- /dev/null
+++ b/src/gui/editors/segment/segmentcanvas/SegmentResizer.h
@@ -0,0 +1,87 @@
+
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
+
+/*
+ Rosegarden
+ A MIDI and audio sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_SEGMENTRESIZER_H_
+#define _RG_SEGMENTRESIZER_H_
+
+#include "SegmentTool.h"
+#include <qstring.h>
+
+
+class QPoint;
+class QMouseEvent;
+class CompositionItem;
+
+
+namespace Rosegarden
+{
+
+class RosegardenGUIDoc;
+class CompositionView;
+
+
+/**
+ * Segment Resizer tool. Allows resizing only at the end of the segment part
+ */
+class SegmentResizer : public SegmentTool
+{
+ Q_OBJECT
+
+ friend class SegmentToolBox;
+ friend class SegmentSelector;
+
+public:
+
+ virtual void ready();
+ virtual void stow();
+
+ virtual void handleMouseButtonPress(QMouseEvent*);
+ virtual void handleMouseButtonRelease(QMouseEvent*);
+ virtual int handleMouseMove(QMouseEvent*);
+
+ static bool cursorIsCloseEnoughToEdge(const CompositionItem&, const QPoint&, int, bool &);
+
+ void setEdgeThreshold(int e) { m_edgeThreshold = e; }
+ int getEdgeThreshold() { return m_edgeThreshold; }
+
+ static const QString ToolName;
+
+protected slots:
+ void slotCanvasScrolled(int newX, int newY);
+
+protected:
+ SegmentResizer(CompositionView*, RosegardenGUIDoc*, int edgeThreshold = 10);
+ void setBasicContextHelp(bool ctrlPressed = false);
+
+ //--------------- Data members ---------------------------------
+
+ int m_edgeThreshold;
+ bool m_resizeStart;
+};
+
+
+}
+
+#endif
diff --git a/src/gui/editors/segment/segmentcanvas/SegmentSelector.cpp b/src/gui/editors/segment/segmentcanvas/SegmentSelector.cpp
new file mode 100644
index 0000000..35ec639
--- /dev/null
+++ b/src/gui/editors/segment/segmentcanvas/SegmentSelector.cpp
@@ -0,0 +1,532 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
+
+/*
+ Rosegarden
+ A MIDI and audio sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "SegmentSelector.h"
+
+#include "base/Event.h"
+#include <klocale.h>
+#include "misc/Debug.h"
+#include "base/Composition.h"
+#include "base/RealTime.h"
+#include "base/SnapGrid.h"
+#include "base/Selection.h"
+#include "base/Track.h"
+#include "commands/segment/SegmentQuickCopyCommand.h"
+#include "commands/segment/SegmentReconfigureCommand.h"
+#include "CompositionItemHelper.h"
+#include "CompositionModel.h"
+#include "CompositionView.h"
+#include "document/RosegardenGUIDoc.h"
+#include "document/ConfigGroups.h"
+#include "gui/general/BaseTool.h"
+#include "gui/general/RosegardenCanvasView.h"
+#include "SegmentPencil.h"
+#include "SegmentResizer.h"
+#include "SegmentTool.h"
+#include "SegmentToolBox.h"
+#include <kapplication.h>
+#include <kconfig.h>
+#include <qcursor.h>
+#include <qevent.h>
+#include <qpoint.h>
+#include <qrect.h>
+#include <qstring.h>
+
+
+namespace Rosegarden
+{
+
+SegmentSelector::SegmentSelector(CompositionView *c, RosegardenGUIDoc *d)
+ : SegmentTool(c, d),
+ m_segmentAddMode(false),
+ m_segmentCopyMode(false),
+ m_segmentQuickCopyDone(false),
+ m_buttonPressed(false),
+ m_selectionMoveStarted(false),
+ m_dispatchTool(0)
+{
+ RG_DEBUG << "SegmentSelector()\n";
+}
+
+SegmentSelector::~SegmentSelector()
+{}
+
+void SegmentSelector::ready()
+{
+ m_canvas->viewport()->setCursor(Qt::arrowCursor);
+ connect(m_canvas, SIGNAL(contentsMoving (int, int)),
+ this, SLOT(slotCanvasScrolled(int, int)));
+ setContextHelp(i18n("Click and drag to select segments"));
+}
+
+void SegmentSelector::stow()
+{}
+
+void SegmentSelector::slotCanvasScrolled(int newX, int newY)
+{
+ QMouseEvent tmpEvent(QEvent::MouseMove,
+ m_canvas->viewport()->mapFromGlobal(QCursor::pos()) + QPoint(newX, newY),
+ Qt::NoButton, Qt::NoButton);
+ handleMouseMove(&tmpEvent);
+}
+
+void
+SegmentSelector::handleMouseButtonPress(QMouseEvent *e)
+{
+ RG_DEBUG << "SegmentSelector::handleMouseButtonPress\n";
+ m_buttonPressed = true;
+
+ CompositionItem item = m_canvas->getFirstItemAt(e->pos());
+
+ // If we're in segmentAddMode or not clicking on an item then we don't
+ // clear the selection vector. If we're clicking on an item and it's
+ // not in the selection - then also clear the selection.
+ //
+ if ((!m_segmentAddMode && !item) ||
+ (!m_segmentAddMode && !(m_canvas->getModel()->isSelected(item)))) {
+ m_canvas->getModel()->clearSelected();
+ }
+
+ if (item) {
+
+ // Fifteen percent of the width of the SegmentItem, up to 10px
+ //
+ int threshold = int(float(item->rect().width()) * 0.15);
+ if (threshold == 0) threshold = 1;
+ if (threshold > 10) threshold = 10;
+
+ bool start = false;
+
+ // Resize if we're dragging from the edge, provided we aren't
+ // in segment-add mode with at least one segment already
+ // selected -- as we aren't able to resize multiple segments
+ // at once, we should assume the segment-add aspect takes
+ // priority
+
+ if ((!m_segmentAddMode ||
+ !m_canvas->getModel()->haveSelection()) &&
+ SegmentResizer::cursorIsCloseEnoughToEdge(item, e->pos(), threshold, start)) {
+
+ SegmentResizer* resizer =
+ dynamic_cast<SegmentResizer*>(getToolBox()->getTool(SegmentResizer::ToolName));
+
+ resizer->setEdgeThreshold(threshold);
+
+ // For the moment we only allow resizing of a single segment
+ // at a time.
+ //
+ m_canvas->getModel()->clearSelected();
+
+ m_dispatchTool = resizer;
+
+ m_dispatchTool->ready(); // set mouse cursor
+ m_dispatchTool->handleMouseButtonPress(e);
+ return ;
+ }
+
+ bool selecting = true;
+
+ if (m_segmentAddMode && m_canvas->getModel()->isSelected(item)) {
+ selecting = false;
+ } else {
+ // put the segment in 'move' mode only if it's being selected
+ m_canvas->getModel()->startChange(item, CompositionModel::ChangeMove);
+ }
+
+ m_canvas->getModel()->setSelected(item, selecting);
+
+ // Moving
+ //
+ // RG_DEBUG << "SegmentSelector::handleMouseButtonPress - m_currentItem = " << item << endl;
+ m_currentItem = item;
+ m_clickPoint = e->pos();
+
+ int guideX = item->rect().x();
+ int guideY = item->rect().y();
+
+ m_canvas->setGuidesPos(guideX, guideY);
+
+ m_canvas->setDrawGuides(true);
+
+ } else {
+
+ // Add on middle button or ctrl+left - bounding box on rest
+ //
+ if (e->button() == MidButton ||
+ (e->button() == LeftButton && (e->state() & Qt::ControlButton))) {
+
+ m_dispatchTool = getToolBox()->getTool(SegmentPencil::ToolName);
+
+ if (m_dispatchTool) {
+ m_dispatchTool->ready(); // set mouse cursor
+ m_dispatchTool->handleMouseButtonPress(e);
+ }
+
+ return ;
+
+ } else {
+
+ m_canvas->setSelectionRectPos(e->pos());
+ m_canvas->setDrawSelectionRect(true);
+ if (!m_segmentAddMode)
+ m_canvas->getModel()->clearSelected();
+
+ }
+ }
+
+ // Tell the RosegardenGUIView that we've selected some new Segments -
+ // when the list is empty we're just unselecting.
+ //
+ m_canvas->getModel()->signalSelection();
+
+ m_passedInertiaEdge = false;
+}
+
+void
+SegmentSelector::handleMouseButtonRelease(QMouseEvent *e)
+{
+ m_buttonPressed = false;
+
+ // Hide guides and stuff
+ //
+ m_canvas->setDrawGuides(false);
+ m_canvas->hideTextFloat();
+
+ if (m_dispatchTool) {
+ m_dispatchTool->handleMouseButtonRelease(e);
+ m_dispatchTool = 0;
+ m_canvas->viewport()->setCursor(Qt::arrowCursor);
+ return ;
+ }
+
+ int startDragTrackPos = m_canvas->grid().getYBin(m_clickPoint.y());
+ int currentTrackPos = m_canvas->grid().getYBin(e->pos().y());
+ int trackDiff = currentTrackPos - startDragTrackPos;
+
+ if (!m_currentItem) {
+ m_canvas->setDrawSelectionRect(false);
+ m_canvas->getModel()->finalizeSelectionRect();
+ m_canvas->getModel()->signalSelection();
+ return ;
+ }
+
+ m_canvas->viewport()->setCursor(Qt::arrowCursor);
+
+ Composition &comp = m_doc->getComposition();
+
+ if (m_canvas->getModel()->isSelected(m_currentItem)) {
+
+ CompositionModel::itemcontainer& changingItems = m_canvas->getModel()->getChangingItems();
+ CompositionModel::itemcontainer::iterator it;
+
+ if (changeMade()) {
+
+ SegmentReconfigureCommand *command =
+ new SegmentReconfigureCommand
+ (m_selectedItems.size() == 1 ? i18n("Move Segment") :
+ i18n("Move Segments"));
+
+ for (it = changingItems.begin();
+ it != changingItems.end();
+ it++) {
+
+ CompositionItem item = *it;
+
+ Segment* segment = CompositionItemHelper::getSegment(item);
+
+ TrackId origTrackId = segment->getTrack();
+ int trackPos = comp.getTrackPositionById(origTrackId);
+ trackPos += trackDiff;
+
+ if (trackPos < 0) {
+ trackPos = 0;
+ } else if (trackPos >= comp.getNbTracks()) {
+ trackPos = comp.getNbTracks() - 1;
+ }
+
+ Track *newTrack = comp.getTrackByPosition(trackPos);
+ int newTrackId = origTrackId;
+ if (newTrack) newTrackId = newTrack->getId();
+
+ timeT itemStartTime = CompositionItemHelper::getStartTime
+ (item, m_canvas->grid());
+
+ // We absolutely don't want to snap the end time to
+ // the grid. We want it to remain exactly the same as
+ // it was, but relative to the new start time.
+ timeT itemEndTime = itemStartTime + segment->getEndMarkerTime()
+ - segment->getStartTime();
+
+// std::cerr << "releasing segment " << segment << ": mouse started at track " << startDragTrackPos << ", is now at " << currentTrackPos << ", diff is " << trackDiff << ", moving from track pos " << comp.getTrackPositionById(origTrackId) << " to " << trackPos << ", id " << origTrackId << " to " << newTrackId << std::endl;
+
+ command->addSegment(segment,
+ itemStartTime,
+ itemEndTime,
+ newTrackId);
+ }
+
+ addCommandToHistory(command);
+ }
+
+ m_canvas->getModel()->endChange();
+ m_canvas->slotUpdateSegmentsDrawBuffer();
+ }
+
+ // if we've just finished a quick copy then drop the Z level back
+ if (m_segmentQuickCopyDone) {
+ m_segmentQuickCopyDone = false;
+ // m_currentItem->setZ(2); // see SegmentItem::setSelected --??
+ }
+
+ setChangeMade(false);
+
+ m_selectionMoveStarted = false;
+
+ m_currentItem = CompositionItem();
+
+ setContextHelpFor(e->pos());
+}
+
+int
+SegmentSelector::handleMouseMove(QMouseEvent *e)
+{
+ if (!m_buttonPressed) {
+ setContextHelpFor(e->pos(), (e->state() & Qt::ControlButton));
+ return RosegardenCanvasView::NoFollow;
+ }
+
+ if (m_dispatchTool) {
+ return m_dispatchTool->handleMouseMove(e);
+ }
+
+ Composition &comp = m_doc->getComposition();
+
+ if (!m_currentItem) {
+
+ // RG_DEBUG << "SegmentSelector::handleMouseMove: no current item\n";
+
+ // do a bounding box
+ QRect selectionRect = m_canvas->getSelectionRect();
+
+ m_canvas->setDrawSelectionRect(true);
+
+ // same as for notation view
+ int w = int(e->pos().x() - selectionRect.x());
+ int h = int(e->pos().y() - selectionRect.y());
+ if (w > 0)
+ ++w;
+ else
+ --w;
+ if (h > 0)
+ ++h;
+ else
+ --h;
+
+ // Translate these points
+ //
+ m_canvas->setSelectionRectSize(w, h);
+
+ m_canvas->getModel()->signalSelection();
+ return RosegardenCanvasView::FollowHorizontal | RosegardenCanvasView::FollowVertical;
+ }
+
+ m_canvas->viewport()->setCursor(Qt::sizeAllCursor);
+
+ if (m_segmentCopyMode && !m_segmentQuickCopyDone) {
+ KMacroCommand *mcommand = new KMacroCommand
+ (SegmentQuickCopyCommand::getGlobalName());
+
+ SegmentSelection selectedItems = m_canvas->getSelectedSegments();
+ SegmentSelection::iterator it;
+ for (it = selectedItems.begin();
+ it != selectedItems.end();
+ it++) {
+ SegmentQuickCopyCommand *command =
+ new SegmentQuickCopyCommand(*it);
+
+ mcommand->addCommand(command);
+ }
+
+ addCommandToHistory(mcommand);
+
+ // generate SegmentItem
+ //
+ m_canvas->updateContents();
+ m_segmentQuickCopyDone = true;
+ }
+
+ m_canvas->setSnapGrain(true);
+
+ int startDragTrackPos = m_canvas->grid().getYBin(m_clickPoint.y());
+ int currentTrackPos = m_canvas->grid().getYBin(e->pos().y());
+ int trackDiff = currentTrackPos - startDragTrackPos;
+
+ if (m_canvas->getModel()->isSelected(m_currentItem)) {
+
+ if (!m_canvas->isFineGrain()) {
+ setContextHelp(i18n("Hold Shift to avoid snapping to beat grid"));
+ } else {
+ clearContextHelp();
+ }
+
+ // RG_DEBUG << "SegmentSelector::handleMouseMove: current item is selected\n";
+
+ if (!m_selectionMoveStarted) { // start move on selected items only once
+ m_canvas->getModel()->startChangeSelection(CompositionModel::ChangeMove);
+ m_selectionMoveStarted = true;
+ }
+
+ CompositionModel::itemcontainer& changingItems = m_canvas->getModel()->getChangingItems();
+ setCurrentItem(CompositionItemHelper::findSiblingCompositionItem(changingItems, m_currentItem));
+
+ CompositionModel::itemcontainer::iterator it;
+ int guideX = 0;
+ int guideY = 0;
+
+ for (it = changingItems.begin();
+ it != changingItems.end();
+ ++it) {
+
+ // RG_DEBUG << "SegmentSelector::handleMouseMove() : movingItem at "
+ // << (*it)->rect().x() << "," << (*it)->rect().y() << endl;
+
+ int dx = e->pos().x() - m_clickPoint.x(),
+ dy = e->pos().y() - m_clickPoint.y();
+
+ const int inertiaDistance = m_canvas->grid().getYSnap() / 3;
+ if (!m_passedInertiaEdge &&
+ (dx < inertiaDistance && dx > -inertiaDistance) &&
+ (dy < inertiaDistance && dy > -inertiaDistance)) {
+ return RosegardenCanvasView::NoFollow;
+ } else {
+ m_passedInertiaEdge = true;
+ }
+
+ timeT newStartTime = m_canvas->grid().snapX((*it)->savedRect().x() + dx);
+
+ int newX = int(m_canvas->grid().getRulerScale()->getXForTime(newStartTime));
+
+ int trackPos = m_canvas->grid().getYBin((*it)->savedRect().y());
+
+// std::cerr << "segment " << *it << ": mouse started at track " << startDragTrackPos << ", is now at " << currentTrackPos << ", trackPos from " << trackPos << " to ";
+
+ trackPos += trackDiff;
+
+// std::cerr << trackPos << std::endl;
+
+ if (trackPos < 0) {
+ trackPos = 0;
+ } else if (trackPos >= comp.getNbTracks()) {
+ trackPos = comp.getNbTracks() - 1;
+ }
+
+ int newY = m_canvas->grid().getYBinCoordinate(trackPos);
+
+ (*it)->moveTo(newX, newY);
+ setChangeMade(true);
+ }
+
+ if (changeMade())
+ m_canvas->getModel()->signalContentChange();
+
+ guideX = m_currentItem->rect().x();
+ guideY = m_currentItem->rect().y();
+
+ m_canvas->setGuidesPos(guideX, guideY);
+
+ timeT currentItemStartTime = m_canvas->grid().snapX(m_currentItem->rect().x());
+
+ RealTime time = comp.getElapsedRealTime(currentItemStartTime);
+ QString ms;
+ ms.sprintf("%03d", time.msec());
+
+ int bar, beat, fraction, remainder;
+ comp.getMusicalTimeForAbsoluteTime(currentItemStartTime, bar, beat, fraction, remainder);
+
+ QString posString = QString("%1.%2s (%3, %4, %5)")
+ .arg(time.sec).arg(ms)
+ .arg(bar + 1).arg(beat).arg(fraction);
+
+ m_canvas->setTextFloat(guideX + 10, guideY - 30, posString);
+ m_canvas->updateContents();
+
+ } else {
+ // RG_DEBUG << "SegmentSelector::handleMouseMove: current item not selected\n";
+ }
+
+ return RosegardenCanvasView::FollowHorizontal | RosegardenCanvasView::FollowVertical;
+}
+
+void SegmentSelector::setContextHelpFor(QPoint p, bool ctrlPressed)
+{
+ kapp->config()->setGroup(GeneralOptionsConfigGroup);
+ if (!kapp->config()->readBoolEntry("toolcontexthelp", true)) return;
+
+ CompositionItem item = m_canvas->getFirstItemAt(p);
+
+ if (!item) {
+ setContextHelp(i18n("Click and drag to select segments; middle-click and drag to draw an empty segment"));
+
+ } else {
+
+ // Same logic as in handleMouseButtonPress to establish
+ // whether we'd be moving or resizing
+
+ int threshold = int(float(item->rect().width()) * 0.15);
+ if (threshold == 0) threshold = 1;
+ if (threshold > 10) threshold = 10;
+ bool start = false;
+
+ if ((!m_segmentAddMode ||
+ !m_canvas->getModel()->haveSelection()) &&
+ SegmentResizer::cursorIsCloseEnoughToEdge(item, p,
+ threshold, start)) {
+ if (!ctrlPressed) {
+ setContextHelp(i18n("Click and drag to resize a segment; hold Ctrl as well to rescale its contents"));
+ } else {
+ setContextHelp(i18n("Click and drag to rescale segment"));
+ }
+ } else {
+ if (m_canvas->getModel()->haveMultipleSelection()) {
+ if (!ctrlPressed) {
+ setContextHelp(i18n("Click and drag to move segments; hold Ctrl as well to copy them"));
+ } else {
+ setContextHelp(i18n("Click and drag to copy segments"));
+ }
+ } else {
+ if (!ctrlPressed) {
+ setContextHelp(i18n("Click and drag to move segment; hold Ctrl as well to copy it; double-click to edit"));
+ } else {
+ setContextHelp(i18n("Click and drag to copy segment"));
+ }
+ }
+ }
+ }
+}
+
+const QString SegmentSelector::ToolName = "segmentselector";
+
+}
+#include "SegmentSelector.moc"
diff --git a/src/gui/editors/segment/segmentcanvas/SegmentSelector.h b/src/gui/editors/segment/segmentcanvas/SegmentSelector.h
new file mode 100644
index 0000000..a6d8d9c
--- /dev/null
+++ b/src/gui/editors/segment/segmentcanvas/SegmentSelector.h
@@ -0,0 +1,109 @@
+
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
+
+/*
+ Rosegarden
+ A MIDI and audio sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_SEGMENTSELECTOR_H_
+#define _RG_SEGMENTSELECTOR_H_
+
+#include "SegmentTool.h"
+#include <qpoint.h>
+#include <qstring.h>
+
+
+class QMouseEvent;
+
+
+namespace Rosegarden
+{
+
+class RosegardenGUIDoc;
+class CompositionView;
+
+
+class SegmentSelector : public SegmentTool
+{
+ Q_OBJECT
+
+ friend class SegmentToolBox;
+ friend class SegmentTool;
+
+public:
+
+ virtual ~SegmentSelector();
+
+ virtual void ready();
+ virtual void stow();
+
+ virtual void handleMouseButtonPress(QMouseEvent*);
+ virtual void handleMouseButtonRelease(QMouseEvent*);
+ virtual int handleMouseMove(QMouseEvent*);
+
+ // These two alter the behaviour of the selection mode
+ //
+ // - SegmentAdd (usually when SHIFT is held down) allows
+ // multiple selections of Segments.
+ //
+ // - SegmentCopy (usually CONTROL) allows draw and drop
+ // copying of Segments - it's a quick shortcut
+ //
+ void setSegmentAdd(const bool &value) { m_segmentAddMode = value; }
+ void setSegmentCopy(const bool &value) { m_segmentCopyMode = value; }
+
+ bool isSegmentAdding() const { return m_segmentAddMode; }
+ bool isSegmentCopying() const { return m_segmentCopyMode; }
+
+ // Return the SegmentItem list for other tools to use
+ //
+ SegmentItemList* getSegmentItemList() { return &m_selectedItems; }
+
+ static const QString ToolName;
+
+protected slots:
+ void slotCanvasScrolled(int newX, int newY);
+
+protected:
+ SegmentSelector(CompositionView*, RosegardenGUIDoc*);
+
+ void setContextHelpFor(QPoint p, bool ctrlPressed = false);
+
+ //--------------- Data members ---------------------------------
+
+ SegmentItemList m_selectedItems;
+
+ bool m_segmentAddMode;
+ bool m_segmentCopyMode;
+ QPoint m_clickPoint;
+ bool m_segmentQuickCopyDone;
+ bool m_passedInertiaEdge;
+ bool m_buttonPressed;
+ bool m_selectionMoveStarted;
+
+ SegmentTool *m_dispatchTool;
+};
+
+
+
+}
+
+#endif
diff --git a/src/gui/editors/segment/segmentcanvas/SegmentSplitter.cpp b/src/gui/editors/segment/segmentcanvas/SegmentSplitter.cpp
new file mode 100644
index 0000000..4fd48c3
--- /dev/null
+++ b/src/gui/editors/segment/segmentcanvas/SegmentSplitter.cpp
@@ -0,0 +1,175 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
+
+/*
+ Rosegarden
+ A MIDI and audio sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "SegmentSplitter.h"
+
+#include "misc/Debug.h"
+#include "base/Segment.h"
+#include "base/SnapGrid.h"
+#include "commands/segment/AudioSegmentSplitCommand.h"
+#include "commands/segment/SegmentSplitCommand.h"
+#include "CompositionItemHelper.h"
+#include "CompositionView.h"
+#include "document/RosegardenGUIDoc.h"
+#include "gui/general/BaseTool.h"
+#include "gui/general/RosegardenCanvasView.h"
+#include "SegmentTool.h"
+#include <kcommand.h>
+#include <qpoint.h>
+#include <qrect.h>
+#include <qstring.h>
+#include <klocale.h>
+
+
+namespace Rosegarden
+{
+
+SegmentSplitter::SegmentSplitter(CompositionView *c, RosegardenGUIDoc *d)
+ : SegmentTool(c, d),
+ m_prevX(0),
+ m_prevY(0)
+{
+ RG_DEBUG << "SegmentSplitter()\n";
+}
+
+SegmentSplitter::~SegmentSplitter()
+{}
+
+void SegmentSplitter::ready()
+{
+ m_canvas->viewport()->setCursor(Qt::splitHCursor);
+ setBasicContextHelp();
+}
+
+void
+SegmentSplitter::handleMouseButtonPress(QMouseEvent *e)
+{
+ // Remove cursor and replace with line on a SegmentItem
+ // at where the cut will be made
+ CompositionItem item = m_canvas->getFirstItemAt(e->pos());
+
+ if (item) {
+ m_canvas->viewport()->setCursor(Qt::blankCursor);
+ m_prevX = item->rect().x();
+ m_prevX = item->rect().y();
+ drawSplitLine(e);
+ delete item;
+ }
+
+}
+
+void
+SegmentSplitter::handleMouseButtonRelease(QMouseEvent *e)
+{
+ setBasicContextHelp();
+
+ CompositionItem item = m_canvas->getFirstItemAt(e->pos());
+
+ if (item) {
+ m_canvas->setSnapGrain(true);
+ Segment* segment = CompositionItemHelper::getSegment(item);
+
+ if (segment->getType() == Segment::Audio) {
+ AudioSegmentSplitCommand *command =
+ new AudioSegmentSplitCommand(segment, m_canvas->grid().snapX(e->pos().x()));
+ addCommandToHistory(command);
+ } else {
+ SegmentSplitCommand *command =
+ new SegmentSplitCommand(segment, m_canvas->grid().snapX(e->pos().x()));
+ addCommandToHistory(command);
+ }
+
+ m_canvas->updateContents(item->rect());
+ delete item;
+ }
+
+ // Reinstate the cursor
+ m_canvas->viewport()->setCursor(Qt::splitHCursor);
+ m_canvas->slotHideSplitLine();
+}
+
+int
+SegmentSplitter::handleMouseMove(QMouseEvent *e)
+{
+ setBasicContextHelp();
+
+ CompositionItem item = m_canvas->getFirstItemAt(e->pos());
+
+ if (item) {
+// m_canvas->viewport()->setCursor(Qt::blankCursor);
+ drawSplitLine(e);
+ delete item;
+ return RosegardenCanvasView::FollowHorizontal;
+ } else {
+ m_canvas->viewport()->setCursor(Qt::splitHCursor);
+ m_canvas->slotHideSplitLine();
+ return RosegardenCanvasView::NoFollow;
+ }
+}
+
+void
+SegmentSplitter::drawSplitLine(QMouseEvent *e)
+{
+ m_canvas->setSnapGrain(true);
+
+ // Turn the real X into a snapped X
+ //
+ timeT xT = m_canvas->grid().snapX(e->pos().x());
+ int x = (int)(m_canvas->grid().getRulerScale()->getXForTime(xT));
+
+ // Need to watch y doesn't leak over the edges of the
+ // current Segment.
+ //
+ int y = m_canvas->grid().snapY(e->pos().y());
+
+ m_canvas->slotShowSplitLine(x, y);
+
+ QRect updateRect(std::max(0, std::min(x, m_prevX) - 5), y,
+ std::max(m_prevX, x) + 5, m_prevY + m_canvas->grid().getYSnap());
+ m_canvas->updateContents(updateRect);
+ m_prevX = x;
+ m_prevY = y;
+}
+
+void
+SegmentSplitter::contentsMouseDoubleClickEvent(QMouseEvent*)
+{
+ // DO NOTHING
+}
+
+void
+SegmentSplitter::setBasicContextHelp()
+{
+ if (!m_canvas->isFineGrain()) {
+ setContextHelp(i18n("Click on a segment to split it in two; hold Shift to avoid snapping to beat grid"));
+ } else {
+ setContextHelp(i18n("Click on a segment to split it in two"));
+ }
+}
+
+const QString SegmentSplitter::ToolName = "segmentsplitter";
+
+}
+#include "SegmentSplitter.moc"
diff --git a/src/gui/editors/segment/segmentcanvas/SegmentSplitter.h b/src/gui/editors/segment/segmentcanvas/SegmentSplitter.h
new file mode 100644
index 0000000..06201ec
--- /dev/null
+++ b/src/gui/editors/segment/segmentcanvas/SegmentSplitter.h
@@ -0,0 +1,83 @@
+
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
+
+/*
+ Rosegarden
+ A MIDI and audio sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_SEGMENTSPLITTER_H_
+#define _RG_SEGMENTSPLITTER_H_
+
+#include "SegmentTool.h"
+#include <qstring.h>
+#include "base/Event.h"
+
+
+class QMouseEvent;
+
+
+namespace Rosegarden
+{
+
+class Segment;
+class RosegardenGUIDoc;
+class CompositionView;
+
+
+class SegmentSplitter : public SegmentTool
+{
+ Q_OBJECT
+
+ friend class SegmentToolBox;
+
+public:
+
+ virtual ~SegmentSplitter();
+
+ virtual void ready();
+
+ virtual void handleMouseButtonPress(QMouseEvent*);
+ virtual void handleMouseButtonRelease(QMouseEvent*);
+ virtual int handleMouseMove(QMouseEvent*);
+
+ // don't do double clicks
+ virtual void contentsMouseDoubleClickEvent(QMouseEvent*);
+
+ static const QString ToolName;
+
+protected:
+ SegmentSplitter(CompositionView*, RosegardenGUIDoc*);
+
+ void setBasicContextHelp();
+
+ void drawSplitLine(QMouseEvent*);
+ void splitSegment(Segment *segment,
+ timeT &splitTime);
+
+ //--------------- Data members ---------------------------------
+ int m_prevX;
+ int m_prevY;
+};
+
+
+}
+
+#endif
diff --git a/src/gui/editors/segment/segmentcanvas/SegmentTool.cpp b/src/gui/editors/segment/segmentcanvas/SegmentTool.cpp
new file mode 100644
index 0000000..9bd7e69
--- /dev/null
+++ b/src/gui/editors/segment/segmentcanvas/SegmentTool.cpp
@@ -0,0 +1,115 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
+
+/*
+ Rosegarden
+ A MIDI and audio sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "SegmentTool.h"
+
+#include "misc/Debug.h"
+#include "CompositionView.h"
+#include "document/RosegardenGUIDoc.h"
+#include "document/MultiViewCommandHistory.h"
+#include "gui/application/RosegardenGUIApp.h"
+#include "gui/general/BaseTool.h"
+#include "SegmentToolBox.h"
+#include <kcommand.h>
+#include <kmainwindow.h>
+#include <qpoint.h>
+#include <qpopupmenu.h>
+
+
+namespace Rosegarden
+{
+
+SegmentTool::SegmentTool(CompositionView* canvas, RosegardenGUIDoc *doc)
+ : BaseTool("segment_tool_menu", dynamic_cast<KMainWindow*>(doc->parent())->factory(), canvas),
+ m_canvas(canvas),
+ m_doc(doc),
+ m_changeMade(false)
+{}
+
+SegmentTool::~SegmentTool()
+{}
+
+
+void SegmentTool::ready()
+{
+ m_canvas->viewport()->setCursor(Qt::arrowCursor);
+}
+
+void
+SegmentTool::handleRightButtonPress(QMouseEvent *e)
+{
+ if (m_currentItem) // mouse button is pressed for some tool
+ return ;
+
+ RG_DEBUG << "SegmentTool::handleRightButtonPress()\n";
+
+ setCurrentItem(m_canvas->getFirstItemAt(e->pos()));
+
+ if (m_currentItem) {
+ if (!m_canvas->getModel()->isSelected(m_currentItem)) {
+
+ m_canvas->getModel()->clearSelected();
+ m_canvas->getModel()->setSelected(m_currentItem);
+ m_canvas->getModel()->signalSelection();
+ }
+ }
+
+ showMenu();
+ setCurrentItem(CompositionItem());
+}
+
+void
+SegmentTool::createMenu()
+{
+ RG_DEBUG << "SegmentTool::createMenu()\n";
+
+ RosegardenGUIApp *app =
+ dynamic_cast<RosegardenGUIApp*>(m_doc->parent());
+
+ if (app) {
+ m_menu = static_cast<QPopupMenu*>
+ //(app->factory()->container("segment_tool_menu", app));
+ (m_parentFactory->container("segment_tool_menu", app));
+
+ if (!m_menu) {
+ RG_DEBUG << "SegmentTool::createMenu() failed\n";
+ }
+ } else {
+ RG_DEBUG << "SegmentTool::createMenu() failed: !app\n";
+ }
+}
+
+void
+SegmentTool::addCommandToHistory(KCommand *command)
+{
+ m_doc->getCommandHistory()->addCommand(command);
+}
+
+SegmentToolBox* SegmentTool::getToolBox()
+{
+ return m_canvas->getToolBox();
+}
+
+}
diff --git a/src/gui/editors/segment/segmentcanvas/SegmentTool.h b/src/gui/editors/segment/segmentcanvas/SegmentTool.h
new file mode 100644
index 0000000..90ed961
--- /dev/null
+++ b/src/gui/editors/segment/segmentcanvas/SegmentTool.h
@@ -0,0 +1,105 @@
+
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
+
+/*
+ Rosegarden
+ A MIDI and audio sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_SEGMENTTOOL_H_
+#define _RG_SEGMENTTOOL_H_
+
+#include "gui/general/BaseTool.h"
+#include "CompositionItem.h"
+#include <qpoint.h>
+#include <utility>
+#include <vector>
+
+
+class QMouseEvent;
+class KCommand;
+
+
+namespace Rosegarden
+{
+
+class SegmentToolBox;
+class RosegardenGUIDoc;
+class CompositionView;
+
+
+//////////////////////////////////////////////////////////////////////
+
+class SegmentToolBox;
+class SegmentSelector;
+
+// Allow the tools to share the Selector tool's selection
+// through these.
+//
+typedef std::pair<QPoint, CompositionItem> SegmentItemPair;
+typedef std::vector<SegmentItemPair> SegmentItemList;
+
+class SegmentTool : public BaseTool
+{
+public:
+ friend class SegmentToolBox;
+
+ virtual ~SegmentTool();
+
+ /**
+ * Is called by the parent View (EditView or SegmentCanvas) when
+ * the tool is set as current.
+ * Add any setup here
+ */
+ virtual void ready();
+
+ virtual void handleRightButtonPress(QMouseEvent*);
+ virtual void handleMouseButtonPress(QMouseEvent*) = 0;
+ virtual void handleMouseButtonRelease(QMouseEvent*) = 0;
+ virtual int handleMouseMove(QMouseEvent*) = 0;
+
+ void addCommandToHistory(KCommand *command);
+
+protected:
+ SegmentTool(CompositionView*, RosegardenGUIDoc *doc);
+
+ virtual void createMenu();
+ virtual bool hasMenu() { return true; }
+
+ void setCurrentItem(CompositionItem item) { if (item != m_currentItem) { delete m_currentItem; m_currentItem = item; } }
+
+ SegmentToolBox* getToolBox();
+
+ bool changeMade() { return m_changeMade; }
+ void setChangeMade(bool c) { m_changeMade = c; }
+
+ //--------------- Data members ---------------------------------
+
+ CompositionView* m_canvas;
+ CompositionItem m_currentItem;
+ RosegardenGUIDoc* m_doc;
+ QPoint m_origPos;
+ bool m_changeMade;
+};
+
+
+}
+
+#endif
diff --git a/src/gui/editors/segment/segmentcanvas/SegmentToolBox.cpp b/src/gui/editors/segment/segmentcanvas/SegmentToolBox.cpp
new file mode 100644
index 0000000..cb0dec9
--- /dev/null
+++ b/src/gui/editors/segment/segmentcanvas/SegmentToolBox.cpp
@@ -0,0 +1,102 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
+
+/*
+ Rosegarden
+ A MIDI and audio sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "SegmentToolBox.h"
+
+#include "CompositionView.h"
+#include "document/RosegardenGUIDoc.h"
+#include "gui/general/BaseToolBox.h"
+#include "SegmentTool.h"
+#include "SegmentSelector.h"
+#include "SegmentEraser.h"
+#include "SegmentJoiner.h"
+#include "SegmentMover.h"
+#include "SegmentPencil.h"
+#include "SegmentResizer.h"
+#include "SegmentSplitter.h"
+#include <qstring.h>
+#include <kmessagebox.h>
+
+namespace Rosegarden
+{
+
+SegmentToolBox::SegmentToolBox(CompositionView* parent, RosegardenGUIDoc* doc)
+ : BaseToolBox(parent),
+ m_canvas(parent),
+ m_doc(doc)
+{}
+
+SegmentTool* SegmentToolBox::createTool(const QString& toolName)
+{
+ SegmentTool* tool = 0;
+
+ QString toolNamelc = toolName.lower();
+
+ if (toolNamelc == SegmentPencil::ToolName)
+
+ tool = new SegmentPencil(m_canvas, m_doc);
+
+ else if (toolNamelc == SegmentEraser::ToolName)
+
+ tool = new SegmentEraser(m_canvas, m_doc);
+
+ else if (toolNamelc == SegmentMover::ToolName)
+
+ tool = new SegmentMover(m_canvas, m_doc);
+
+ else if (toolNamelc == SegmentResizer::ToolName)
+
+ tool = new SegmentResizer(m_canvas, m_doc);
+
+ else if (toolNamelc == SegmentSelector::ToolName)
+
+ tool = new SegmentSelector(m_canvas, m_doc);
+
+ else if (toolNamelc == SegmentSplitter::ToolName)
+
+ tool = new SegmentSplitter(m_canvas, m_doc);
+
+ else if (toolNamelc == SegmentJoiner::ToolName)
+
+ tool = new SegmentJoiner(m_canvas, m_doc);
+
+ else {
+ KMessageBox::error(0, QString("SegmentToolBox::createTool : unrecognised toolname %1 (%2)")
+ .arg(toolName).arg(toolNamelc));
+ return 0;
+ }
+
+ m_tools.insert(toolName, tool);
+
+ return tool;
+}
+
+SegmentTool* SegmentToolBox::getTool(const QString& toolName)
+{
+ return dynamic_cast<SegmentTool*>(BaseToolBox::getTool(toolName));
+}
+
+}
+#include "SegmentToolBox.moc"
diff --git a/src/gui/editors/segment/segmentcanvas/SegmentToolBox.h b/src/gui/editors/segment/segmentcanvas/SegmentToolBox.h
new file mode 100644
index 0000000..7a46fb6
--- /dev/null
+++ b/src/gui/editors/segment/segmentcanvas/SegmentToolBox.h
@@ -0,0 +1,63 @@
+
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
+
+/*
+ Rosegarden
+ A MIDI and audio sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#include "TempoListItem.h"
+
+namespace Rosegarden {
+
+int
+TempoListItem::compare(QListViewItem *i, int col, bool ascending) const
+{
+ TempoListItem *ti = dynamic_cast<TempoListItem *>(i);
+ if (!ti) return QListViewItem::compare(i, col, ascending);
+
+ if (col == 0) { // time
+ if (m_time == ti->m_time) {
+ return int(m_type) - int(ti->m_type);
+ } else {
+ return int(m_time - ti->m_time);
+ }
+ } else if (col == 1) { // type
+ if (m_type == ti->m_type) {
+ return int(m_time - ti->m_time);
+ } else {
+ return int(m_type) - int(ti->m_type);
+ }
+ } else {
+ return key(col, ascending).compare(i->key(col, ascending));
+ }
+}
+
+}
diff --git a/src/gui/editors/tempo/TempoListItem.h b/src/gui/editors/tempo/TempoListItem.h
new file mode 100644
index 0000000..143edde
--- /dev/null
+++ b/src/gui/editors/tempo/TempoListItem.h
@@ -0,0 +1,72 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
+
+/*
+ Rosegarden
+ A MIDI and audio sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_TEMPOLISTITEM_H_
+#define _RG_TEMPOLISTITEM_H_
+
+#include <klistview.h>
+
+#include "base/Event.h"
+
+namespace Rosegarden {
+
+class Composition;
+
+class TempoListItem : public KListViewItem
+{
+public:
+ enum Type { TimeSignature, Tempo };
+
+ TempoListItem(Composition *composition,
+ Type type,
+ timeT time,
+ int index,
+ KListView *parent,
+ QString label1,
+ QString label2,
+ QString label3,
+ QString label4 = QString::null) :
+ KListViewItem(parent, label1, label2, label3, label4),
+ m_composition(composition),
+ m_type(type),
+ m_time(time),
+ m_index(index) { }
+
+ Rosegarden::Composition *getComposition() { return m_composition; }
+ Type getType() const { return m_type; }
+ Rosegarden::timeT getTime() const { return m_time; }
+ int getIndex() const { return m_index; }
+
+ virtual int compare(QListViewItem *i, int col, bool ascending) const;
+
+protected:
+ Rosegarden::Composition *m_composition;
+ Type m_type;
+ Rosegarden::timeT m_time;
+ int m_index;
+};
+
+}
+
+#endif
diff --git a/src/gui/editors/tempo/TempoView.cpp b/src/gui/editors/tempo/TempoView.cpp
new file mode 100644
index 0000000..6ff6c1d
--- /dev/null
+++ b/src/gui/editors/tempo/TempoView.cpp
@@ -0,0 +1,839 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
+
+/*
+ Rosegarden
+ A MIDI and audio sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "TempoView.h"
+
+#include <klocale.h>
+#include <kstddirs.h>
+#include "misc/Debug.h"
+#include "base/Composition.h"
+#include "base/NotationTypes.h"
+#include "base/RealTime.h"
+#include "base/Segment.h"
+#include "commands/segment/AddTempoChangeCommand.h"
+#include "commands/segment/AddTimeSignatureAndNormalizeCommand.h"
+#include "commands/segment/AddTimeSignatureCommand.h"
+#include "commands/segment/RemoveTempoChangeCommand.h"
+#include "commands/segment/RemoveTimeSignatureCommand.h"
+#include "document/RosegardenGUIDoc.h"
+#include "document/ConfigGroups.h"
+#include "gui/dialogs/TempoDialog.h"
+#include "gui/dialogs/TimeSignatureDialog.h"
+#include "gui/general/EditViewBase.h"
+#include "gui/kdeext/KTmpStatusMsg.h"
+#include "TempoListItem.h"
+#include <kaction.h>
+#include <kglobal.h>
+#include <kconfig.h>
+#include <klistview.h>
+#include <kxmlguiclient.h>
+#include <qbuttongroup.h>
+#include <qcheckbox.h>
+#include <qdialog.h>
+#include <qiconset.h>
+#include <qlistview.h>
+#include <qpixmap.h>
+#include <qptrlist.h>
+#include <qsize.h>
+#include <qstring.h>
+#include <qlayout.h>
+#include <qcanvas.h>
+#include <kstatusbar.h>
+
+
+namespace Rosegarden
+{
+
+int
+TempoView::m_lastSetFilter = -1;
+
+
+TempoView::TempoView(RosegardenGUIDoc *doc, QWidget *parent, timeT openTime):
+ EditViewBase(doc, std::vector<Segment *>(), 2, parent, "tempoview"),
+ m_filter(Tempo | TimeSignature),
+ m_ignoreUpdates(true)
+{
+ if (m_lastSetFilter < 0)
+ m_lastSetFilter = m_filter;
+ else
+ m_filter = m_lastSetFilter;
+
+ initStatusBar();
+ setupActions();
+
+ // define some note filtering buttons in a group
+ //
+ m_filterGroup =
+ new QButtonGroup(1, Horizontal, i18n("Filter"), getCentralWidget());
+
+ m_tempoCheckBox = new QCheckBox(i18n("Tempo"), m_filterGroup);
+ m_timeSigCheckBox = new QCheckBox(i18n("Time Signature"), m_filterGroup);
+ m_grid->addWidget(m_filterGroup, 2, 0);
+
+ // Connect up
+ //
+ connect(m_filterGroup, SIGNAL(released(int)),
+ SLOT(slotModifyFilter(int)));
+
+ m_list = new KListView(getCentralWidget());
+ m_list->setItemsRenameable(true);
+
+ m_grid->addWidget(m_list, 2, 1);
+
+ updateViewCaption();
+
+ doc->getComposition().addObserver(this);
+
+ // Connect double clicker
+ //
+ connect(m_list, SIGNAL(doubleClicked(QListViewItem*)),
+ SLOT(slotPopupEditor(QListViewItem*)));
+
+ m_list->setAllColumnsShowFocus(true);
+ m_list->setSelectionMode(QListView::Extended);
+
+ m_list->addColumn(i18n("Time "));
+ m_list->addColumn(i18n("Type "));
+ m_list->addColumn(i18n("Value "));
+ m_list->addColumn(i18n("Properties "));
+
+ for (int col = 0; col < m_list->columns(); ++col)
+ m_list->setRenameable(col, true);
+
+ readOptions();
+ setButtonsToFilter();
+ applyLayout();
+
+ makeInitialSelection(openTime);
+
+ m_ignoreUpdates = false;
+ setOutOfCtor();
+}
+
+TempoView::~TempoView()
+{
+ if (!getDocument()->isBeingDestroyed() && !isCompositionDeleted()) {
+ getDocument()->getComposition().removeObserver(this);
+ }
+}
+
+void
+TempoView::closeEvent(QCloseEvent *e)
+{
+ emit closing();
+ EditViewBase::closeEvent(e);
+}
+
+void
+TempoView::tempoChanged(const Composition *comp)
+{
+ if (m_ignoreUpdates)
+ return ;
+ if (comp == &getDocument()->getComposition()) {
+ applyLayout();
+ }
+}
+
+void
+TempoView::timeSignatureChanged(const Composition *comp)
+{
+ if (m_ignoreUpdates)
+ return ;
+ if (comp == &getDocument()->getComposition()) {
+ applyLayout();
+ }
+}
+
+bool
+TempoView::applyLayout(int /*staffNo*/)
+{
+ // If no selection has already been set then we copy what's
+ // already set and try to replicate this after the rebuild
+ // of the view. This code borrowed from EventView.
+ //
+ if (m_listSelection.size() == 0) {
+ QPtrList<QListViewItem> selection = m_list->selectedItems();
+
+ if (selection.count()) {
+ QPtrListIterator<QListViewItem> it(selection);
+ QListViewItem *listItem;
+
+ while ((listItem = it.current()) != 0) {
+ m_listSelection.push_back(m_list->itemIndex(*it));
+ ++it;
+ }
+ }
+ }
+
+ // Ok, recreate list
+ //
+ m_list->clear();
+
+ Composition *comp = &getDocument()->getComposition();
+
+ m_config->setGroup(TempoViewConfigGroup);
+ int timeMode = m_config->readNumEntry("timemode", 0);
+
+ if (m_filter & TimeSignature) {
+ for (int i = 0; i < comp->getTimeSignatureCount(); ++i) {
+
+ std::pair<timeT, Rosegarden::TimeSignature> sig =
+ comp->getTimeSignatureChange(i);
+
+ QString properties;
+ if (sig.second.isHidden()) {
+ if (sig.second.isCommon())
+ properties = i18n("Common, hidden");
+ else
+ properties = i18n("Hidden");
+ } else {
+ if (sig.second.isCommon())
+ properties = i18n("Common");
+ }
+
+ QString timeString = makeTimeString(sig.first, timeMode);
+
+ new TempoListItem(comp, TempoListItem::TimeSignature,
+ sig.first, i, m_list, timeString,
+ i18n("Time Signature "),
+ QString("%1/%2 ").arg(sig.second.getNumerator()).
+ arg(sig.second.getDenominator()),
+ properties);
+ }
+ }
+
+ if (m_filter & Tempo) {
+ for (int i = 0; i < comp->getTempoChangeCount(); ++i) {
+
+ std::pair<timeT, tempoT> tempo =
+ comp->getTempoChange(i);
+
+ QString desc;
+
+ //!!! imprecise -- better to work from tempoT directly
+
+ float qpm = comp->getTempoQpm(tempo.second);
+ int qpmUnits = int(qpm + 0.001);
+ int qpmTenths = int((qpm - qpmUnits) * 10 + 0.001);
+ int qpmHundredths = int((qpm - qpmUnits - qpmTenths / 10.0) * 100 + 0.001);
+
+ Rosegarden::TimeSignature sig = comp->getTimeSignatureAt(tempo.first);
+ if (sig.getBeatDuration() ==
+ Note(Note::Crotchet).getDuration()) {
+ desc = i18n("%1.%2%3").
+ arg(qpmUnits).arg(qpmTenths).arg(qpmHundredths);
+ } else {
+ float bpm = (qpm *
+ Note(Note::Crotchet).getDuration()) /
+ sig.getBeatDuration();
+ int bpmUnits = int(bpm + 0.001);
+ int bpmTenths = int((bpm - bpmUnits) * 10 + 0.001);
+ int bpmHundredths = int((bpm - bpmUnits - bpmTenths / 10.0) * 100 + 0.001);
+
+ desc = i18n("%1.%2%3 qpm (%4.%5%6 bpm) ").
+ arg(qpmUnits).arg(qpmTenths).arg(qpmHundredths).
+ arg(bpmUnits).arg(bpmTenths).arg(bpmHundredths);
+ }
+
+ QString timeString = makeTimeString(tempo.first, timeMode);
+
+ new TempoListItem(comp, TempoListItem::Tempo,
+ tempo.first, i, m_list, timeString,
+ i18n("Tempo "),
+ desc);
+ }
+ }
+
+ if (m_list->childCount() == 0) {
+ new QListViewItem(m_list,
+ i18n("<nothing at this filter level>"));
+ m_list->setSelectionMode(QListView::NoSelection);
+ stateChanged("have_selection", KXMLGUIClient::StateReverse);
+ } else {
+ m_list->setSelectionMode(QListView::Extended);
+
+ // If no selection then select the first event
+ if (m_listSelection.size() == 0)
+ m_listSelection.push_back(0);
+ stateChanged("have_selection", KXMLGUIClient::StateNoReverse);
+ }
+
+ // Set a selection from a range of indexes
+ //
+ std::vector<int>::iterator sIt = m_listSelection.begin();
+ int index = 0;
+
+ for (; sIt != m_listSelection.end(); ++sIt) {
+ index = *sIt;
+
+ while (index > 0 && !m_list->itemAtIndex(index))
+ index--;
+
+ m_list->setSelected(m_list->itemAtIndex(index), true);
+ m_list->setCurrentItem(m_list->itemAtIndex(index));
+
+ // ensure visible
+ m_list->ensureItemVisible(m_list->itemAtIndex(index));
+ }
+
+ m_listSelection.clear();
+
+ return true;
+}
+
+void
+TempoView::makeInitialSelection(timeT time)
+{
+ m_listSelection.clear();
+
+ TempoListItem *goodItem = 0;
+ int goodItemNo = 0;
+
+ for (int i = 0; m_list->itemAtIndex(i); ++i) {
+
+ TempoListItem *item = dynamic_cast<TempoListItem *>
+ (m_list->itemAtIndex(i));
+
+ m_list->setSelected(item, false);
+
+ if (item) {
+ if (item->getTime() > time)
+ break;
+ goodItem = item;
+ goodItemNo = i;
+ }
+ }
+
+ if (goodItem) {
+ m_listSelection.push_back(goodItemNo);
+ m_list->setSelected(goodItem, true);
+ m_list->ensureItemVisible(goodItem);
+ }
+}
+
+Segment *
+TempoView::getCurrentSegment()
+{
+ if (m_segments.empty())
+ return 0;
+ else
+ return *m_segments.begin();
+}
+
+QString
+TempoView::makeTimeString(timeT time, int timeMode)
+{
+ switch (timeMode) {
+
+ case 0: // musical time
+ {
+ int bar, beat, fraction, remainder;
+ getDocument()->getComposition().getMusicalTimeForAbsoluteTime
+ (time, bar, beat, fraction, remainder);
+ ++bar;
+ return QString("%1%2%3-%4%5-%6%7-%8%9 ")
+ .arg(bar / 100)
+ .arg((bar % 100) / 10)
+ .arg(bar % 10)
+ .arg(beat / 10)
+ .arg(beat % 10)
+ .arg(fraction / 10)
+ .arg(fraction % 10)
+ .arg(remainder / 10)
+ .arg(remainder % 10);
+ }
+
+ case 1: // real time
+ {
+ RealTime rt =
+ getDocument()->getComposition().getElapsedRealTime(time);
+ // return QString("%1 ").arg(rt.toString().c_str());
+ return QString("%1 ").arg(rt.toText().c_str());
+ }
+
+ default:
+ return QString("%1 ").arg(time);
+ }
+}
+
+void
+TempoView::refreshSegment(Segment * /*segment*/,
+ timeT /*startTime*/,
+ timeT /*endTime*/)
+{
+ RG_DEBUG << "TempoView::refreshSegment" << endl;
+ applyLayout(0);
+}
+
+void
+TempoView::updateView()
+{
+ m_list->update();
+}
+
+void
+TempoView::slotEditCut()
+{
+ // not implemented yet -- can't use traditional clipboard (which
+ // only holds events from segments, or segments)
+}
+
+void
+TempoView::slotEditCopy()
+{
+ // likewise
+}
+
+void
+TempoView::slotEditPaste()
+{
+ // likewise
+}
+
+void
+TempoView::slotEditDelete()
+{
+ QPtrList<QListViewItem> selection = m_list->selectedItems();
+ if (selection.count() == 0)
+ return ;
+
+ RG_DEBUG << "TempoView::slotEditDelete - deleting "
+ << selection.count() << " items" << endl;
+
+ QPtrListIterator<QListViewItem> it(selection);
+ QListViewItem *listItem;
+ TempoListItem *item;
+ int itemIndex = -1;
+
+ m_ignoreUpdates = true;
+ bool haveSomething = false;
+
+ // We want the Remove commands to be in reverse order, because
+ // removing one item by index will affect the indices of
+ // subsequent items. So we'll stack them onto here and then pull
+ // them off again.
+ std::vector<KCommand *> commands;
+
+ while ((listItem = it.current()) != 0) {
+ item = dynamic_cast<TempoListItem*>((*it));
+
+ if (itemIndex == -1)
+ itemIndex = m_list->itemIndex(*it);
+
+ if (item) {
+ if (item->getType() == TempoListItem::TimeSignature) {
+ commands.push_back(new RemoveTimeSignatureCommand
+ (item->getComposition(),
+ item->getIndex()));
+ haveSomething = true;
+ } else {
+ commands.push_back(new RemoveTempoChangeCommand
+ (item->getComposition(),
+ item->getIndex()));
+ haveSomething = true;
+ }
+ }
+ ++it;
+ }
+
+ if (haveSomething) {
+ KMacroCommand *command = new KMacroCommand
+ (i18n("Delete Tempo or Time Signature"));
+ for (std::vector<KCommand *>::iterator i = commands.end();
+ i != commands.begin();) {
+ command->addCommand(*--i);
+ }
+ addCommandToHistory(command);
+ }
+
+ applyLayout();
+ m_ignoreUpdates = false;
+}
+
+void
+TempoView::slotEditInsertTempo()
+{
+ timeT insertTime = 0;
+ QPtrList<QListViewItem> selection = m_list->selectedItems();
+
+ if (selection.count() > 0) {
+ TempoListItem *item =
+ dynamic_cast<TempoListItem*>(selection.getFirst());
+ if (item)
+ insertTime = item->getTime();
+ }
+
+ TempoDialog dialog(this, getDocument(), true);
+ dialog.setTempoPosition(insertTime);
+
+ connect(&dialog,
+ SIGNAL(changeTempo(timeT,
+ tempoT,
+ tempoT,
+ TempoDialog::TempoDialogAction)),
+ this,
+ SIGNAL(changeTempo(timeT,
+ tempoT,
+ tempoT,
+ TempoDialog::TempoDialogAction)));
+
+ dialog.exec();
+}
+
+void
+TempoView::slotEditInsertTimeSignature()
+{
+ timeT insertTime = 0;
+ QPtrList<QListViewItem> selection = m_list->selectedItems();
+
+ if (selection.count() > 0) {
+ TempoListItem *item =
+ dynamic_cast<TempoListItem*>(selection.getFirst());
+ if (item)
+ insertTime = item->getTime();
+ }
+
+ Composition &composition(m_doc->getComposition());
+ Rosegarden::TimeSignature sig = composition.getTimeSignatureAt(insertTime);
+
+ TimeSignatureDialog dialog(this, &composition, insertTime, sig, true);
+
+ if (dialog.exec() == QDialog::Accepted) {
+
+ insertTime = dialog.getTime();
+
+ if (dialog.shouldNormalizeRests()) {
+ addCommandToHistory
+ (new AddTimeSignatureAndNormalizeCommand
+ (&composition, insertTime, dialog.getTimeSignature()));
+ } else {
+ addCommandToHistory
+ (new AddTimeSignatureCommand
+ (&composition, insertTime, dialog.getTimeSignature()));
+ }
+ }
+}
+
+void
+TempoView::slotEdit()
+{
+ RG_DEBUG << "TempoView::slotEdit" << endl;
+
+ QPtrList<QListViewItem> selection = m_list->selectedItems();
+
+ if (selection.count() > 0) {
+ TempoListItem *item =
+ dynamic_cast<TempoListItem*>(selection.getFirst());
+ if (item)
+ slotPopupEditor(item);
+ }
+}
+
+void
+TempoView::slotSelectAll()
+{
+ m_listSelection.clear();
+ for (int i = 0; m_list->itemAtIndex(i); ++i) {
+ m_listSelection.push_back(i);
+ m_list->setSelected(m_list->itemAtIndex(i), true);
+ }
+}
+
+void
+TempoView::slotClearSelection()
+{
+ m_listSelection.clear();
+ for (int i = 0; m_list->itemAtIndex(i); ++i) {
+ m_list->setSelected(m_list->itemAtIndex(i), false);
+ }
+}
+
+void
+TempoView::setupActions()
+{
+ EditViewBase::setupActions("tempoview.rc", false);
+
+ QString pixmapDir = KGlobal::dirs()->findResource("appdata", "pixmaps/");
+ QIconSet icon(QPixmap(pixmapDir + "/toolbar/event-insert-tempo.png"));
+
+ new KAction(AddTempoChangeCommand::getGlobalName(), icon, Key_I, this,
+ SLOT(slotEditInsertTempo()), actionCollection(),
+ "insert_tempo");
+
+ QCanvasPixmap pixmap(pixmapDir + "/toolbar/event-insert-timesig.png");
+ icon = QIconSet(pixmap);
+
+ new KAction(AddTimeSignatureCommand::getGlobalName(), icon, Key_G, this,
+ SLOT(slotEditInsertTimeSignature()), actionCollection(),
+ "insert_timesig");
+
+ pixmap.load(pixmapDir + "/toolbar/event-delete.png");
+ icon = QIconSet(pixmap);
+
+ new KAction(i18n("&Delete"), icon, Key_Delete, this,
+ SLOT(slotEditDelete()), actionCollection(),
+ "delete");
+
+ pixmap.load(pixmapDir + "/toolbar/event-edit.png");
+ icon = QIconSet(pixmap);
+
+ new KAction(i18n("&Edit Item"), icon, Key_E, this,
+ SLOT(slotEdit()), actionCollection(),
+ "edit");
+
+ new KAction(i18n("Select &All"), 0, this,
+ SLOT(slotSelectAll()), actionCollection(),
+ "select_all");
+
+ new KAction(i18n("Clear Selection"), Key_Escape, this,
+ SLOT(slotClearSelection()), actionCollection(),
+ "clear_selection");
+
+ m_config->setGroup(TempoViewConfigGroup);
+ int timeMode = m_config->readNumEntry("timemode", 0);
+
+ KRadioAction *action;
+
+ pixmap.load(pixmapDir + "/toolbar/time-musical.png");
+ icon = QIconSet(pixmap);
+
+ action = new KRadioAction(i18n("&Musical Times"), icon, 0, this,
+ SLOT(slotMusicalTime()),
+ actionCollection(), "time_musical");
+ action->setExclusiveGroup("timeMode");
+ if (timeMode == 0)
+ action->setChecked(true);
+
+ pixmap.load(pixmapDir + "/toolbar/time-real.png");
+ icon = QIconSet(pixmap);
+
+ action = new KRadioAction(i18n("&Real Times"), icon, 0, this,
+ SLOT(slotRealTime()),
+ actionCollection(), "time_real");
+ action->setExclusiveGroup("timeMode");
+ if (timeMode == 1)
+ action->setChecked(true);
+
+ pixmap.load(pixmapDir + "/toolbar/time-raw.png");
+ icon = QIconSet(pixmap);
+
+ action = new KRadioAction(i18n("Ra&w Times"), icon, 0, this,
+ SLOT(slotRawTime()),
+ actionCollection(), "time_raw");
+ action->setExclusiveGroup("timeMode");
+ if (timeMode == 2)
+ action->setChecked(true);
+
+ createGUI(getRCFileName());
+}
+
+void
+TempoView::initStatusBar()
+{
+ KStatusBar* sb = statusBar();
+
+ sb->insertItem(KTmpStatusMsg::getDefaultMsg(),
+ KTmpStatusMsg::getDefaultId(), 1);
+ sb->setItemAlignment(KTmpStatusMsg::getDefaultId(),
+ AlignLeft | AlignVCenter);
+}
+
+QSize
+TempoView::getViewSize()
+{
+ return m_list->size();
+}
+
+void
+TempoView::setViewSize(QSize s)
+{
+ m_list->setFixedSize(s);
+}
+
+void
+TempoView::readOptions()
+{
+ m_config->setGroup(TempoViewConfigGroup);
+ EditViewBase::readOptions();
+ m_filter = m_config->readNumEntry("filter", m_filter);
+ m_list->restoreLayout(m_config, TempoViewLayoutConfigGroupName);
+}
+
+void
+TempoView::slotSaveOptions()
+{
+ m_config->setGroup(TempoViewConfigGroup);
+ m_config->writeEntry("filter", m_filter);
+ m_list->saveLayout(m_config, TempoViewLayoutConfigGroupName);
+}
+
+void
+TempoView::slotModifyFilter(int button)
+{
+ QCheckBox *checkBox = dynamic_cast<QCheckBox*>(m_filterGroup->find(button));
+
+ if (checkBox == 0)
+ return ;
+
+ if (checkBox->isChecked()) {
+ switch (button) {
+ case 0:
+ m_filter |= Tempo;
+ break;
+
+ case 1:
+ m_filter |= TimeSignature;
+ break;
+
+ default:
+ break;
+ }
+
+ } else {
+ switch (button) {
+ case 0:
+ m_filter ^= Tempo;
+ break;
+
+ case 1:
+ m_filter ^= TimeSignature;
+ break;
+
+ default:
+ break;
+ }
+ }
+
+ m_lastSetFilter = m_filter;
+
+ applyLayout(0);
+}
+
+void
+TempoView::setButtonsToFilter()
+{
+ if (m_filter & Tempo)
+ m_tempoCheckBox->setChecked(true);
+ else
+ m_tempoCheckBox->setChecked(false);
+
+ if (m_filter & TimeSignature)
+ m_timeSigCheckBox->setChecked(true);
+ else
+ m_timeSigCheckBox->setChecked(false);
+}
+
+void
+TempoView::slotMusicalTime()
+{
+ m_config->setGroup(TempoViewConfigGroup);
+ m_config->writeEntry("timemode", 0);
+ applyLayout();
+}
+
+void
+TempoView::slotRealTime()
+{
+ m_config->setGroup(TempoViewConfigGroup);
+ m_config->writeEntry("timemode", 1);
+ applyLayout();
+}
+
+void
+TempoView::slotRawTime()
+{
+ m_config->setGroup(TempoViewConfigGroup);
+ m_config->writeEntry("timemode", 2);
+ applyLayout();
+}
+
+void
+TempoView::slotPopupEditor(QListViewItem *qitem)
+{
+ TempoListItem *item = dynamic_cast<TempoListItem *>(qitem);
+ if (!item)
+ return ;
+
+ timeT time = item->getTime();
+
+ switch (item->getType()) {
+
+ case TempoListItem::Tempo:
+ {
+ TempoDialog dialog(this, getDocument(), true);
+ dialog.setTempoPosition(time);
+
+ connect(&dialog,
+ SIGNAL(changeTempo(timeT,
+ tempoT,
+ tempoT,
+ TempoDialog::TempoDialogAction)),
+ this,
+ SIGNAL(changeTempo(timeT,
+ tempoT,
+ tempoT,
+ TempoDialog::TempoDialogAction)));
+
+ dialog.exec();
+ break;
+ }
+
+ case TempoListItem::TimeSignature:
+ {
+ Composition &composition(getDocument()->getComposition());
+ Rosegarden::TimeSignature sig = composition.getTimeSignatureAt(time);
+
+ TimeSignatureDialog dialog(this, &composition, time, sig, true);
+
+ if (dialog.exec() == QDialog::Accepted) {
+
+ time = dialog.getTime();
+
+ if (dialog.shouldNormalizeRests()) {
+ addCommandToHistory
+ (new AddTimeSignatureAndNormalizeCommand
+ (&composition, time, dialog.getTimeSignature()));
+ } else {
+ addCommandToHistory
+ (new AddTimeSignatureCommand
+ (&composition, time, dialog.getTimeSignature()));
+ }
+ }
+ }
+
+ default:
+ break;
+ }
+}
+
+void
+TempoView::updateViewCaption()
+{
+ setCaption(i18n("%1 - Tempo and Time Signature Editor")
+ .arg(getDocument()->getTitle()));
+}
+
+}
+#include "TempoView.moc"
diff --git a/src/gui/editors/tempo/TempoView.h b/src/gui/editors/tempo/TempoView.h
new file mode 100644
index 0000000..9a78e1b
--- /dev/null
+++ b/src/gui/editors/tempo/TempoView.h
@@ -0,0 +1,172 @@
+
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
+
+/*
+ Rosegarden
+ A MIDI and audio sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_TEMPOVIEW_H_
+#define _RG_TEMPOVIEW_H_
+
+#include "base/Composition.h"
+#include "base/NotationTypes.h"
+#include "gui/dialogs/TempoDialog.h"
+#include "gui/general/EditViewBase.h"
+#include <qsize.h>
+#include <qstring.h>
+#include <vector>
+#include "base/Event.h"
+
+
+class QWidget;
+class QListViewItem;
+class QCloseEvent;
+class QCheckBox;
+class QButtonGroup;
+class KListView;
+
+
+namespace Rosegarden
+{
+
+class Segment;
+class RosegardenGUIDoc;
+class Composition;
+
+
+/**
+ * Tempo and time signature list-style editor. This has some code
+ * in common with EventView, but not enough to make them any more
+ * shareable than simply through EditViewBase. Hopefully this one
+ * should prove considerably simpler, anyway.
+ */
+
+class TempoView : public EditViewBase, public CompositionObserver
+{
+ Q_OBJECT
+
+ enum Filter
+ {
+ None = 0x0000,
+ Tempo = 0x0001,
+ TimeSignature = 0x0002
+ };
+
+public:
+ TempoView(RosegardenGUIDoc *doc, QWidget *parent, timeT);
+ virtual ~TempoView();
+
+ virtual bool applyLayout(int staffNo = -1);
+
+ virtual void refreshSegment(Segment *segment,
+ timeT startTime = 0,
+ timeT endTime = 0);
+
+ virtual void updateView();
+
+ virtual void setupActions();
+ virtual void initStatusBar();
+ virtual QSize getViewSize();
+ virtual void setViewSize(QSize);
+
+ // Set the button states to the current filter positions
+ //
+ void setButtonsToFilter();
+
+ // Menu creation and show
+ //
+ void createMenu();
+
+ // Composition Observer callbacks
+ //
+ virtual void timeSignatureChanged(const Composition *);
+ virtual void tempoChanged(const Composition *);
+
+signals:
+ // forwarded from tempo dialog:
+ void changeTempo(timeT, // tempo change time
+ tempoT, // tempo value
+ tempoT, // tempo target
+ TempoDialog::TempoDialogAction); // tempo action
+
+ void closing();
+
+public slots:
+ // standard slots
+ virtual void slotEditCut();
+ virtual void slotEditCopy();
+ virtual void slotEditPaste();
+
+ // other edit slots
+ void slotEditDelete();
+ void slotEditInsertTempo();
+ void slotEditInsertTimeSignature();
+ void slotEdit();
+
+ void slotSelectAll();
+ void slotClearSelection();
+
+ void slotMusicalTime();
+ void slotRealTime();
+ void slotRawTime();
+
+ // on double click on the event list
+ //
+ void slotPopupEditor(QListViewItem*);
+
+ // Change filter parameters
+ //
+ void slotModifyFilter(int);
+
+protected slots:
+
+ virtual void slotSaveOptions();
+
+protected:
+
+ virtual void readOptions();
+ void makeInitialSelection(timeT);
+ QString makeTimeString(timeT time, int timeMode);
+ virtual Segment *getCurrentSegment();
+ virtual void updateViewCaption();
+
+ virtual void closeEvent(QCloseEvent *);
+
+ //--------------- Data members ---------------------------------
+ KListView *m_list;
+ int m_filter;
+
+ static int m_lastSetFilter;
+
+ QButtonGroup *m_filterGroup;
+ QCheckBox *m_tempoCheckBox;
+ QCheckBox *m_timeSigCheckBox;
+
+ std::vector<int> m_listSelection;
+
+ bool m_ignoreUpdates;
+};
+
+
+
+}
+
+#endif
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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#include "BarLine.h"
+
+#include <qpainter.h>
+
+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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_BARLINE_H_
+#define _RG_BARLINE_H_
+
+#include "LinedStaff.h"
+#include <qcanvas.h>
+
+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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "BaseTool.h"
+
+#include "misc/Debug.h"
+#include <kxmlguifactory.h>
+#include <qcursor.h>
+#include <qobject.h>
+#include <qpopupmenu.h>
+#include <qstring.h>
+
+
+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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_BASETOOL_H_
+#define _RG_BASETOOL_H_
+
+#include <qobject.h>
+#include <qstring.h>
+
+
+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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "BaseToolBox.h"
+
+#include "BaseTool.h"
+#include <qobject.h>
+#include <qstring.h>
+#include <qwidget.h>
+
+
+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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_BASETOOLBOX_H_
+#define _RG_BASETOOLBOX_H_
+
+#include <qdict.h>
+#include <qobject.h>
+
+
+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<BaseTool> 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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "CanvasCursor.h"
+
+#include "GUIPalette.h"
+#include <qcanvas.h>
+#include <qpen.h>
+
+
+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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_CANVASCURSOR_H_
+#define _RG_CANVASCURSOR_H_
+
+
+#include <qcanvas.h>
+
+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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "CanvasItemGC.h"
+
+#include "misc/Debug.h"
+
+#include <qcanvas.h>
+
+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<QCanvasItem*> 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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_CANVASITEMGC_H_
+#define _RG_CANVASITEMGC_H_
+
+#include <vector>
+
+
+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<QCanvasItem*> 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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "CategoryElement.h"
+
+#include "misc/Debug.h"
+#include "PresetElement.h"
+#include <qstring.h>
+
+
+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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_CATEGORYELEMENT_H_
+#define _RG_CATEGORYELEMENT_H_
+
+#include "PresetElement.h"
+#include <qstring.h>
+
+
+
+
+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<CategoryElement> 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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <bownie@bownie.com>
+
+ 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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <bownie@bownie.com>
+
+ 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 <qstring.h>
+
+// 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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "EditTool.h"
+
+#include "misc/Debug.h"
+#include "base/Event.h"
+#include "BaseTool.h"
+#include "base/ViewElement.h"
+#include "EditView.h"
+#include "RosegardenCanvasView.h"
+#include <kxmlguiclient.h>
+#include <qevent.h>
+#include <qpopupmenu.h>
+#include <qstring.h>
+
+
+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<QPopupMenu*>(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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_EDITTOOL_H_
+#define _RG_EDITTOOL_H_
+
+#include "BaseTool.h"
+#include <kxmlguiclient.h>
+#include <qstring.h>
+#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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "EditToolBox.h"
+
+#include "BaseToolBox.h"
+#include "EditTool.h"
+#include "EditView.h"
+#include <qobject.h>
+#include <qstring.h>
+#include <kmessagebox.h>
+
+namespace Rosegarden
+{
+
+EditToolBox::EditToolBox(EditView *parent)
+ : BaseToolBox(parent),
+ m_parentView(parent)
+{
+}
+
+EditTool* EditToolBox::getTool(const QString& toolName)
+{
+ return dynamic_cast<EditTool*>(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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "EditView.h"
+#include <qlayout.h>
+
+#include "base/BaseProperties.h"
+#include <klocale.h>
+#include <kconfig.h>
+#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 <kaction.h>
+#include <kcommand.h>
+#include <kdockwidget.h>
+#include <kglobal.h>
+#include <kiconloader.h>
+#include <kstddirs.h>
+#include <ktabwidget.h>
+#include <kxmlguiclient.h>
+#include <qaccel.h>
+#include <qbutton.h>
+#include <qdialog.h>
+#include <qframe.h>
+#include <qinputdialog.h>
+#include <qlabel.h>
+#include <qobjectlist.h>
+#include <qpopupmenu.h>
+#include <qsize.h>
+#include <qstring.h>
+#include <qtabwidget.h>
+#include <qvbox.h>
+#include <qwidget.h>
+#include <qwmatrix.h>
+
+
+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<Segment *> 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<QBoxLayout*>(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<ControlRuler*>(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<ControlRuler*>(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<PropertyControlRuler *>
+ (m_controlRulers->page(i));
+
+ if (pcr) pcr->setStaff(getCurrentStaff());
+ else {
+
+ ControllerEventsRuler *cer = dynamic_cast<ControllerEventsRuler *>
+ (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<QButton*>(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<QCanvasGroupableItem*>(item);
+ if (gitem)
+ item = gitem->group();
+
+ // Check if it's an active item
+ //
+ ActiveItem *activeItem = dynamic_cast<ActiveItem*>(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<Event *> 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<Event *> 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<QPopupMenu*>
+ (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<MidiDevice *>(getCurrentDevice());
+ if (!c) {
+ c = dynamic_cast<SoftSynthDevice *>(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<MidiDevice *>(getCurrentDevice());
+ if (!c) {
+ c = dynamic_cast<SoftSynthDevice *>(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<MidiDevice *>(getCurrentDevice());
+ if (!c) {
+ c = dynamic_cast<SoftSynthDevice *>(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<ControllerEventsRuler*>(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<PropertyControlRuler*>(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<QListBoxRGProperty*>(propList->selectedItem());
+ QListBoxRGProperty* item = dynamic_cast<QListBoxRGProperty*>
+ (propList->item(propList->currentItem()));
+
+ if (item) {
+ PropertyName property = item->getPropertyName();
+ showPropertyControlRuler(property);
+ }
+ }
+ */
+}
+
+void
+EditView::slotInsertControlRulerItem()
+{
+ ControllerEventsRuler* ruler = dynamic_cast<ControllerEventsRuler*>(getCurrentControlRuler());
+ if (ruler)
+ ruler->insertControllerEvent();
+}
+
+void
+EditView::slotEraseControlRulerItem()
+{
+ ControllerEventsRuler* ruler = dynamic_cast<ControllerEventsRuler*>(getCurrentControlRuler());
+ if (ruler)
+ ruler->eraseControllerEvent();
+}
+
+void
+EditView::slotStartControlLineItem()
+{
+ ControllerEventsRuler* ruler = dynamic_cast<ControllerEventsRuler*>(getCurrentControlRuler());
+ if (ruler)
+ ruler->startControlLine();
+}
+
+void
+EditView::slotDrawPropertyLine()
+{
+ int index = 0;
+ PropertyControlRuler* ruler = dynamic_cast<PropertyControlRuler*>
+ (findRuler(BaseProperties::VELOCITY, index));
+
+ if (ruler)
+ ruler->startPropertyLine();
+}
+
+void
+EditView::slotSelectAllProperties()
+{
+ int index = 0;
+ PropertyControlRuler* ruler = dynamic_cast<PropertyControlRuler*>
+ (findRuler(BaseProperties::VELOCITY, index));
+
+ if (ruler)
+ ruler->selectAllProperties();
+}
+
+void
+EditView::slotClearControlRulerItem()
+{
+ ControllerEventsRuler* ruler = dynamic_cast<ControllerEventsRuler*>(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<ControlRuler*>(m_controlRulers->currentPage());
+}
+
+ControlRuler* EditView::findRuler(PropertyName propertyName, int &index)
+{
+ for(index = 0; index < m_controlRulers->count(); ++index) {
+ PropertyControlRuler* ruler = dynamic_cast<PropertyControlRuler*>(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<ControllerEventsRuler*>(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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_EDITVIEW_H_
+#define _RG_EDITVIEW_H_
+
+#include "base/PropertyName.h"
+#include "EditViewBase.h"
+#include "gui/dialogs/TempoDialog.h"
+#include <qsize.h>
+#include <qstring.h>
+#include <qwmatrix.h>
+#include <vector>
+#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<Segment *> 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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "EditViewBase.h"
+#include <qlayout.h>
+#include <kapplication.h>
+
+#include <klocale.h>
+#include <kstddirs.h>
+#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 <kaction.h>
+#include <kcommand.h>
+#include <kconfig.h>
+#include <kdockwidget.h>
+#include <kedittoolbar.h>
+#include <kglobal.h>
+#include <kkeydialog.h>
+#include <kmainwindow.h>
+#include <kstatusbar.h>
+#include <kstdaccel.h>
+#include <kstdaction.h>
+#include <kxmlguiclient.h>
+#include <qaccel.h>
+#include <qcanvas.h>
+#include <qdialog.h>
+#include <qframe.h>
+#include <qiconset.h>
+#include <qobject.h>
+#include <qpixmap.h>
+#include <qstring.h>
+#include <qwidget.h>
+
+
+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<Segment *> 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<int> 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<KToggleAction*>(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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_EDITVIEWBASE_H_
+#define _RG_EDITVIEWBASE_H_
+
+#include <set>
+#include <string>
+#include <kdockwidget.h>
+#include <qstring.h>
+#include <vector>
+#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<Segment *> 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<Segment *>);
+ void openInMatrix(std::vector<Segment *>);
+ void openInPercussionMatrix(std::vector<Segment *>);
+ void openInEventList(std::vector<Segment *>);
+
+ /**
+ * 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<int> m_viewNumberPool;
+ std::string makeViewLocalPropertyPrefix();
+ int m_viewNumber;
+ std::string m_viewLocalPropertyPrefix;
+
+ KConfig* m_config;
+
+ RosegardenGUIDoc* m_doc;
+ std::vector<Segment *> m_segments;
+ std::vector<unsigned int> 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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "GUIPalette.h"
+#include <kapplication.h>
+
+#include "base/Colour.h"
+#include "document/ConfigGroups.h"
+#include <kconfig.h>
+#include <qcolor.h>
+
+
+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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_GUIPALETTE_H_
+#define _RG_GUIPALETTE_H_
+
+#include "base/Colour.h"
+#include <map>
+#include <qcolor.h>
+
+
+
+
+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<const char* const, QColor> 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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "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 <qcanvas.h>
+#include <qcolor.h>
+#include <qfont.h>
+#include <qfontmetrics.h>
+#include <qpen.h>
+#include <qrect.h>
+#include <qstring.h>
+#include <algorithm>
+
+
+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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#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 <qrect.h>
+#include <utility>
+#include <vector>
+
+
+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<double, int> 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<QCanvasItem *> ItemList;
+ typedef std::vector<ItemList> ItemMatrix;
+ ItemMatrix m_staffLines;
+ ItemList m_staffConnectingLines;
+
+ typedef std::pair<double, QCanvasItem *> LineRec; // layout-x, line
+ typedef FastVector<LineRec> LineRecList;
+ typedef FastVector<BarLine *> 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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "MidiPitchLabel.h"
+#include <kapplication.h>
+
+#include "document/ConfigGroups.h"
+#include <kconfig.h>
+#include <klocale.h>
+#include <qstring.h>
+
+
+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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_MIDIPITCHLABEL_H_
+#define _RG_MIDIPITCHLABEL_H_
+
+#include <string>
+#include <qstring.h>
+
+
+
+
+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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "PixmapFunctions.h"
+
+#include <qbitmap.h>
+#include <qcolor.h>
+#include <qimage.h>
+#include <qpainter.h>
+#include <qpixmap.h>
+
+#include <iostream>
+
+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<QPixmap, QPixmap>
+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<QPixmap, QPixmap>(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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_PIXMAPFUNCTIONS_H_
+#define _RG_PIXMAPFUNCTIONS_H_
+
+#include <qbitmap.h>
+#include <qpixmap.h>
+#include <utility>
+
+
+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<QPixmap, QPixmap> 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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ This file is Copyright 2006
+ D. Michael McIntyre <dmmcintyr@users.sourceforge.net>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either 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 <qstring.h>
+
+
+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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ This file is Copyright 2006
+ D. Michael McIntyre <dmmcintyr@users.sourceforge.net>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 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 <qstring.h>
+
+#include <vector>
+
+
+
+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<PresetElement> 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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ This file is Copyright 2006
+ D. Michael McIntyre <dmmcintyr@users.sourceforge.net>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either 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 <klocale.h>
+#include <kstddirs.h>
+#include <kglobal.h>
+#include <qfile.h>
+#include <qfileinfo.h>
+#include <qregexp.h>
+#include <qstring.h>
+
+
+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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ This file is Copyright 2006
+ D. Michael McIntyre <dmmcintyr@users.sourceforge.net>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 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 <qstring.h>
+#include <qxml.h>
+
+
+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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ This file is Copyright 2006
+ D. Michael McIntyre <dmmcintyr@users.sourceforge.net>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either 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 <qlayout.h>
+#include <kapplication.h>
+
+#include <klocale.h>
+#include "misc/Debug.h"
+#include "document/ConfigGroups.h"
+#include "CategoryElement.h"
+#include "PresetElement.h"
+#include "PresetGroup.h"
+#include <kcombobox.h>
+#include <kconfig.h>
+#include <kdialogbase.h>
+#include <qbuttongroup.h>
+#include <qdialog.h>
+#include <qframe.h>
+#include <qgroupbox.h>
+#include <qlabel.h>
+#include <qstring.h>
+#include <qvbox.h>
+#include <qwidget.h>
+
+
+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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ This file is Copyright 2006
+ D. Michael McIntyre <dmmcintyr@users.sourceforge.net>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 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 <kdialogbase.h>
+#include <qradiobutton.h>
+#include <qstring.h>
+#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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "ProgressReporter.h"
+
+#include <qobject.h>
+
+
+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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_PROGRESSREPORTER_H_
+#define _RG_PROGRESSREPORTER_H_
+
+#include <qobject.h>
+
+
+
+
+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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "RosegardenCanvasView.h"
+
+#include "misc/Debug.h"
+#include "gui/general/CanvasItemGC.h"
+#include <qcanvas.h>
+#include <qcursor.h>
+#include <qpoint.h>
+#include <qrect.h>
+#include <qscrollbar.h>
+#include <qsize.h>
+#include <qsizepolicy.h>
+#include <qtimer.h>
+#include <qwidget.h>
+
+
+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<QScrollView *>(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<QScrollView *>(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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_ROSEGARDENCANVASVIEW_H_
+#define _RG_ROSEGARDENCANVASVIEW_H_
+
+#include <qpoint.h>
+#include <qtimer.h>
+#include <qcanvas.h>
+#include <qdatetime.h>
+#include <qwmatrix.h>
+
+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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "RosegardenScrollView.h"
+
+#include "misc/Debug.h"
+#include <qapplication.h>
+#include <qcursor.h>
+#include <qpoint.h>
+#include <qrect.h>
+#include <qscrollbar.h>
+#include <qscrollview.h>
+#include <qsizepolicy.h>
+#include <qtimer.h>
+#include <qwidget.h>
+
+
+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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_ROSEGARDENSCROLLVIEW_H_
+#define _RG_ROSEGARDENSCROLLVIEW_H_
+
+#include <qpoint.h>
+#include <qscrollview.h>
+#include <qdatetime.h>
+#include <qtimer.h>
+
+
+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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "Spline.h"
+
+#include <qpoint.h>
+
+
+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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_SPLINE_H_
+#define _RG_SPLINE_H_
+
+#include "base/FastVector.h"
+
+
+class QPoint;
+class PointList;
+
+
+namespace Rosegarden
+{
+
+
+
+class Spline
+{
+public:
+ typedef FastVector<QPoint> 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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "StaffLine.h"
+
+#include "misc/Debug.h"
+#include <qcanvas.h>
+#include <qpen.h>
+
+
+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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_STAFFLINE_H_
+#define _RG_STAFFLINE_H_
+
+#include "gui/kdeext/QCanvasGroupableItem.h"
+#include <qpen.h>
+
+
+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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ 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 <kled.h>
+#include <qcolor.h>
+#include <qwidget.h>
+
+
+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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ 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 <kled.h>
+
+
+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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <bownie@bownie.com>
+
+ 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 <unistd.h>
+#include <kapplication.h>
+
+#include <qpainter.h>
+#include <qfontmetrics.h>
+
+#include <kapp.h>
+#include <kstddirs.h>
+#include <kconfig.h>
+#include <ktip.h>
+
+#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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <bownie@bownie.com>
+
+ 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 <qwidget.h>
+#include <qpixmap.h>
+
+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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <bownie@bownie.com>
+
+ 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 <qapplication.h>
+#include <qeventloop.h>
+
+#include <kmainwindow.h>
+#include <kstatusbar.h>
+#include <klocale.h>
+
+#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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <bownie@bownie.com>
+
+ 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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <bownie@bownie.com>
+
+ 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 <qcanvas.h>
+#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<QCanvasGroupableItem*>(*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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <bownie@bownie.com>
+
+ 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 <qcanvas.h>
+
+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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <bownie@bownie.com>
+
+ 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 <vector>
+#include "misc/Debug.h"
+
+#include <qpainter.h>
+
+#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<QPixmap> 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<QPoint> spotlist;
+ spotlist.setAutoDelete(true);
+ spotlist.append(new QPoint(0, 0));
+
+ return new QCanvasPixmapArray(pixlist, spotlist);
+}
+
+QCanvasPixmapArray*
+QCanvasSimpleSprite::makePixmapArray(QCanvasPixmap *pixmap)
+{
+ QList<QPixmap> 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<QPoint> 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<QCanvasPixmapArray*> 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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <bownie@bownie.com>
+
+ 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 <qwmatrix.h>
+#include <qcanvas.h>
+
+#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<QCanvasPixmapArray*> 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 <kunert@physik.tu-dresden.de>
+ *
+ * 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 <qdatetime.h>
+#include <stdio.h>
+#endif
+
+
+#include <qpainter.h>
+#include <qimage.h>
+#include <qcolor.h>
+#include <kapplication.h>
+#include <kpixmapeffect.h>
+#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 <bero@r?dh?t.com>
+ Preston Brown <pbrown@r?dh?t.com>
+ Than Ngo <than@r?dh?t.com>
+
+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 <kdeversion.h>
+#include <qmenubar.h>
+#include <qapplication.h>
+#include <qpainter.h>
+#include <qpalette.h>
+#include <qframe.h>
+#include <qpushbutton.h>
+#include <qdrawutil.h>
+#include <qscrollbar.h>
+#include <qtabbar.h>
+#include <qtabwidget.h>
+#include <qguardedptr.h>
+#include <qlayout.h>
+#include <qlineedit.h>
+#include <qlistview.h>
+#include <qbitmap.h>
+#include <qcleanuphandler.h>
+#include <qimage.h>
+#include <qcombobox.h>
+#include <qspinbox.h>
+#include <qslider.h>
+#include <qstylefactory.h>
+#include <qcleanuphandler.h>
+#include <qcheckbox.h>
+#include <qradiobutton.h>
+#include <qtoolbutton.h>
+#include <qtoolbar.h>
+#include <qprogressbar.h>
+#include <qcursor.h>
+#include <qheader.h>
+#include <qwidgetstack.h>
+#include <qsplitter.h>
+#include <math.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include "klearlook.h"
+#include <qsettings.h>
+
+#if KDE_VERSION >= 0x30200
+#include <qfile.h>
+#include <qdir.h>
+#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<QRadioButton *>( widget )
+ || ::qt_cast<QCheckBox *>( widget )
+ || ::qt_cast<QSpinWidget *>( widget )
+ || widget->inherits( "QSplitterHandle" ) ) {
+#if QT_VERSION >= 0x030200
+ widget->setMouseTracking( true );
+#endif
+
+ widget->installEventFilter( this );
+ } else if ( ::qt_cast<QButton *>( widget ) || ::qt_cast<QComboBox *>( widget ) ||
+ widget->inherits( "QToolBarExtensionWidget" ) ) {
+ widget->setBackgroundMode( QWidget::PaletteBackground );
+ widget->installEventFilter( this );
+
+ } else if ( ::qt_cast<QMenuBar *>( widget )
+ || ::qt_cast<QToolBar *>( widget )
+ || ::qt_cast<QPopupMenu *>( widget ) )
+ widget->setBackgroundMode( QWidget::PaletteBackground );
+
+ else if ( widget->inherits( "KToolBarSeparator" ) ) {
+ widget->setBackgroundMode( QWidget::NoBackground );
+ widget->installEventFilter( this );
+
+ } else if ( ::qt_cast<QScrollBar *>( widget ) ) {
+ widget->setMouseTracking( true );
+ widget->installEventFilter( this );
+ widget->setBackgroundMode( QWidget::NoBackground );
+
+ } else if ( ::qt_cast<QSlider *>( widget ) || ::qt_cast<QHeader *>( 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<QRadioButton *>( widget ) ||
+ ::qt_cast<QCheckBox *>( widget ) ||
+ ::qt_cast<QSpinWidget *>( widget ) ||
+ widget->inherits( "QSplitterHandle" ) ) {
+#if QT_VERSION >= 0x030200
+ widget->setMouseTracking( false );
+#endif
+
+ widget->removeEventFilter( this );
+ } else if ( ::qt_cast<QButton *>( widget ) || ::qt_cast<QComboBox *>( widget ) ||
+ widget->inherits( "QToolBarExtensionWidget" ) ) {
+ widget->setBackgroundMode( QWidget::PaletteButton );
+ widget->removeEventFilter( this );
+
+ } else if ( ::qt_cast<QMenuBar *>( widget ) ||
+ ::qt_cast<QToolBar *>( widget ) ||
+ ::qt_cast<QPopupMenu *>( widget ) )
+ widget->setBackgroundMode( QWidget::PaletteBackground );
+
+ else if ( widget->inherits( "KToolBarSeparator" ) ) {
+ widget->setBackgroundMode( PaletteBackground );
+ widget->removeEventFilter( this );
+
+ } else if ( ::qt_cast<QScrollBar *>( widget ) ) {
+ widget->setMouseTracking( false );
+ widget->removeEventFilter( this );
+ widget->setBackgroundMode( QWidget::PaletteButton );
+
+ } else if ( ::qt_cast<QSlider *>( widget ) ||
+ ::qt_cast<QHeader *>( 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<QWidget*>( object ),
+ *parent = static_cast<QWidget*>( 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<QWidget*>( 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<QToolBar*>( 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<QFrame*>( 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<QHeader *>( 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<QBitmap> 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<const QTabWidget *>( 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<const QPushButton *>( 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<const QPushButton *>( 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<QToolBar *>( widget->parentWidget() ),
+ onExtender = !onToolbar &&
+ widget->parentWidget() &&
+ widget->parentWidget() ->inherits( "QToolBarExtensionWidget" ) &&
+ ::qt_cast<QToolBar *>( 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<const QToolBar *>( 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<const QTabBar *>( 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<const QTabBar *>( widget ) ||
+ ::qt_cast<const QWidgetStack *>( widget ) ||
+ ::qt_cast<const QPopupMenu *>( 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<const QPopupMenu *>( 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<const QPushButton*>( 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<QToolBar*>( 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<QRadioButton *>( 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<QCheckBox *>( 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<QScrollBar *>( 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<QToolButton *>( hoverWidget );
+
+ if ( tb ) {
+ hover = APP_KICKER == themedApp ? HOVER_KICKER : HOVER_NONE;
+ return HOVER_KICKER == hover;
+ } else {
+#endif
+ QHeader *hd = dynamic_cast<QHeader *>( 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 <bero@r?dh?t.com>
+ Preston Brown <pbrown@r?dh?t.com>
+ Than Ngo <than@r?dh?t.com>
+
+ 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 <kdeversion.h>
+#include <kstyle.h>
+#include <qcolor.h>
+#include <qpoint.h>
+
+#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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "ChordNameRuler.h"
+#include "misc/Debug.h"
+
+#include <klocale.h>
+#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 <qfont.h>
+#include <qfontmetrics.h>
+#include <qobject.h>
+#include <qpainter.h>
+#include <qrect.h>
+#include <qsize.h>
+#include <qtooltip.h>
+#include <qwidget.h>
+
+
+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<Segment *> &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<Segment *>::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<SegmentRefreshMap::iterator> 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<SegmentRefreshMap::iterator>::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
+ <String>(Text::TextPropertyName));
+
+ if ((*i)->get
+ <String>(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
+ <Int>(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
+ <Int>(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
+ <String>(Text::TextPropertyName));
+ std::string type((*i)->get
+ <String>(Text::TextTypePropertyName));
+
+ if (!(*i)->has(TEXT_FORMAL_X))
+ continue;
+
+ long formalX = (*i)->get
+ <Int>(TEXT_FORMAL_X);
+ long actualX = (*i)->get
+ <Int>(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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_CHORDNAMERULER_H_
+#define _RG_CHORDNAMERULER_H_
+
+#include "base/PropertyName.h"
+#include <map>
+#include <qfont.h>
+#include <qfontmetrics.h>
+#include <qsize.h>
+#include <qwidget.h>
+#include <vector>
+#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<Segment *> &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<Segment *, int> 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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#include "ControlChangeCommand.h"
+#include "ControlItem.h"
+#include "misc/Debug.h"
+#include <klocale.h>
+
+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<ControlItem*>(*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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_CONTROLCHANGECOMMAND_H_
+#define _RG_CONTROLCHANGECOMMAND_H_
+
+#include "document/BasicCommand.h"
+#include <qcanvas.h>
+
+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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#include "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<int, QCanvasItem*> 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<ItemPair> 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<QCanvasRectangle*>(*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<ItemPair>::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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#include <qcanvas.h>
+
+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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "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 <kmainwindow.h>
+#include <qcanvas.h>
+#include <qcolor.h>
+#include <qcursor.h>
+#include <qpoint.h>
+#include <qpopupmenu.h>
+#include <qscrollbar.h>
+#include <qscrollview.h>
+#include <qstring.h>
+#include <qwidget.h>
+#include <algorithm>
+
+
+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(<time area>)
+
+ canvas()->update();
+ repaint();
+}
+
+void ControlRuler::slotUpdateElementsHPos()
+{
+ computeStaffOffset();
+
+ QCanvasItemList list = canvas()->allItems();
+
+ QCanvasItemList::Iterator it = list.begin();
+ for (; it != list.end(); ++it) {
+ ControlItem* item = dynamic_cast<ControlItem*>(*it);
+ if (!item)
+ continue;
+ layoutItem(item);
+ }
+
+ canvas()->update();
+}
+
+void ControlRuler::layoutItem(ControlItem* item)
+{
+ timeT itemTime = item->getElementAdapter()->getTime();
+
+ double x = m_rulerScale->getXForTime(itemTime);
+
+ item->setX(x + m_staffOffset);
+ int itemElementDuration = item->getElementAdapter()->getDuration();
+
+ int width = int(m_rulerScale->getXForTime(itemTime + itemElementDuration) - x);
+
+ item->setWidth(width);
+
+ // RG_DEBUG << "ControlRuler::layoutItem ControlItem x = " << x << " - width = " << width << endl;
+}
+
+void ControlRuler::setControlTool(ControlTool* tool)
+{
+ if (m_tool)
+ delete m_tool;
+ m_tool = tool;
+}
+
+void
+ControlRuler::segmentDeleted(const Segment *)
+{
+ m_segment = 0;
+}
+
+void ControlRuler::contentsMousePressEvent(QMouseEvent* e)
+{
+ if (e->button() != Qt::LeftButton) {
+ m_numberFloat->hide();
+ m_selecting = false;
+ return ;
+ }
+
+ RG_DEBUG << "ControlRuler::contentsMousePressEvent()\n";
+
+ QPoint p = inverseMapPoint(e->pos());
+
+ QCanvasItemList l = canvas()->collisions(p);
+
+ if (l.count() == 0) { // de-select current item
+ clearSelectedItems();
+ m_selecting = true;
+ m_selector->handleMouseButtonPress(e);
+ RG_DEBUG << "ControlRuler::contentsMousePressEvent : entering selection mode\n";
+ return ;
+ }
+
+ // clear selection unless control was pressed, in which case
+ // add the event to the current selection
+ if (!(e->state() && QMouseEvent::ControlButton)) {
+ clearSelectedItems();
+ }
+
+ ControlItem *topItem = 0;
+ for (QCanvasItemList::Iterator it = l.begin(); it != l.end(); ++it) {
+
+ if (ControlItem *item = dynamic_cast<ControlItem*>(*it)) {
+
+ if (topItem == 0)
+ topItem = item;
+
+ if (item->isSelected()) { // if the item which was clicked
+ // on is part of a selection,
+ // propagate mousepress on all
+ // selected items
+
+ item->handleMouseButtonPress(e);
+
+ for (QCanvasItemList::Iterator it = m_selectedItems.begin();
+ it != m_selectedItems.end(); ++it) {
+ if (ControlItem *selectedItem =
+ dynamic_cast<ControlItem*>(*it)) {
+ selectedItem->handleMouseButtonPress(e);
+ }
+ }
+
+
+ } else { // select it
+
+ if (!(e->state() && QMouseEvent::ControlButton)) {
+ if (item->z() > topItem->z())
+ topItem = item;
+
+ } else {
+ m_selectedItems << item;
+ item->setSelected(true);
+ item->handleMouseButtonPress(e);
+ ElementAdapter* adapter = item->getElementAdapter();
+ m_eventSelection->addEvent(adapter->getEvent());
+ }
+ }
+ }
+ }
+
+ if (topItem && !m_selectedItems.contains(topItem)) { // select the top item
+ m_selectedItems << topItem;
+ topItem->setSelected(true);
+ topItem->handleMouseButtonPress(e);
+ ElementAdapter* adapter = topItem->getElementAdapter();
+ m_eventSelection->addEvent(adapter->getEvent());
+ }
+
+ m_itemMoved = false;
+ m_lastEventPos = p;
+}
+
+void ControlRuler::contentsMouseReleaseEvent(QMouseEvent* e)
+{
+ if (e->button() != Qt::LeftButton) {
+ m_numberFloat->hide();
+ m_selecting = false;
+ return ;
+ }
+
+ if (m_selecting) {
+ updateSelection();
+ m_selector->handleMouseButtonRelease(e);
+ RG_DEBUG << "ControlRuler::contentsMouseReleaseEvent : leaving selection mode\n";
+ m_selecting = false;
+ return ;
+ }
+
+ for (QCanvasItemList::Iterator it = m_selectedItems.begin(); it != m_selectedItems.end(); ++it) {
+ if (ControlItem *item = dynamic_cast<ControlItem*>(*it)) {
+
+ ElementAdapter * adapter = item->getElementAdapter();
+ m_eventSelection->addEvent(adapter->getEvent());
+ item->handleMouseButtonRelease(e);
+ }
+ }
+
+ emit stateChange("have_controller_item_selected", true);
+
+ if (m_itemMoved) {
+
+ m_lastEventPos = inverseMapPoint(e->pos());
+
+ // Add command to history
+ ControlChangeCommand* command = new ControlChangeCommand(m_selectedItems,
+ *m_segment,
+ m_eventSelection->getStartTime(),
+ m_eventSelection->getEndTime());
+
+ RG_DEBUG << "ControlRuler::contentsMouseReleaseEvent : adding command\n";
+ m_parentEditView->addCommandToHistory(command);
+
+ m_itemMoved = false;
+ }
+
+ m_numberFloat->hide();
+}
+
+void ControlRuler::contentsMouseMoveEvent(QMouseEvent* e)
+{
+ QPoint p = inverseMapPoint(e->pos());
+
+ int deltaX = p.x() - m_lastEventPos.x(),
+ deltaY = p.y() - m_lastEventPos.y();
+ m_lastEventPos = p;
+
+ if (m_selecting) {
+ updateSelection();
+ m_selector->handleMouseMove(e, deltaX, deltaY);
+ slotScrollHorizSmallSteps(p.x());
+ return ;
+ }
+
+ m_itemMoved = true;
+
+ // Borrowed from Rotary - compute total position within window
+ //
+ QPoint totalPos = mapTo(topLevelWidget(), QPoint(0, 0));
+
+ int scrollX = dynamic_cast<EditView*>(m_parentEditView)->getRawCanvasView()->
+ horizontalScrollBar()->value();
+
+ /*
+ RG_DEBUG << "ControlRuler::contentsMouseMoveEvent - total pos = " << totalPos.x()
+ << ",e pos = " << e->pos().x()
+ << ", scroll bar = " << scrollX
+ << endl;
+ */
+
+ // Allow for scrollbar
+ //
+ m_numberFloat->move(totalPos.x() + e->pos().x() - scrollX + 20,
+ totalPos.y() + e->pos().y() - 10);
+
+ int value = 0;
+
+ for (QCanvasItemList::Iterator it = m_selectedItems.begin(); it != m_selectedItems.end(); ++it) {
+ if (ControlItem *item = dynamic_cast<ControlItem*>(*it)) {
+ item->handleMouseMove(e, deltaX, deltaY);
+ // ElementAdapter* adapter = item->getElementAdapter();
+
+ // set value to highest in selection
+ if (item->getValue() >= value) {
+ value = item->getValue();
+ m_numberFloat->setText(QString("%1").arg(value));
+ }
+ }
+ }
+ canvas()->update();
+
+ m_numberFloat->show();
+
+}
+
+void
+ControlRuler::contentsWheelEvent(QWheelEvent *e)
+{
+ // not sure what to do yet
+ QCanvasView::contentsWheelEvent(e);
+}
+
+void ControlRuler::updateSelection()
+{
+ clearSelectedItems();
+
+ bool haveSelectedItems = false;
+
+ QCanvasItemList l = getSelectionRectangle()->collisions(true);
+
+ for (QCanvasItemList::Iterator it = l.begin(); it != l.end(); ++it) {
+
+ if (ControlItem *item = dynamic_cast<ControlItem*>(*it)) {
+ item->setSelected(true);
+ m_selectedItems << item;
+ haveSelectedItems = true;
+
+ ElementAdapter* adapter = item->getElementAdapter();
+ m_eventSelection->addEvent(adapter->getEvent());
+ }
+ }
+
+ emit stateChange("have_controller_item_selected", haveSelectedItems);
+}
+
+void ControlRuler::contentsContextMenuEvent(QContextMenuEvent* e)
+{
+ if (!m_menu && !m_menuName.isEmpty())
+ createMenu();
+
+ if (m_menu) {
+ RG_DEBUG << "ControlRuler::showMenu() - show menu with" << m_menu->count() << " items\n";
+ m_lastEventPos = inverseMapPoint(e->pos());
+ m_menu->exec(QCursor::pos());
+ } else
+ RG_DEBUG << "ControlRuler::showMenu() : no menu to show\n";
+
+}
+
+void ControlRuler::createMenu()
+{
+ RG_DEBUG << "ControlRuler::createMenu()\n";
+
+ KMainWindow* parentMainWindow = dynamic_cast<KMainWindow*>(topLevelWidget());
+
+ if (parentMainWindow && parentMainWindow->factory()) {
+ m_menu = static_cast<QPopupMenu*>(parentMainWindow->factory()->container(m_menuName, parentMainWindow));
+
+ if (!m_menu) {
+ RG_DEBUG << "ControlRuler::createMenu() failed\n";
+ }
+ } else {
+ RG_DEBUG << "ControlRuler::createMenu() failed: no parent factory\n";
+ }
+}
+
+void
+ControlRuler::clearSelectedItems()
+{
+ for (QCanvasItemList::Iterator it = m_selectedItems.begin(); it != m_selectedItems.end(); ++it) {
+ (*it)->setSelected(false);
+ }
+ m_selectedItems.clear();
+
+ delete m_eventSelection;
+ m_eventSelection = new EventSelection(*m_segment);
+}
+
+void ControlRuler::clear()
+{
+ QCanvasItemList allItems = canvas()->allItems();
+
+ for (QCanvasItemList::Iterator it = allItems.begin(); it != allItems.end(); ++it) {
+ if (ControlItem *item = dynamic_cast<ControlItem*>(*it)) {
+ delete item;
+ }
+ }
+}
+
+int ControlRuler::valueToHeight(long val)
+{
+ long scaleVal = val * (ItemHeightRange);
+
+ int res = -(int(scaleVal / getMaxItemValue()) + MinItemHeight);
+
+ //RG_DEBUG << "ControlRuler::valueToHeight : val = " << val << " - height = " << res
+ //<< " - scaleVal = " << scaleVal << endl;
+
+ return res;
+}
+
+long ControlRuler::heightToValue(int h)
+{
+ long val = -h;
+ val -= MinItemHeight;
+ val *= getMaxItemValue();
+ val /= (ItemHeightRange);
+ val = std::min(val, long(getMaxItemValue()));
+ return val;
+}
+
+QColor ControlRuler::valueToColour(int max, int val)
+{
+ int maxDefault = DefaultVelocityColour::getInstance()->getMaxValue();
+
+ int value = val;
+
+ // Scale value accordingly
+ //
+ if (maxDefault != max)
+ value = int(double(maxDefault) * double(val) / double(max));
+
+ return DefaultVelocityColour::getInstance()->getColour(value);
+}
+
+int ControlRuler::applyTool(double x, int val)
+{
+ if (m_tool)
+ return (*m_tool)(x, val);
+ return val;
+}
+
+void ControlRuler::flipForwards()
+{
+ std::pair<int, int> minMax = getZMinMax();
+
+ QCanvasItemList l = canvas()->allItems();
+ for (QCanvasItemList::Iterator it = l.begin(); it != l.end(); ++it) {
+
+ // skip all but rectangles
+ if ((*it)->rtti() != QCanvasItem::Rtti_Rectangle)
+ continue;
+
+ // match min
+ if ((*it)->z() == minMax.second)
+ (*it)->setZ(minMax.first);
+ else
+ (*it)->setZ((*it)->z() + 1);
+ }
+
+ canvas()->update();
+}
+
+void ControlRuler::flipBackwards()
+{
+ std::pair<int, int> minMax = getZMinMax();
+
+ QCanvasItemList l = canvas()->allItems();
+ for (QCanvasItemList::Iterator it = l.begin(); it != l.end(); ++it) {
+
+ // skip all but rectangles
+ if ((*it)->rtti() != QCanvasItem::Rtti_Rectangle)
+ continue;
+
+ // match min
+ if ((*it)->z() == minMax.first)
+ (*it)->setZ(minMax.second);
+ else
+ (*it)->setZ((*it)->z() - 1);
+ }
+
+ canvas()->update();
+}
+
+std::pair<int, int> ControlRuler::getZMinMax()
+{
+ QCanvasItemList l = canvas()->allItems();
+ std::vector<int> zList;
+ for (QCanvasItemList::Iterator it=l.begin(); it!=l.end(); ++it) {
+
+ // skip all but rectangles
+ if ((*it)->rtti() != QCanvasItem::Rtti_Rectangle) continue;
+ zList.push_back(int((*it)->z()));
+ }
+
+ std::sort(zList.begin(), zList.end());
+
+ return std::pair<int, int>(zList[0], zList[zList.size() - 1]);
+}
+
+QScrollBar* ControlRuler::getMainHorizontalScrollBar()
+{
+ return m_mainHorizontalScrollBar ? m_mainHorizontalScrollBar : horizontalScrollBar();
+}
+
+}
+#include "ControlRuler.moc"
diff --git a/src/gui/rulers/ControlRuler.h b/src/gui/rulers/ControlRuler.h
new file mode 100644
index 0000000..ac6eba4
--- /dev/null
+++ b/src/gui/rulers/ControlRuler.h
@@ -0,0 +1,182 @@
+
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
+
+/*
+ Rosegarden
+ A MIDI and audio sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_CONTROLRULER_H_
+#define _RG_CONTROLRULER_H_
+
+#include "base/Segment.h"
+#include "gui/general/RosegardenCanvasView.h"
+#include <qcolor.h>
+#include <qpoint.h>
+#include <qstring.h>
+#include <utility>
+
+
+class QWidget;
+class QWheelEvent;
+class QScrollBar;
+class QPopupMenu;
+class QMouseEvent;
+class QContextMenuEvent;
+class QCanvasRectangle;
+class QCanvas;
+
+
+namespace Rosegarden
+{
+
+class ControlTool;
+class ControlSelector;
+class ControlItem;
+class TextFloat;
+class Segment;
+class RulerScale;
+class EventSelection;
+class EditViewBase;
+
+
+/**
+ * ControlRuler : base class for Control Rulers
+ */
+class ControlRuler : public RosegardenCanvasView, public SegmentObserver
+{
+ Q_OBJECT
+
+ friend class ControlItem;
+
+public:
+ ControlRuler(Segment*,
+ RulerScale*,
+ EditViewBase* parentView,
+ QCanvas*,
+ QWidget* parent=0, const char* name=0, WFlags f=0);
+ virtual ~ControlRuler();
+
+ virtual QString getName() = 0;
+
+ int getMaxItemValue() { return m_maxItemValue; }
+ void setMaxItemValue(int val) { m_maxItemValue = val; }
+
+ void clear();
+
+ void setControlTool(ControlTool*);
+
+ int applyTool(double x, int val);
+
+ QCanvasRectangle* getSelectionRectangle() { return m_selectionRect; }
+
+ RulerScale* getRulerScale() { return m_rulerScale; }
+
+ // SegmentObserver interface
+ virtual void segmentDeleted(const Segment *);
+
+ static const int DefaultRulerHeight;
+ static const int MinItemHeight;
+ static const int MaxItemHeight;
+ static const int ItemHeightRange;
+
+ void flipForwards();
+ void flipBackwards();
+
+ void setMainHorizontalScrollBar(QScrollBar* s ) { m_mainHorizontalScrollBar = s; }
+
+signals:
+ void stateChange(const QString&, bool);
+
+public slots:
+ /// override RosegardenCanvasView - we don't want to change the main hscrollbar
+ virtual void slotUpdate();
+ virtual void slotUpdateElementsHPos();
+
+protected:
+ virtual void contentsMousePressEvent(QMouseEvent*);
+ virtual void contentsMouseReleaseEvent(QMouseEvent*);
+ virtual void contentsMouseMoveEvent(QMouseEvent*);
+ virtual void contentsWheelEvent(QWheelEvent*);
+ virtual void contentsContextMenuEvent(QContextMenuEvent*);
+
+ virtual QScrollBar* getMainHorizontalScrollBar();
+
+ virtual void computeStaffOffset() {};
+
+ virtual void layoutItem(ControlItem*);
+
+
+
+ // Stacking of the SegmentItems on the canvas
+ //
+ std::pair<int, int> getZMinMax();
+
+ virtual void init() = 0;
+ virtual void drawBackground() = 0;
+
+ int valueToHeight(long val);
+ long heightToValue(int height);
+ QColor valueToColour(int max, int val);
+
+ void clearSelectedItems();
+ void updateSelection();
+
+ void setMenuName(QString menuName) { m_menuName = menuName; }
+ void createMenu();
+
+ //--------------- Data members ---------------------------------
+
+ EditViewBase* m_parentEditView;
+ QScrollBar* m_mainHorizontalScrollBar;
+ RulerScale* m_rulerScale;
+ EventSelection* m_eventSelection;
+ Segment* m_segment;
+
+ ControlItem* m_currentItem;
+ QCanvasItemList m_selectedItems;
+
+ ControlTool *m_tool;
+
+ int m_maxItemValue;
+
+ double m_staffOffset;
+
+ double m_currentX;
+
+ QPoint m_lastEventPos;
+ bool m_itemMoved;
+
+ bool m_selecting;
+ ControlSelector* m_selector;
+ QCanvasRectangle* m_selectionRect;
+
+ QString m_menuName;
+ QPopupMenu* m_menu;
+
+ TextFloat *m_numberFloat;
+
+ bool m_hposUpdatePending;
+};
+
+
+}
+
+#endif
diff --git a/src/gui/rulers/ControlRulerEventEraseCommand.cpp b/src/gui/rulers/ControlRulerEventEraseCommand.cpp
new file mode 100644
index 0000000..7a1e493
--- /dev/null
+++ b/src/gui/rulers/ControlRulerEventEraseCommand.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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#include "ControlRulerEventEraseCommand.h"
+#include "ControlItem.h"
+#include "ElementAdapter.h"
+#include "misc/Debug.h"
+#include <klocale.h>
+
+namespace Rosegarden
+{
+
+ControlRulerEventEraseCommand::ControlRulerEventEraseCommand(QCanvasItemList selectedItems,
+ Segment &segment,
+ Rosegarden::timeT start, Rosegarden::timeT end)
+ : BasicCommand(i18n("Erase Controller Event(s)"),
+ segment,
+ start,
+ (start == end) ? start + 10 : end,
+ true),
+ m_selectedItems(selectedItems)
+{
+ RG_DEBUG << "ControllerEventEraseCommand : from " << start << " to " << end << endl;
+}
+
+
+void ControlRulerEventEraseCommand::modifySegment()
+{
+ Segment &segment(getSegment());
+
+ for (QCanvasItemList::Iterator it=m_selectedItems.begin(); it!=m_selectedItems.end(); ++it) {
+ if (ControlItem *item = dynamic_cast<ControlItem*>(*it))
+ segment.eraseSingle(item->getElementAdapter()->getEvent());
+ }
+}
+
+}
diff --git a/src/gui/rulers/ControlRulerEventEraseCommand.h b/src/gui/rulers/ControlRulerEventEraseCommand.h
new file mode 100644
index 0000000..15e213b
--- /dev/null
+++ b/src/gui/rulers/ControlRulerEventEraseCommand.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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_CONTROLLEREVENTERASECOMMAND_H_
+#define _RG_CONTROLLEREVENTERASECOMMAND_H_
+
+#include "base/Event.h"
+#include "document/BasicCommand.h"
+#include <qcanvas.h>
+
+namespace Rosegarden
+{
+
+class ControlRulerEventEraseCommand : public BasicCommand
+{
+public:
+
+ ControlRulerEventEraseCommand(QCanvasItemList selectedItems,
+ Segment &segment,
+ Rosegarden::timeT start, Rosegarden::timeT end);
+ virtual ~ControlRulerEventEraseCommand() {;}
+
+
+protected:
+
+ virtual void modifySegment();
+
+ QCanvasItemList m_selectedItems;
+};
+
+}
+
+#endif /*CONTROLLEREVENTERASECOMMAND_H_*/
diff --git a/src/gui/rulers/ControlRulerEventInsertCommand.cpp b/src/gui/rulers/ControlRulerEventInsertCommand.cpp
new file mode 100644
index 0000000..ab6a73c
--- /dev/null
+++ b/src/gui/rulers/ControlRulerEventInsertCommand.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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#include "ControlRulerEventInsertCommand.h"
+#include "base/MidiTypes.h"
+#include <klocale.h>
+
+namespace Rosegarden
+{
+
+ControlRulerEventInsertCommand::ControlRulerEventInsertCommand(const std::string &type,
+ timeT insertTime,
+ long number, long initialValue,
+ Segment &segment)
+ : BasicCommand(i18n("Insert Controller Event"),
+ segment,
+ insertTime,
+ (insertTime + Rosegarden::Note(Rosegarden::Note::Quaver).getDuration())), // must have a duration other undo doesn't work
+ m_type(type),
+ m_number(number),
+ m_initialValue(initialValue)
+{
+}
+
+void ControlRulerEventInsertCommand::modifySegment()
+{
+ Event* controllerEvent = new Event(m_type, getStartTime());
+
+ if (m_type == Rosegarden::Controller::EventType)
+ {
+ controllerEvent->set<Rosegarden::Int>(Rosegarden::Controller::VALUE, m_initialValue);
+ controllerEvent->set<Rosegarden::Int>(Rosegarden::Controller::NUMBER, m_number);
+ }
+ else if (m_type == Rosegarden::PitchBend::EventType)
+ {
+ // Convert to PitchBend MSB/LSB
+ int lsb = m_initialValue & 0x7f;
+ int msb = (m_initialValue >> 7) & 0x7f;
+ controllerEvent->set<Rosegarden::Int>(Rosegarden::PitchBend::MSB, msb);
+ controllerEvent->set<Rosegarden::Int>(Rosegarden::PitchBend::LSB, lsb);
+ }
+
+ getSegment().insert(controllerEvent);
+}
+
+}
diff --git a/src/gui/rulers/ControlRulerEventInsertCommand.h b/src/gui/rulers/ControlRulerEventInsertCommand.h
new file mode 100644
index 0000000..a180006
--- /dev/null
+++ b/src/gui/rulers/ControlRulerEventInsertCommand.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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_CONTROLRULEREVENTINSERTCOMMAND_H_
+#define _RG_CONTROLRULEREVENTINSERTCOMMAND_H_
+
+#include "document/BasicCommand.h"
+#include <string>
+
+namespace Rosegarden
+{
+
+class ControlRulerEventInsertCommand : public BasicCommand
+{
+public:
+ ControlRulerEventInsertCommand(const std::string &type,
+ timeT insertTime,
+ long number,
+ long initialValue,
+ Segment &segment);
+
+ virtual ~ControlRulerEventInsertCommand() {;}
+
+protected:
+
+ virtual void modifySegment();
+
+ std::string m_type;
+ long m_number;
+ long m_initialValue;
+};
+
+}
+
+#endif /*CONTROLRULEREVENTINSERTCOMMAND_H_*/
diff --git a/src/gui/rulers/ControlSelector.cpp b/src/gui/rulers/ControlSelector.cpp
new file mode 100644
index 0000000..fb4abbb
--- /dev/null
+++ b/src/gui/rulers/ControlSelector.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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_CONTROLSELECTOR_CPP_
+#define _RG_CONTROLSELECTOR_CPP_
+
+#include "ControlSelector.h"
+
+namespace Rosegarden {
+
+ControlSelector::ControlSelector(ControlRuler* parent)
+ : QObject(parent),
+ m_ruler(parent)
+{
+}
+
+void ControlSelector::handleMouseButtonPress(QMouseEvent *e)
+{
+ QPoint p = m_ruler->inverseMapPoint(e->pos());
+
+ getSelectionRectangle()->setX(p.x());
+ getSelectionRectangle()->setY(p.y());
+ getSelectionRectangle()->setSize(0,0);
+
+ getSelectionRectangle()->show();
+ m_ruler->canvas()->update();
+}
+
+void ControlSelector::handleMouseButtonRelease(QMouseEvent*)
+{
+ getSelectionRectangle()->hide();
+ m_ruler->canvas()->update();
+}
+
+void ControlSelector::handleMouseMove(QMouseEvent *e, int, int)
+{
+ QPoint p = m_ruler->inverseMapPoint(e->pos());
+
+ int w = int(p.x() - getSelectionRectangle()->x());
+ int h = int(p.y() - getSelectionRectangle()->y());
+ if (w > 0) ++w; else --w;
+ if (h > 0) ++h; else --h;
+
+ getSelectionRectangle()->setSize(w, h);
+
+ m_ruler->canvas()->update();
+}
+
+}
+
+#endif /*CONTROLSELECTOR_CPP_*/
diff --git a/src/gui/rulers/ControlSelector.h b/src/gui/rulers/ControlSelector.h
new file mode 100644
index 0000000..99ec773
--- /dev/null
+++ b/src/gui/rulers/ControlSelector.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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_CONTROLSELECTOR_H_
+#define _RG_CONTROLSELECTOR_H_
+
+#include "ControlRuler.h"
+
+class QCanvasRectangle;
+
+namespace Rosegarden {
+
+
+/**
+ * Selector tool for the ControlRuler
+ *
+ * Allow the user to select several ControlItems so he can change them
+ * all at the same time
+ */
+class ControlSelector : public QObject
+{
+public:
+ ControlSelector(ControlRuler* parent);
+ virtual ~ControlSelector() {};
+
+ virtual void handleMouseButtonPress(QMouseEvent *e);
+ virtual void handleMouseButtonRelease(QMouseEvent *e);
+ virtual void handleMouseMove(QMouseEvent *e, int deltaX, int deltaY);
+
+ QCanvasRectangle* getSelectionRectangle() { return m_ruler->getSelectionRectangle(); }
+protected:
+ //--------------- Data members ---------------------------------
+
+ ControlRuler* m_ruler;
+};
+
+}
+
+#endif /*CONTROLSELECTOR_H_*/
diff --git a/src/gui/rulers/ControlTool.h b/src/gui/rulers/ControlTool.h
new file mode 100644
index 0000000..fcc528a
--- /dev/null
+++ b/src/gui/rulers/ControlTool.h
@@ -0,0 +1,39 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
+
+/*
+ Rosegarden
+ A MIDI and audio sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef CONTROLTOOL_H_
+#define CONTROLTOOL_H_
+
+namespace Rosegarden {
+
+class ControlTool
+{
+public:
+ virtual ~ControlTool() {};
+ virtual int operator()(double x, int val) = 0;
+};
+
+}
+
+#endif /*CONTROLTOOL_H_*/
diff --git a/src/gui/rulers/ControllerEventAdapter.cpp b/src/gui/rulers/ControllerEventAdapter.cpp
new file mode 100644
index 0000000..55abd50
--- /dev/null
+++ b/src/gui/rulers/ControllerEventAdapter.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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#include "ControllerEventAdapter.h"
+#include "base/MidiTypes.h"
+#include "misc/Debug.h"
+
+namespace Rosegarden {
+
+bool ControllerEventAdapter::getValue(long& val)
+{
+ if (m_event->getType() == Rosegarden::Controller::EventType)
+ {
+ return m_event->get<Rosegarden::Int>(Rosegarden::Controller::VALUE, val);
+ }
+ else if (m_event->getType() == Rosegarden::PitchBend::EventType)
+ {
+ long msb = 0, lsb = 0;
+ m_event->get<Rosegarden::Int>(Rosegarden::PitchBend::MSB, msb);
+ m_event->get<Rosegarden::Int>(Rosegarden::PitchBend::LSB, lsb);
+
+ long value = msb;
+ value <<= 7;
+ value |= lsb;
+
+ //RG_DEBUG << "PitchBend Get Value = " << value << endl;
+
+ val = value;
+ return true;
+ }
+
+ return false;
+}
+
+void ControllerEventAdapter::setValue(long val)
+{
+ if (m_event->getType() == Rosegarden::Controller::EventType)
+ {
+ m_event->set<Rosegarden::Int>(Rosegarden::Controller::VALUE, val);
+ }
+ else if (m_event->getType() == Rosegarden::PitchBend::EventType)
+ {
+ RG_DEBUG << "PitchBend Set Value = " << val << endl;
+
+ int lsb = val & 0x7f;
+ int msb = (val >> 7) & 0x7f;
+ m_event->set<Rosegarden::Int>(Rosegarden::PitchBend::MSB, msb);
+ m_event->set<Rosegarden::Int>(Rosegarden::PitchBend::LSB, lsb);
+ }
+}
+
+timeT ControllerEventAdapter::getTime()
+{
+ return m_event->getAbsoluteTime();
+}
+
+timeT ControllerEventAdapter::getDuration()
+{
+ return m_event->getDuration();
+}
+
+}
diff --git a/src/gui/rulers/ControllerEventAdapter.h b/src/gui/rulers/ControllerEventAdapter.h
new file mode 100644
index 0000000..4f71884
--- /dev/null
+++ b/src/gui/rulers/ControllerEventAdapter.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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_CONTROLLEREVENTADAPTER_H_
+#define _RG_CONTROLLEREVENTADAPTER_H_
+
+#include "ElementAdapter.h"
+
+namespace Rosegarden {
+
+class ControllerEventAdapter : public Rosegarden::ElementAdapter
+{
+public:
+ ControllerEventAdapter(Event* e) : m_event(e) {}
+
+ virtual bool getValue(long&);
+ virtual void setValue(long);
+ virtual timeT getTime();
+ virtual timeT getDuration();
+
+ virtual Event* getEvent() { return m_event; }
+
+protected:
+
+ //--------------- Data members ---------------------------------
+
+ Event* m_event;
+};
+
+}
+
+#endif /*CONTROLLEREVENTADAPTER_H_*/
diff --git a/src/gui/rulers/ControllerEventsRuler.cpp b/src/gui/rulers/ControllerEventsRuler.cpp
new file mode 100644
index 0000000..265a701
--- /dev/null
+++ b/src/gui/rulers/ControllerEventsRuler.cpp
@@ -0,0 +1,499 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
+
+/*
+ Rosegarden
+ A MIDI and audio sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "ControllerEventsRuler.h"
+
+#include <klocale.h>
+#include "misc/Debug.h"
+#include "misc/Strings.h"
+#include "base/ControlParameter.h"
+#include "base/Event.h"
+#include "base/MidiTypes.h"
+#include "base/NotationTypes.h"
+#include "base/RulerScale.h"
+#include "base/Segment.h"
+#include "base/Selection.h"
+#include "commands/edit/EraseCommand.h"
+#include "ControlRuler.h"
+#include "ControlItem.h"
+#include "ControllerEventAdapter.h"
+#include "ControlRulerEventInsertCommand.h"
+#include "ControlRulerEventEraseCommand.h"
+#include "gui/general/EditViewBase.h"
+#include "gui/widgets/TextFloat.h"
+#include <klineeditdlg.h>
+#include <qcanvas.h>
+#include <qcolor.h>
+#include <qpoint.h>
+#include <qstring.h>
+#include <qvalidator.h>
+#include <qwidget.h>
+
+
+namespace Rosegarden
+{
+
+ControllerEventsRuler::ControllerEventsRuler(Segment *segment,
+ RulerScale* rulerScale,
+ EditViewBase* parentView,
+ QCanvas* c,
+ QWidget* parent,
+ const ControlParameter *controller,
+ const char* name, WFlags f)
+ : ControlRuler(segment, rulerScale, parentView, c, parent, name, f),
+ m_defaultItemWidth(20),
+ m_controlLine(new QCanvasLine(canvas())),
+ m_controlLineShowing(false),
+ m_controlLineX(0),
+ m_controlLineY(0)
+{
+ // Make a copy of the ControlParameter if we have one
+ //
+ if (controller)
+ m_controller = new ControlParameter(*controller);
+ else
+ m_controller = 0;
+
+ setMenuName("controller_events_ruler_menu");
+ drawBackground();
+ init();
+}
+
+void
+ControllerEventsRuler::setSegment(Segment *segment)
+{
+ RG_DEBUG << "ControllerEventsRuler::setSegment(" << segment << ")" << endl;
+
+ m_segment->removeObserver(this);
+ m_segment = segment;
+ m_segment->addObserver(this);
+
+ while (child(NULL))
+ delete (child(NULL));
+
+ drawBackground();
+ init();
+}
+
+void
+ControllerEventsRuler::init()
+{
+ // Reset range information for this controller type (for the moment
+ // this assumes min is always 0.
+ //
+ setMaxItemValue(m_controller->getMax());
+
+ for (Segment::iterator i = m_segment->begin();
+ i != m_segment->end(); ++i) {
+
+ // skip if not the same type of event that we're expecting
+ //
+ if (m_controller->getType() != (*i)->getType())
+ continue;
+
+ int width = getDefaultItemWidth();
+
+ // Check for specific controller value if we need to
+ //
+ if (m_controller->getType() == Controller::EventType) {
+ try {
+ if ((*i)->get
+ <Int>(Controller::NUMBER)
+ != m_controller->getControllerValue())
+ continue;
+ } catch (...) {
+ continue;
+ }
+ } else if (m_controller->getType() == PitchBend::EventType)
+ width /= 4;
+
+ //RG_DEBUG << "ControllerEventsRuler: adding element\n";
+
+ double x = m_rulerScale->getXForTime((*i)->getAbsoluteTime());
+ new ControlItem(this, new ControllerEventAdapter(*i),
+ int(x + m_staffOffset), width);
+ }
+}
+
+void
+ControllerEventsRuler::drawBackground()
+{
+ // Draw some minimum and maximum controller value guide lines
+ //
+ QCanvasLine *topLine = new QCanvasLine(canvas());
+ QCanvasLine *topQLine = new QCanvasLine(canvas());
+ QCanvasLine *midLine = new QCanvasLine(canvas());
+ QCanvasLine *botQLine = new QCanvasLine(canvas());
+ QCanvasLine *bottomLine = new QCanvasLine(canvas());
+ //m_controlLine->setPoints(m_controlLineX, m_controlLineY, m_controlLineX, m_controlLineY);
+ int cHeight = canvas()->height();
+ int cWidth = canvas()->width();
+
+ topLine->setPen(QColor(127, 127, 127));
+ topLine->setPoints(0, 0, cWidth, 0);
+ topLine->setZ( -10);
+ topLine->show();
+
+ topQLine->setPen(QColor(192, 192, 192));
+ topQLine->setPoints(0, cHeight / 4, cWidth, cHeight / 4);
+ topQLine->setZ( -10);
+ topQLine->show();
+
+ midLine->setPen(QColor(127, 127, 127));
+ midLine->setPoints(0, cHeight / 2, cWidth, cHeight / 2);
+ midLine->setZ( -10);
+ midLine->show();
+
+ botQLine->setPen(QColor(192, 192, 192));
+ botQLine->setPoints(0, 3*cHeight / 4, cWidth, 3*cHeight / 4);
+ botQLine->setZ( -10);
+ botQLine->show();
+
+ bottomLine->setPen(QColor(127, 127, 127));
+ bottomLine->setPoints(0, cHeight - 1, cWidth, cHeight - 1);
+ bottomLine->setZ( -10);
+ bottomLine->show();
+
+ canvas()->update();
+}
+
+ControllerEventsRuler::~ControllerEventsRuler()
+{}
+
+QString ControllerEventsRuler::getName()
+{
+ if (m_controller) {
+ QString name = i18n("Unsupported Event Type");
+
+ if (m_controller->getType() == Controller::EventType) {
+ QString hexValue;
+ hexValue.sprintf("0x%x", m_controller->getControllerValue());
+
+ name = QString("%1 (%2 / %3)").arg(strtoqstr(m_controller->getName()))
+ .arg(int(m_controller->getControllerValue()))
+ .arg(hexValue);
+ } else if (m_controller->getType() == PitchBend::EventType) {
+ name = i18n("Pitch Bend");
+ }
+
+ return name;
+ } else
+ return i18n("Controller Events");
+}
+
+void ControllerEventsRuler::eventAdded(const Segment*, Event *e)
+{
+ if (e->getType() != m_controller->getType())
+ return ;
+
+ // Check for specific controller value if we need to
+ //
+ if (e->getType() == Controller::EventType) {
+ try {
+ if (e->get
+ <Int>(Controller::NUMBER) !=
+ m_controller->getControllerValue())
+ return ;
+ } catch (...) {
+ return ;
+ }
+ }
+
+ RG_DEBUG << "ControllerEventsRuler::elementAdded()\n";
+
+ double x = m_rulerScale->getXForTime(e->getAbsoluteTime());
+
+ int width = getDefaultItemWidth();
+
+ if (m_controller->getType() == PitchBend::EventType)
+ width /= 4;
+
+ new ControlItem(this, new ControllerEventAdapter(e), int(x + m_staffOffset), width);
+}
+
+void ControllerEventsRuler::eventRemoved(const Segment*, Event *e)
+{
+ if (e->getType() != m_controller->getType())
+ return ;
+
+ clearSelectedItems();
+
+ QCanvasItemList allItems = canvas()->allItems();
+
+ for (QCanvasItemList::Iterator it = allItems.begin(); it != allItems.end(); ++it) {
+ if (ControlItem *item = dynamic_cast<ControlItem*>(*it)) {
+ ControllerEventAdapter * adapter = dynamic_cast<ControllerEventAdapter*>(item->getElementAdapter());
+ if (adapter->getEvent() == e) {
+ delete item;
+ break;
+ }
+ }
+ }
+}
+
+void ControllerEventsRuler::insertControllerEvent()
+{
+ timeT insertTime = m_rulerScale->getTimeForX(m_lastEventPos.x());
+
+
+ // compute initial value from cursor height
+ //
+ long initialValue = heightToValue(m_lastEventPos.y() - canvas()->height());
+
+ RG_DEBUG << "ControllerEventsRuler::insertControllerEvent() : inserting event at "
+ << insertTime
+ << " - initial value = " << initialValue
+ << endl;
+
+ // ask controller number to user
+ long number = 0;
+
+ if (m_controller) {
+ number = m_controller->getControllerValue();
+ } else {
+ bool ok = false;
+ QIntValidator intValidator(0, 128, this);
+ QString res = KLineEditDlg::getText(i18n("Controller Event Number"), "0",
+ &ok, this, &intValidator);
+ if (ok)
+ number = res.toULong();
+ }
+
+ ControlRulerEventInsertCommand* command =
+ new ControlRulerEventInsertCommand(m_controller->getType(),
+ insertTime, number,
+ initialValue, *m_segment);
+
+ m_parentEditView->addCommandToHistory(command);
+}
+
+void ControllerEventsRuler::eraseControllerEvent()
+{
+ RG_DEBUG << "ControllerEventsRuler::eraseControllerEvent() : deleting selected events\n";
+
+ ControlRulerEventEraseCommand* command =
+ new ControlRulerEventEraseCommand(m_selectedItems,
+ *m_segment,
+ m_eventSelection->getStartTime(),
+ m_eventSelection->getEndTime());
+ m_parentEditView->addCommandToHistory(command);
+ updateSelection();
+}
+
+void ControllerEventsRuler::clearControllerEvents()
+{
+ EventSelection *es = new EventSelection(*m_segment);
+
+ for (Segment::iterator it = m_segment->begin(); it != m_segment->end(); ++it) {
+ if (!(*it)->isa(Controller::EventType))
+ continue;
+ {
+ if (m_controller) // ensure we have only the controller events we want for this ruler
+ {
+ try
+ {
+ if ((*it)->get
+ <Int>(Controller::NUMBER)
+ != m_controller->getControllerValue())
+ continue;
+ } catch (...)
+ {
+ continue;
+ }
+
+ es->addEvent(*it);
+ }
+ }
+ }
+
+ EraseCommand *command = new EraseCommand(*es);
+ m_parentEditView->addCommandToHistory(command);
+
+}
+
+void ControllerEventsRuler::startControlLine()
+{
+ m_controlLineShowing = true;
+ this->setCursor(Qt::pointingHandCursor);
+}
+
+void ControllerEventsRuler::contentsMousePressEvent(QMouseEvent *e)
+{
+ if (!m_controlLineShowing) {
+ if (e->button() == MidButton)
+ m_lastEventPos = inverseMapPoint(e->pos());
+
+ ControlRuler::contentsMousePressEvent(e); // send super
+
+ return ;
+ }
+
+ // cancel control line mode
+ if (e->button() == RightButton) {
+ m_controlLineShowing = false;
+ m_controlLine->hide();
+
+ this->setCursor(Qt::arrowCursor);
+ return ;
+ }
+
+ if (e->button() == LeftButton) {
+ QPoint p = inverseMapPoint(e->pos());
+
+ m_controlLine->show();
+ m_controlLineX = p.x();
+ m_controlLineY = p.y();
+ m_controlLine->setPoints(m_controlLineX, m_controlLineY, m_controlLineX, m_controlLineY);
+ canvas()->update();
+ }
+}
+
+void ControllerEventsRuler::contentsMouseReleaseEvent(QMouseEvent *e)
+{
+ if (!m_controlLineShowing) {
+ if (e->button() == MidButton)
+ insertControllerEvent();
+
+ ControlRuler::contentsMouseReleaseEvent(e); // send super
+
+ return ;
+ } else {
+ QPoint p = inverseMapPoint(e->pos());
+
+ timeT startTime = m_rulerScale->getTimeForX(m_controlLineX);
+ timeT endTime = m_rulerScale->getTimeForX(p.x());
+
+ long startValue = heightToValue(m_controlLineY - canvas()->height());
+ long endValue = heightToValue(p.y() - canvas()->height());
+
+ RG_DEBUG << "ControllerEventsRuler::contentsMouseReleaseEvent - "
+ << "starttime = " << startTime
+ << ", endtime = " << endTime
+ << ", startValue = " << startValue
+ << ", endValue = " << endValue
+ << endl;
+
+ drawControlLine(startTime, endTime, startValue, endValue);
+
+ m_controlLineShowing = false;
+ m_controlLine->hide();
+ this->setCursor(Qt::arrowCursor);
+ canvas()->update();
+ }
+}
+
+void ControllerEventsRuler::contentsMouseMoveEvent(QMouseEvent *e)
+{
+ if (!m_controlLineShowing) {
+ // Don't send super if we're using the middle button
+ //
+ if (e->button() == MidButton) {
+ m_lastEventPos = inverseMapPoint(e->pos());
+ return ;
+ }
+
+ ControlRuler::contentsMouseMoveEvent(e); // send super
+ return ;
+ }
+
+ QPoint p = inverseMapPoint(e->pos());
+
+ m_controlLine->setPoints(m_controlLineX, m_controlLineY, p.x(), p.y());
+ canvas()->update();
+
+}
+
+void ControllerEventsRuler::layoutItem(ControlItem* item)
+{
+ timeT itemTime = item->getElementAdapter()->getTime();
+
+ double x = m_rulerScale->getXForTime(itemTime) + m_staffOffset;
+
+ item->setX(x);
+
+ int width = getDefaultItemWidth(); // TODO: how to scale that ??
+
+ if (m_controller->getType() == PitchBend::EventType)
+ width /= 4;
+
+ item->setWidth(width);
+
+ //RG_DEBUG << "ControllerEventsRuler::layoutItem ControlItem x = " << x
+ //<< " - width = " << width << endl;
+}
+
+void
+ControllerEventsRuler::drawControlLine(timeT startTime,
+ timeT endTime,
+ int startValue,
+ int endValue)
+{
+ if (m_controller == 0)
+ return ;
+ if (startTime > endTime) {
+ std::swap(startTime, endTime);
+ std::swap(startValue, endValue);
+ }
+
+ timeT quantDur = Note(Note::Quaver).getDuration();
+
+ // If inserting a line of PitchBends then we want a smoother curve
+ //
+ if (m_controller->getType() == PitchBend::EventType)
+ quantDur = Note(Note::Demisemiquaver).getDuration();
+
+ // for the moment enter a quantized set of events
+ timeT time = startTime, newTime = 0;
+ double step = double(endValue - startValue) / double(endTime - startTime);
+
+ KMacroCommand *macro = new KMacroCommand(i18n("Add line of controllers"));
+
+ while (time < endTime) {
+ int value = startValue + int(step * double(time - startTime));
+
+ // hit the buffers
+ if (value < m_controller->getMin())
+ value = m_controller->getMin();
+ else if (value > m_controller->getMax())
+ value = m_controller->getMax();
+
+ ControlRulerEventInsertCommand* command =
+ new ControlRulerEventInsertCommand(m_controller->getType(),
+ time, m_controller->getControllerValue(), value, *m_segment);
+
+ macro->addCommand(command);
+
+ // get new time - do it by quantized distances
+ newTime = (time / quantDur) * quantDur;
+ if (newTime > time)
+ time = newTime;
+ else
+ time += quantDur;
+ }
+
+ m_parentEditView->addCommandToHistory(macro);
+}
+
+}
diff --git a/src/gui/rulers/ControllerEventsRuler.h b/src/gui/rulers/ControllerEventsRuler.h
new file mode 100644
index 0000000..2b42274
--- /dev/null
+++ b/src/gui/rulers/ControllerEventsRuler.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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_CONTROLLEREVENTSRULER_H_
+#define _RG_CONTROLLEREVENTSRULER_H_
+
+#include "ControlRuler.h"
+#include <qstring.h>
+#include "base/Event.h"
+
+
+class QWidget;
+class QMouseEvent;
+class QCanvasLine;
+class QCanvas;
+
+
+namespace Rosegarden
+{
+
+class Segment;
+class RulerScale;
+class Event;
+class EditViewBase;
+class ControlParameter;
+class ControlItem;
+
+
+/**
+ * ControllerEventsRuler : edit Controller events
+ */
+class ControllerEventsRuler : public ControlRuler
+{
+public:
+ ControllerEventsRuler(Segment*,
+ RulerScale*,
+ EditViewBase* parentView,
+ QCanvas*,
+ QWidget* parent=0,
+ const ControlParameter *controller = 0,
+ const char* name=0, WFlags f=0);
+
+ virtual ~ControllerEventsRuler();
+
+ virtual QString getName();
+ int getDefaultItemWidth() { return m_defaultItemWidth; }
+
+ // Allow something external to reset the selection of Events
+ // that this ruler is displaying
+ //
+ void setSegment(Segment *);
+
+ /// SegmentObserver interface
+ virtual void eventAdded(const Segment *, Event *);
+ virtual void eventRemoved(const Segment *, Event *);
+
+ virtual void insertControllerEvent();
+ virtual void eraseControllerEvent();
+ virtual void clearControllerEvents();
+ virtual void startControlLine();
+
+ ControlParameter* getControlParameter() { return m_controller; }
+
+protected:
+
+ virtual void init();
+ virtual void drawBackground();
+
+ // Let's override these again here
+ //
+ virtual void contentsMousePressEvent(QMouseEvent*);
+ virtual void contentsMouseReleaseEvent(QMouseEvent*);
+ virtual void contentsMouseMoveEvent(QMouseEvent*);
+
+ virtual void layoutItem(ControlItem*);
+
+ void drawControlLine(timeT startTime,
+ timeT endTime,
+ int startValue,
+ int endValue);
+
+ //--------------- Data members ---------------------------------
+ int m_defaultItemWidth;
+
+ ControlParameter *m_controller;
+ QCanvasLine *m_controlLine;
+
+ bool m_controlLineShowing;
+ int m_controlLineX;
+ int m_controlLineY;
+};
+
+
+}
+
+#endif
diff --git a/src/gui/rulers/DefaultVelocityColour.cpp b/src/gui/rulers/DefaultVelocityColour.cpp
new file mode 100644
index 0000000..21cf75e
--- /dev/null
+++ b/src/gui/rulers/DefaultVelocityColour.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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "DefaultVelocityColour.h"
+
+#include "gui/general/GUIPalette.h"
+#include <qcolor.h>
+#include "VelocityColour.h"
+
+
+namespace Rosegarden
+{
+
+DefaultVelocityColour::DefaultVelocityColour()
+ : VelocityColour(GUIPalette::getColour(GUIPalette::LevelMeterRed),
+ GUIPalette::getColour(GUIPalette::LevelMeterOrange),
+ GUIPalette::getColour(GUIPalette::LevelMeterGreen),
+ 127, // max knee
+ 115, // red knee
+ 75, // orange knee
+ 25) // green knee
+{}
+
+DefaultVelocityColour* DefaultVelocityColour::getInstance()
+{
+ if (!m_instance) m_instance = new DefaultVelocityColour;
+
+ return m_instance;
+}
+
+DefaultVelocityColour* DefaultVelocityColour::m_instance = 0;
+
+}
diff --git a/src/gui/rulers/DefaultVelocityColour.h b/src/gui/rulers/DefaultVelocityColour.h
new file mode 100644
index 0000000..09430f3
--- /dev/null
+++ b/src/gui/rulers/DefaultVelocityColour.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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_DEFAULTVELOCITYCOLOUR_H_
+#define _RG_DEFAULTVELOCITYCOLOUR_H_
+
+#include "VelocityColour.h"
+
+
+
+
+namespace Rosegarden
+{
+
+
+
+class DefaultVelocityColour : public VelocityColour
+{
+public:
+ static DefaultVelocityColour* getInstance();
+
+protected:
+ DefaultVelocityColour();
+
+ static DefaultVelocityColour* m_instance;
+};
+
+
+
+}
+
+#endif
diff --git a/src/gui/rulers/ElementAdapter.h b/src/gui/rulers/ElementAdapter.h
new file mode 100644
index 0000000..e14ee63
--- /dev/null
+++ b/src/gui/rulers/ElementAdapter.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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_ELEMENTADAPTER_H_
+#define _RG_ELEMENTADAPTER_H_
+
+#include "base/Event.h"
+
+namespace Rosegarden {
+
+class ElementAdapter
+{
+public:
+ virtual ~ElementAdapter() {};
+
+ virtual bool getValue(long&) = 0;
+ virtual void setValue(long) = 0;
+ virtual timeT getTime() = 0;
+ virtual timeT getDuration() = 0;
+ virtual Event* getEvent() = 0;
+};
+
+}
+
+#endif /*ELEMENTADAPTER_H_*/
diff --git a/src/gui/rulers/LoopRuler.cpp b/src/gui/rulers/LoopRuler.cpp
new file mode 100644
index 0000000..bdf6c5e
--- /dev/null
+++ b/src/gui/rulers/LoopRuler.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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "LoopRuler.h"
+
+#include "misc/Debug.h"
+#include "base/RulerScale.h"
+#include "base/SnapGrid.h"
+#include "gui/general/GUIPalette.h"
+#include "gui/general/HZoomable.h"
+#include "gui/general/RosegardenCanvasView.h"
+#include <qpainter.h>
+#include <qrect.h>
+#include <qsize.h>
+#include <qwidget.h>
+#include <qtooltip.h>
+#include <klocale.h>
+#include <kaction.h>
+#include <qpainter.h>
+#include <qpointarray.h>
+#include "document/RosegardenGUIDoc.h"
+
+
+namespace Rosegarden
+{
+
+LoopRuler::LoopRuler(RosegardenGUIDoc *doc,
+ RulerScale *rulerScale,
+ int height,
+ double xorigin,
+ bool invert,
+ QWidget *parent,
+ const char *name) :
+ QWidget(parent, name),
+ m_doc(doc),
+ m_height(height),
+ m_xorigin(xorigin),
+ m_invert(invert),
+ m_currentXOffset(0),
+ m_width( -1),
+ m_activeMousePress(false),
+ m_rulerScale(rulerScale),
+ m_defaultGrid(rulerScale),
+ m_loopGrid(rulerScale),
+ m_grid(&m_defaultGrid),
+ m_loopingMode(false),
+ m_startLoop(0), m_endLoop(0),
+ m_quickMarkerPen(QPen(GUIPalette::getColour(GUIPalette::QuickMarker), 4))
+{
+ /*
+ * I need to understand if this ruler is being built for the main
+ * window (Track Editor) or for any of the segment editors. Apparently
+ * the name parameter is supplied (non-NULL) only for the main window.
+ * I can't find of any other (more decent) way to do this. Sorry.
+ */
+ m_mainWindow = (name != 0 && std::string(name).length() != 0);
+
+ setBackgroundColor(GUIPalette::getColour(GUIPalette::LoopRulerBackground));
+
+ // Always snap loop extents to beats; by default apply no snap to
+ // pointer position
+ //
+ m_defaultGrid.setSnapTime(SnapGrid::NoSnap);
+ m_loopGrid.setSnapTime(SnapGrid::SnapToBeat);
+
+ QToolTip::add
+ (this, i18n("Click and drag to move the playback pointer.\nShift-click and drag to set a range for looping or editing.\nShift-click to clear the loop or range.\nDouble-click to start playback."));
+}
+
+LoopRuler::~LoopRuler()
+{}
+
+void
+LoopRuler::setSnapGrid(SnapGrid *grid)
+{
+ if (grid == 0) m_grid = &m_defaultGrid;
+ else m_grid = grid;
+}
+
+void LoopRuler::scrollHoriz(int x)
+{
+ if (getHScaleFactor() != 1.0) {
+ m_currentXOffset = static_cast<int>( -x / getHScaleFactor());
+ repaint();
+ return;
+ }
+
+ int w = width(), h = height();
+ int dx = x - ( -m_currentXOffset);
+ m_currentXOffset = -x;
+
+ if (dx > w*3 / 4 || dx < -w*3 / 4) {
+ 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 LoopRuler::sizeHint() const
+{
+ double width =
+ m_rulerScale->getBarPosition(m_rulerScale->getLastVisibleBar()) +
+ m_rulerScale->getBarWidth(m_rulerScale->getLastVisibleBar()) +
+ m_xorigin;
+
+ QSize res(std::max(int(width), m_width), m_height);
+
+ return res;
+}
+
+QSize LoopRuler::minimumSizeHint() const
+{
+ double firstBarWidth = m_rulerScale->getBarWidth(0) + m_xorigin;
+
+ QSize res = QSize(int(firstBarWidth), m_height);
+
+ return res;
+}
+
+void LoopRuler::paintEvent(QPaintEvent* e)
+{
+ QPainter paint(this);
+
+ if (getHScaleFactor() != 1.0)
+ paint.scale(getHScaleFactor(), 1.0);
+
+ paint.setClipRegion(e->region());
+ paint.setClipRect(e->rect().normalize());
+
+ paint.setBrush(colorGroup().foreground());
+ drawBarSections(&paint);
+ drawLoopMarker(&paint);
+
+ if (m_mainWindow) {
+ timeT tQM = m_doc->getQuickMarkerTime();
+ if (tQM >= 0) {
+ // draw quick marker
+ double xQM = m_rulerScale->getXForTime(tQM)
+ + m_xorigin + m_currentXOffset;
+
+ paint.setPen(m_quickMarkerPen);
+
+ // looks necessary to compensate for shift in the CompositionView (cursor)
+ paint.translate(1, 0);
+
+ // draw red segment
+ paint.drawLine(int(xQM), 1, int(xQM), m_height-1);
+ }
+ }
+}
+
+void LoopRuler::drawBarSections(QPainter* paint)
+{
+ QRect clipRect = paint->clipRegion().boundingRect();
+
+ int firstBar = m_rulerScale->getBarForX(clipRect.x() -
+ m_currentXOffset -
+ m_xorigin);
+ int lastBar = m_rulerScale->getLastVisibleBar();
+ if (firstBar < m_rulerScale->getFirstVisibleBar()) {
+ firstBar = m_rulerScale->getFirstVisibleBar();
+ }
+
+ paint->setPen(GUIPalette::getColour(GUIPalette::LoopRulerForeground));
+
+ for (int i = firstBar; i <= lastBar; ++i) {
+
+ double x = m_rulerScale->getBarPosition(i) + m_currentXOffset + m_xorigin;
+ if ((x * getHScaleFactor()) > clipRect.x() + clipRect.width())
+ break;
+
+ double width = m_rulerScale->getBarWidth(i);
+ if (width == 0)
+ continue;
+
+ if (x + width < clipRect.x())
+ continue;
+
+ if (m_invert) {
+ paint->drawLine(int(x), 0, int(x), 5*m_height / 7);
+ } else {
+ paint->drawLine(int(x), 2*m_height / 7, int(x), m_height);
+ }
+
+ double beatAccumulator = m_rulerScale->getBeatWidth(i);
+ double inc = beatAccumulator;
+ if (inc == 0)
+ continue;
+
+ for (; beatAccumulator < width; beatAccumulator += inc) {
+ if (m_invert) {
+ paint->drawLine(int(x + beatAccumulator), 0,
+ int(x + beatAccumulator), 2 * m_height / 7);
+ } else {
+ paint->drawLine(int(x + beatAccumulator), 5*m_height / 7,
+ int(x + beatAccumulator), m_height);
+ }
+ }
+ }
+}
+
+void
+LoopRuler::drawLoopMarker(QPainter* paint)
+{
+ double x1 = (int)m_rulerScale->getXForTime(m_startLoop);
+ double x2 = (int)m_rulerScale->getXForTime(m_endLoop);
+
+ if (x1 > x2) {
+ x2 = x1;
+ x1 = (int)m_rulerScale->getXForTime(m_endLoop);
+ }
+
+ x1 += m_currentXOffset + m_xorigin;
+ x2 += m_currentXOffset + m_xorigin;
+
+ paint->save();
+ paint->setBrush(GUIPalette::getColour(GUIPalette::LoopHighlight));
+ paint->setPen(GUIPalette::getColour(GUIPalette::LoopHighlight));
+ paint->drawRect(static_cast<int>(x1), 0, static_cast<int>(x2 - x1), m_height);
+ paint->restore();
+
+}
+
+void
+LoopRuler::mousePressEvent(QMouseEvent *mE)
+{
+ RG_DEBUG << "LoopRuler::mousePressEvent: x = " << mE->x() << endl;
+
+ Qt::ButtonState bs = mE->state();
+ setLoopingMode((bs & Qt::ShiftButton) != 0);
+
+ if (mE->button() == LeftButton) {
+ double x = mE->pos().x() / getHScaleFactor() - m_currentXOffset - m_xorigin;
+
+ if (m_loopingMode) {
+ m_endLoop = m_startLoop = m_loopGrid.snapX(x);
+ } else {
+ // No -- now that we're emitting when the button is
+ // released, we _don't_ want to emit here as well --
+ // otherwise we get an irritating stutter when simply
+ // clicking on the ruler during playback
+// RG_DEBUG << "emitting setPointerPosition(" << m_rulerScale->getTimeForX(x) << ")" << endl;
+// emit setPointerPosition(m_rulerScale->getTimeForX(x));
+ }
+
+ m_activeMousePress = true;
+ emit startMouseMove(RosegardenCanvasView::FollowHorizontal);
+ }
+}
+
+void
+LoopRuler::mouseReleaseEvent(QMouseEvent *mE)
+{
+ if (mE->button() == LeftButton) {
+ if (m_loopingMode) {
+ // Cancel the loop if there was no drag
+ //
+ if (m_endLoop == m_startLoop) {
+ m_endLoop = m_startLoop = 0;
+
+ // to clear any other loop rulers
+ emit setLoop(m_startLoop, m_endLoop);
+ update();
+ }
+
+ // emit with the args around the right way
+ //
+ if (m_endLoop < m_startLoop)
+ emit setLoop(m_endLoop, m_startLoop);
+ else
+ emit setLoop(m_startLoop, m_endLoop);
+ } else {
+ // we need to re-emit this signal so that when the user releases the button
+ // after dragging the pointer, the pointer's position is updated again in the
+ // other views (typically, in the seg. canvas while the user has dragged the pointer
+ // in an edit view)
+ //
+ double x = mE->pos().x() / getHScaleFactor() - m_currentXOffset - m_xorigin;
+ emit setPointerPosition(m_grid->snapX(x));
+ }
+ emit stopMouseMove();
+ m_activeMousePress = false;
+ }
+}
+
+void
+LoopRuler::mouseDoubleClickEvent(QMouseEvent *mE)
+{
+ double x = mE->pos().x() / getHScaleFactor() - m_currentXOffset - m_xorigin;
+ if (x < 0)
+ x = 0;
+
+ RG_DEBUG << "LoopRuler::mouseDoubleClickEvent: x = " << x << ", looping = " << m_loopingMode << endl;
+
+ if (mE->button() == LeftButton && !m_loopingMode)
+ emit setPlayPosition(m_grid->snapX(x));
+}
+
+void
+LoopRuler::mouseMoveEvent(QMouseEvent *mE)
+{
+ double x = mE->pos().x() / getHScaleFactor() - m_currentXOffset - m_xorigin;
+ if (x < 0)
+ x = 0;
+
+ if (m_loopingMode) {
+ if (m_grid->snapX(x) != m_endLoop) {
+ m_endLoop = m_loopGrid.snapX(x);
+ emit dragLoopToPosition(m_endLoop);
+ update();
+ }
+ } else {
+ emit dragPointerToPosition(m_grid->snapX(x));
+ }
+
+ emit mouseMove();
+}
+
+void LoopRuler::slotSetLoopMarker(timeT startLoop,
+ timeT endLoop)
+{
+ m_startLoop = startLoop;
+ m_endLoop = endLoop;
+
+ QPainter paint(this);
+ paint.setBrush(colorGroup().foreground());
+ drawBarSections(&paint);
+ drawLoopMarker(&paint);
+
+ update();
+}
+
+}
+#include "LoopRuler.moc"
diff --git a/src/gui/rulers/LoopRuler.h b/src/gui/rulers/LoopRuler.h
new file mode 100644
index 0000000..7c30cc8
--- /dev/null
+++ b/src/gui/rulers/LoopRuler.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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_LOOPRULER_H_
+#define _RG_LOOPRULER_H_
+
+#include "base/SnapGrid.h"
+#include "gui/general/HZoomable.h"
+#include <qsize.h>
+#include <qwidget.h>
+#include <qpen.h>
+#include "base/Event.h"
+
+
+class QPaintEvent;
+class QPainter;
+class QMouseEvent;
+
+
+namespace Rosegarden
+{
+
+class RulerScale;
+class RosegardenGUIDoc;
+
+
+/**
+ * LoopRuler is a widget that shows bar and beat durations on a
+ * ruler-like scale, and reacts to mouse clicks by sending relevant
+ * signals to modify position pointer and playback/looping states.
+*/
+class LoopRuler : public QWidget, public HZoomable
+{
+ Q_OBJECT
+
+public:
+ LoopRuler(RosegardenGUIDoc *doc,
+ RulerScale *rulerScale,
+ int height = 0,
+ double xorigin = 0.0,
+ bool invert = false,
+ QWidget* parent = 0,
+ const char *name = 0);
+
+ ~LoopRuler();
+
+ void setSnapGrid(SnapGrid *grid);
+
+ virtual QSize sizeHint() const;
+ virtual QSize minimumSizeHint() const;
+
+ void scrollHoriz(int x);
+
+ void setMinimumWidth(int width) { m_width = width; }
+
+ void setHorizScaleFactor(double dy) { m_hScaleFactor = dy; }
+
+ bool hasActiveMousePress() { return m_activeMousePress; }
+
+ bool getLoopingMode() { return m_loopingMode; }
+
+public slots:
+ void slotSetLoopMarker(timeT startLoop,
+ timeT endLoop);
+
+protected:
+ // ActiveItem interface
+ virtual void mousePressEvent (QMouseEvent*);
+ virtual void mouseReleaseEvent (QMouseEvent*);
+ virtual void mouseDoubleClickEvent (QMouseEvent*);
+ virtual void mouseMoveEvent (QMouseEvent*);
+
+ virtual void paintEvent(QPaintEvent*);
+
+ void setLoopingMode(bool value) { m_loopingMode = value; }
+ void drawBarSections(QPainter*);
+ void drawLoopMarker(QPainter*); // between loop positions
+
+signals:
+ // The three main functions that this class performs
+ //
+ /// Set the pointer position on mouse single click
+ void setPointerPosition(timeT);
+
+ /// Set the pointer position on mouse drag
+ void dragPointerToPosition(timeT);
+
+ /// Set pointer position and start playing on double click
+ void setPlayPosition(timeT);
+
+ /// Set a playing loop
+ void setLoop(timeT, timeT);
+
+ /// Set the loop end position on mouse drag
+ void dragLoopToPosition(timeT);
+
+ void startMouseMove(int directionConstraint);
+ void stopMouseMove();
+ void mouseMove();
+
+protected:
+
+ //--------------- Data members ---------------------------------
+ int m_height;
+ double m_xorigin;
+ bool m_invert;
+ int m_currentXOffset;
+ int m_width;
+ bool m_activeMousePress;
+
+ RosegardenGUIDoc *m_doc;
+ bool m_mainWindow;
+ RulerScale *m_rulerScale;
+ SnapGrid m_defaultGrid;
+ SnapGrid m_loopGrid;
+ SnapGrid *m_grid;
+ QPen m_quickMarkerPen;
+ bool m_loopingMode;
+ timeT m_startLoop;
+ timeT m_endLoop;
+};
+
+
+}
+
+#endif
diff --git a/src/gui/rulers/MarkerRuler.cpp b/src/gui/rulers/MarkerRuler.cpp
new file mode 100644
index 0000000..7dcb67a
--- /dev/null
+++ b/src/gui/rulers/MarkerRuler.cpp
@@ -0,0 +1,490 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
+
+/*
+ Rosegarden
+ A MIDI and audio sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "MarkerRuler.h"
+
+#include "misc/Debug.h"
+#include "misc/Strings.h"
+#include "base/Composition.h"
+#include "base/RulerScale.h"
+#include "document/RosegardenGUIDoc.h"
+#include "gui/general/GUIPalette.h"
+#include "gui/general/HZoomable.h"
+#include "gui/dialogs/MarkerModifyDialog.h"
+#include "commands/edit/ModifyMarkerCommand.h"
+#include "document/MultiViewCommandHistory.h"
+#include <kxmlguifactory.h>
+#include <qbrush.h>
+#include <qcursor.h>
+#include <qfont.h>
+#include <qfontmetrics.h>
+#include <qpainter.h>
+#include <qpen.h>
+#include <qpoint.h>
+#include <qpopupmenu.h>
+#include <qrect.h>
+#include <qsize.h>
+#include <qstring.h>
+#include <qwidget.h>
+#include <klocale.h>
+#include <kaction.h>
+#include <kstddirs.h>
+#include <qtooltip.h>
+
+
+namespace Rosegarden
+{
+
+MarkerRuler::MarkerRuler(RosegardenGUIDoc *doc,
+ RulerScale *rulerScale,
+ int barHeight,
+ double xorigin,
+ QWidget* parent,
+ const char* name,
+ WFlags f)
+ : QWidget(parent, name, f),
+ m_barHeight(barHeight),
+ m_xorigin(xorigin),
+ m_currentXOffset(0),
+ m_width(-1),
+ m_clickX(0),
+ m_menu(0),
+ m_doc(doc),
+ m_rulerScale(rulerScale),
+ m_parentMainWindow(dynamic_cast<KMainWindow*>(doc->parent()))
+{
+ // If the parent window has a main window above it, we need to use
+ // that as the parent main window, not the document's parent.
+ // Otherwise we'll end up adding all actions to the same
+ // (document-level) action collection regardless of which window
+ // we're in.
+ QObject *probe = parent;
+ while (probe && !dynamic_cast<KMainWindow *>(probe)) probe = probe->parent();
+ if (probe) m_parentMainWindow = dynamic_cast<KMainWindow *>(probe);
+
+ // m_barFont = new QFont("helvetica", 12);
+ // m_barFont->setPixelSize(12);
+ m_barFont = new QFont();
+ m_barFont->setPointSize(10);
+
+ QString pixmapDir = KGlobal::dirs()->findResource("appdata", "pixmaps/");
+ QIconSet icon;
+
+ // Use the event insert, delete, edit icons because they are
+ // actually generic enough to serve for anything. Let's hope they
+ // don't become more event-specific in future...
+
+ icon = QIconSet(QPixmap(pixmapDir + "/toolbar/event-insert.png"));
+ new KAction(i18n("Insert Marker"), icon, 0, this,
+ SLOT(slotInsertMarkerHere()), actionCollection(),
+ "insert_marker_here");
+
+ new KAction(i18n("Insert Marker at Playback Position"), 0, this,
+ SLOT(slotInsertMarkerAtPointer()), actionCollection(),
+ "insert_marker_at_pointer");
+
+ icon = QIconSet(QPixmap(pixmapDir + "/toolbar/event-delete.png"));
+ new KAction(i18n("Delete Marker"), icon, 0, this,
+ SLOT(slotDeleteMarker()), actionCollection(),
+ "delete_marker");
+
+ icon = QIconSet(QPixmap(pixmapDir + "/toolbar/event-edit.png"));
+ new KAction(i18n("Edit Marker..."), icon, 0, this,
+ SLOT(slotEditMarker()), actionCollection(),
+ "edit_marker");
+
+ QToolTip::add
+ (this, i18n("Click on a marker to move the playback pointer.\nShift-click to set a range between markers.\nDouble-click to open the marker editor."));
+}
+
+MarkerRuler::~MarkerRuler()
+{
+ delete m_barFont;
+ // we have to do this so that the menu is re-created properly
+ // when the main window is itself recreated (on a File->New for instance)
+ KXMLGUIFactory* factory = m_parentMainWindow->factory();
+ if (factory)
+ factory->removeClient(this);
+}
+
+void
+MarkerRuler::createMenu()
+{
+ setXMLFile("markerruler.rc");
+
+ KXMLGUIFactory* factory = m_parentMainWindow->factory();
+ factory->addClient(this);
+
+ QWidget* tmp = factory->container("marker_ruler_menu", this);
+
+// if (!tmp) {
+// RG_DEBUG << "MarkerRuler::createMenu() menu not found\n"
+// << domDocument().toString(4) << endl;
+// }
+
+ m_menu = dynamic_cast<QPopupMenu*>(tmp);
+
+ if (!m_menu) {
+ RG_DEBUG << "MarkerRuler::createMenu() failed\n";
+ }
+}
+
+
+void
+MarkerRuler::scrollHoriz(int x)
+{
+ m_currentXOffset = static_cast<int>( -x / getHScaleFactor());
+ repaint();
+}
+
+QSize
+MarkerRuler::sizeHint() const
+{
+ int lastBar =
+ m_rulerScale->getLastVisibleBar();
+ double width =
+ m_rulerScale->getBarPosition(lastBar) +
+ m_rulerScale->getBarWidth(lastBar) + m_xorigin;
+
+ return QSize(std::max(int(width), m_width), m_barHeight);
+}
+
+QSize
+MarkerRuler::minimumSizeHint() const
+{
+ double firstBarWidth = m_rulerScale->getBarWidth(0) + m_xorigin;
+
+ return QSize(static_cast<int>(firstBarWidth), m_barHeight);
+}
+
+void
+MarkerRuler::slotInsertMarkerHere()
+{
+ emit addMarker(getClickPosition());
+}
+
+void
+MarkerRuler::slotInsertMarkerAtPointer()
+{
+ emit addMarker(m_doc->getComposition().getPosition());
+}
+
+void
+MarkerRuler::slotDeleteMarker()
+{
+ RG_DEBUG << "MarkerRuler::slotDeleteMarker()\n";
+
+ Rosegarden::Marker* marker = getMarkerAtClickPosition();
+
+ if (marker)
+ emit deleteMarker(marker->getID(),
+ marker->getTime(),
+ marker->getName(),
+ marker->getDescription());
+}
+
+void
+MarkerRuler::slotEditMarker()
+{
+ Rosegarden::Marker* marker = getMarkerAtClickPosition();
+
+ if (!marker) return;
+
+ // I think the ruler should be doing all this stuff itself, or
+ // emitting signals connected to a dedicated marker model object,
+ // not just relying on the app object. Same goes for practically
+ // everything else we do. Hey ho. Having this here is
+ // inconsistent with the other methods, so if anyone wants to move
+ // it, be my guest.
+
+ MarkerModifyDialog dialog(this, &m_doc->getComposition(), marker);
+ if (dialog.exec() == QDialog::Accepted) {
+ ModifyMarkerCommand *command =
+ new ModifyMarkerCommand(&m_doc->getComposition(),
+ marker->getID(),
+ dialog.getOriginalTime(),
+ dialog.getTime(),
+ qstrtostr(dialog.getName()),
+ qstrtostr(dialog.getDescription()));
+ m_doc->getCommandHistory()->addCommand(command);
+ }
+}
+
+timeT
+MarkerRuler::getClickPosition()
+{
+ timeT t = m_rulerScale->getTimeForX
+ (m_clickX - m_xorigin - m_currentXOffset);
+
+ return t;
+}
+
+Rosegarden::Marker*
+MarkerRuler::getMarkerAtClickPosition()
+{
+ QRect clipRect = visibleRect();
+
+ int firstBar = m_rulerScale->getBarForX(clipRect.x() -
+ m_currentXOffset -
+ m_xorigin);
+ int lastBar = m_rulerScale->getLastVisibleBar();
+ if (firstBar < m_rulerScale->getFirstVisibleBar()) {
+ firstBar = m_rulerScale->getFirstVisibleBar();
+ }
+
+ Composition &comp = m_doc->getComposition();
+ Composition::markercontainer markers = comp.getMarkers();
+
+ timeT start = comp.getBarStart(firstBar);
+ timeT end = comp.getBarEnd(lastBar);
+
+ // need these to calculate the visible extents of a marker tag
+ QPainter painter(this);
+ painter.setFont(*m_barFont);
+ QFontMetrics metrics = painter.fontMetrics();
+
+ for (Composition::markerconstiterator i = markers.begin();
+ i != markers.end(); ++i) {
+
+ if ((*i)->getTime() >= start && (*i)->getTime() < end) {
+
+ QString name(strtoqstr((*i)->getName()));
+
+ int x = m_rulerScale->getXForTime((*i)->getTime())
+ + m_xorigin + m_currentXOffset;
+
+ int width = metrics.width(name) + 5;
+
+ int nextX = -1;
+ Composition::markerconstiterator j = i;
+ ++j;
+ if (j != markers.end()) {
+ nextX = m_rulerScale->getXForTime((*j)->getTime())
+ + m_xorigin + m_currentXOffset;
+ }
+
+ if (m_clickX >= x && m_clickX <= x + width) {
+
+ if (nextX < x || m_clickX <= nextX) {
+
+ return *i;
+ }
+ }
+ }
+ }
+
+ return 0L;
+}
+
+void
+MarkerRuler::paintEvent(QPaintEvent*)
+{
+ QPainter painter(this);
+ painter.setFont(*m_barFont);
+
+ if (getHScaleFactor() != 1.0)
+ painter.scale(getHScaleFactor(), 1.0);
+
+ QRect clipRect = visibleRect();
+
+ int firstBar = m_rulerScale->getBarForX(clipRect.x() -
+ m_currentXOffset -
+ m_xorigin);
+ int lastBar = m_rulerScale->getLastVisibleBar();
+ if (firstBar < m_rulerScale->getFirstVisibleBar()) {
+ firstBar = m_rulerScale->getFirstVisibleBar();
+ }
+
+ painter.drawLine(m_currentXOffset, 0, static_cast<int>(visibleRect().width() / getHScaleFactor()), 0);
+
+ float minimumWidth = 25.0;
+ float testSize = ((float)(m_rulerScale->getBarPosition(firstBar + 1) -
+ m_rulerScale->getBarPosition(firstBar)))
+ / minimumWidth;
+
+ int every = 0;
+ int count = 0;
+
+ if (testSize < 1.0) {
+ every = (int(1.0 / testSize));
+
+ if (every % 2 == 0)
+ every++;
+ }
+
+ for (int i = firstBar; i <= lastBar; ++i) {
+
+ double x = m_rulerScale->getBarPosition(i) + m_xorigin + m_currentXOffset;
+
+ if ((x * getHScaleFactor()) > clipRect.x() + clipRect.width())
+ break;
+
+ // always the first bar number
+ if (every && i != firstBar) {
+ if (count < every) {
+ count++;
+ continue;
+ }
+
+ // reset count if we passed
+ count = 0;
+ }
+
+ // adjust count for first bar line
+ if (every == firstBar)
+ count++;
+
+ if (i != lastBar) {
+ painter.drawLine(static_cast<int>(x), 0, static_cast<int>(x), m_barHeight);
+
+ // disable worldXForm for text
+ QPoint textDrawPoint = painter.xForm(QPoint(static_cast<int>(x + 4), 12));
+
+ bool enableXForm = painter.hasWorldXForm();
+ painter.setWorldXForm(false);
+
+ if (i >= 0)
+ painter.drawText(textDrawPoint, QString("%1").arg(i + 1));
+
+ painter.setWorldXForm(enableXForm);
+ } else {
+ const QPen normalPen = painter.pen();
+ ;
+ QPen endPen(black, 2);
+ painter.setPen(endPen);
+ painter.drawLine(static_cast<int>(x), 0, static_cast<int>(x), m_barHeight);
+ painter.setPen(normalPen);
+ }
+ }
+
+ if (m_doc) {
+ Composition &comp = m_doc->getComposition();
+ Composition::markercontainer markers = comp.getMarkers();
+ Composition::markerconstiterator it;
+
+ timeT start = comp.getBarStart(firstBar);
+ timeT end = comp.getBarEnd(lastBar);
+
+ QFontMetrics metrics = painter.fontMetrics();
+
+ for (it = markers.begin(); it != markers.end(); ++it) {
+ if ((*it)->getTime() >= start && (*it)->getTime() < end) {
+ QString name(strtoqstr((*it)->getName()));
+
+ double x = m_rulerScale->getXForTime((*it)->getTime())
+ + m_xorigin + m_currentXOffset;
+
+ painter.fillRect(static_cast<int>(x), 1,
+ static_cast<int>(metrics.width(name) + 5),
+ m_barHeight - 2,
+ QBrush(GUIPalette::getColour(GUIPalette::MarkerBackground)));
+
+ painter.drawLine(int(x), 1, int(x), m_barHeight - 2);
+ painter.drawLine(int(x) + 1, 1, int(x) + 1, m_barHeight - 2);
+
+ QPoint textDrawPoint = painter.xForm
+ (QPoint(static_cast<int>(x + 3), m_barHeight - 4));
+
+ // disable worldXForm for text
+ bool enableXForm = painter.hasWorldXForm();
+ painter.setWorldXForm(false);
+
+ painter.drawText(textDrawPoint, name);
+
+ painter.setWorldXForm(enableXForm);
+ }
+ }
+ }
+}
+
+void
+MarkerRuler::mousePressEvent(QMouseEvent *e)
+{
+ RG_DEBUG << "MarkerRuler::mousePressEvent: x = " << e->x() << endl;
+
+ if (!m_doc || !e)
+ return;
+
+ m_clickX = e->x();
+ Rosegarden::Marker* clickedMarker = getMarkerAtClickPosition();
+
+ // if right-click, show popup menu
+ //
+ if (e->button() == RightButton) {
+ if (!m_menu)
+ createMenu();
+ if (m_menu) {
+ actionCollection()->action("delete_marker")->setEnabled(clickedMarker != 0);
+ actionCollection()->action("edit_marker")->setEnabled(clickedMarker != 0);
+ m_menu->exec(QCursor::pos());
+ }
+ return;
+ }
+
+ bool shiftPressed = ((e->state() & Qt::ShiftButton) != 0);
+
+ Composition &comp = m_doc->getComposition();
+ Composition::markercontainer markers = comp.getMarkers();
+
+ if (shiftPressed) { // set loop
+
+ timeT t = m_rulerScale->getTimeForX
+ (e->x() - m_xorigin - m_currentXOffset);
+
+ timeT prev = 0;
+
+ for (Composition::markerconstiterator i = markers.begin();
+ i != markers.end(); ++i) {
+
+ timeT cur = (*i)->getTime();
+
+ if (cur >= t) {
+ emit setLoop(prev, cur);
+ return ;
+ }
+
+ prev = cur;
+ }
+
+ if (prev > 0)
+ emit setLoop(prev, comp.getEndMarker());
+
+ } else { // set pointer to clicked marker
+
+ if (clickedMarker)
+ emit setPointerPosition(clickedMarker->getTime());
+ }
+}
+
+void
+MarkerRuler::mouseDoubleClickEvent(QMouseEvent *)
+{
+ RG_DEBUG << "MarkerRuler::mouseDoubleClickEvent" << endl;
+
+ emit editMarkers();
+}
+
+}
+#include "MarkerRuler.moc"
diff --git a/src/gui/rulers/MarkerRuler.h b/src/gui/rulers/MarkerRuler.h
new file mode 100644
index 0000000..c77e6a9
--- /dev/null
+++ b/src/gui/rulers/MarkerRuler.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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_MARKERRULER_H_
+#define _RG_MARKERRULER_H_
+
+#include "gui/general/HZoomable.h"
+#include <qsize.h>
+#include <qwidget.h>
+#include <kxmlguiclient.h>
+#include "base/Event.h"
+
+
+class QPaintEvent;
+class QMouseEvent;
+class QFont;
+class QPopupMenu;
+class KMainWindow;
+
+namespace Rosegarden
+{
+
+class Marker;
+class RulerScale;
+class RosegardenGUIDoc;
+
+
+class MarkerRuler : public QWidget, public HZoomable, public KXMLGUIClient
+{
+ Q_OBJECT
+
+public:
+ MarkerRuler(RosegardenGUIDoc *doc,
+ RulerScale *rulerScale,
+ int buttonHeight,
+ double xorigin = 0.0,
+ QWidget* parent = 0,
+ const char* name = 0,
+ WFlags f=0);
+
+ virtual ~MarkerRuler();
+
+ virtual QSize sizeHint() const;
+ virtual QSize minimumSizeHint() const;
+
+ void scrollHoriz(int x);
+
+ void setWidth(int width) { m_width = width; }
+
+signals:
+ /// Set the pointer position on mouse single click
+ void setPointerPosition(timeT);
+
+ /// Open the marker editor window on double click
+ void editMarkers();
+
+ /// add a marker
+ void addMarker(timeT);
+
+ void deleteMarker(int, timeT, QString name, QString description);
+
+ /// Set a loop range
+ void setLoop(timeT, timeT);
+
+protected slots:
+ void slotInsertMarkerHere();
+ void slotInsertMarkerAtPointer();
+ void slotDeleteMarker();
+ void slotEditMarker();
+
+protected:
+ virtual void paintEvent(QPaintEvent*);
+ virtual void mousePressEvent(QMouseEvent *e);
+ virtual void mouseDoubleClickEvent(QMouseEvent *e);
+
+ void createMenu();
+ timeT getClickPosition();
+ Rosegarden::Marker* getMarkerAtClickPosition();
+
+ //--------------- Data members ---------------------------------
+ int m_barHeight;
+ double m_xorigin;
+ int m_currentXOffset;
+ int m_width;
+ int m_clickX;
+
+ QFont *m_barFont;
+ QPopupMenu *m_menu;
+
+ RosegardenGUIDoc *m_doc;
+ RulerScale *m_rulerScale;
+ KMainWindow* m_parentMainWindow;
+
+};
+
+
+}
+
+#endif
diff --git a/src/gui/rulers/PercussionPitchRuler.cpp b/src/gui/rulers/PercussionPitchRuler.cpp
new file mode 100644
index 0000000..a89ae89
--- /dev/null
+++ b/src/gui/rulers/PercussionPitchRuler.cpp
@@ -0,0 +1,204 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
+
+/*
+ Rosegarden
+ A MIDI and audio sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "PercussionPitchRuler.h"
+
+#include "misc/Debug.h"
+#include "misc/Strings.h"
+#include "base/MidiProgram.h"
+#include "gui/editors/matrix/MatrixStaff.h"
+#include "gui/editors/matrix/MatrixView.h"
+#include "gui/general/MidiPitchLabel.h"
+#include "PitchRuler.h"
+#include <qcolor.h>
+#include <qevent.h>
+#include <qfont.h>
+#include <qfontmetrics.h>
+#include <qpainter.h>
+#include <qsize.h>
+#include <qwidget.h>
+
+
+namespace Rosegarden
+{
+
+PercussionPitchRuler::PercussionPitchRuler(QWidget *parent,
+ const MidiKeyMapping *mapping,
+ int lineSpacing) :
+ PitchRuler(parent),
+ m_mapping(mapping),
+ m_lineSpacing(lineSpacing),
+ m_mouseDown(false),
+ m_lastHoverHighlight( -1)
+{
+ m_font = new QFont();
+ m_font->setPixelSize(9);
+ m_fontMetrics = new QFontMetrics(*m_font);
+ m_width = m_fontMetrics->width(" A#2 Acoustic Bass Drum ");
+
+ setPaletteBackgroundColor(QColor(238, 238, 224));
+
+ setMouseTracking(true);
+}
+
+QSize PercussionPitchRuler::sizeHint() const
+{
+ return QSize(m_width,
+ (m_lineSpacing + 1) * m_mapping->getPitchExtent());
+}
+
+QSize PercussionPitchRuler::minimumSizeHint() const
+{
+ return QSize(m_width, m_lineSpacing + 1);
+}
+
+void PercussionPitchRuler::paintEvent(QPaintEvent*)
+{
+ QPainter paint(this);
+
+ paint.setFont(*m_font);
+
+ int minPitch = m_mapping->getPitchForOffset(0);
+ int extent = m_mapping->getPitchExtent();
+
+ for (int i = 0; i < extent; ++i) {
+ paint.drawLine(0, i * (m_lineSpacing + 1),
+ m_width, i * (m_lineSpacing + 1));
+ }
+
+ int lw = m_fontMetrics->width("A#2");
+
+ for (int i = 0; i < extent; ++i) {
+
+ MidiPitchLabel label(minPitch + i);
+ std::string key = m_mapping->getMapForKeyName(minPitch + i);
+
+ RG_DEBUG << i << ": " << label.getQString() << ": " << key << endl;
+
+ paint.drawText
+ (2, (extent - i - 1) * (m_lineSpacing + 1) +
+ m_fontMetrics->ascent() + 1,
+ label.getQString());
+
+ paint.drawText
+ (9 + lw, (extent - i - 1) * (m_lineSpacing + 1) +
+ m_fontMetrics->ascent() + 1,
+ strtoqstr(key));
+ }
+}
+
+void PercussionPitchRuler::enterEvent(QEvent *)
+{}
+
+void PercussionPitchRuler::leaveEvent(QEvent*)
+{
+ // m_hoverHighlight->hide();
+}
+
+void PercussionPitchRuler::drawHoverNote(int evPitch)
+{
+ QPainter paint(this);
+ paint.setFont(*m_font);
+
+ if (m_lastHoverHighlight != evPitch) {
+
+ int minPitch = m_mapping->getPitchForOffset(0);
+ int extent = m_mapping->getPitchExtent();
+
+ int lw = m_fontMetrics->width("A#2");
+
+ if (m_lastHoverHighlight >= 0) {
+
+ int y = (extent - (m_lastHoverHighlight - minPitch) - 1) * (m_lineSpacing + 1);
+ paint.setBrush(QColor(238, 238, 224));
+ paint.setPen(QColor(238, 238, 224));
+ paint.drawRect(lw + 7, y + 1, m_width - lw, m_lineSpacing);
+ std::string key = m_mapping->getMapForKeyName(m_lastHoverHighlight);
+ paint.setPen(Qt::black);
+ paint.drawText
+ (9 + lw, y + m_fontMetrics->ascent() + 1,
+ strtoqstr(key));
+ }
+
+ int y = (extent - (evPitch - minPitch) - 1) * (m_lineSpacing + 1);
+ m_lastHoverHighlight = evPitch;
+ paint.setBrush(paint.pen().color());
+ paint.drawRect(lw + 7, y, m_width - lw, m_lineSpacing + 1);
+ paint.setPen(QColor(238, 238, 224));
+
+ std::string key = m_mapping->getMapForKeyName(evPitch);
+ paint.drawText
+ (9 + lw, y + m_fontMetrics->ascent() + 1,
+ strtoqstr(key));
+ }
+}
+
+void PercussionPitchRuler::mouseMoveEvent(QMouseEvent* e)
+{
+ // ugh
+
+ MatrixView *matrixView = dynamic_cast<MatrixView*>(topLevelWidget());
+ if (matrixView) {
+ MatrixStaff *staff = matrixView->getStaff(0);
+ if (staff) {
+ drawHoverNote(staff->getHeightAtCanvasCoords(e->x(), e->y()));
+ }
+ }
+
+ if (m_mouseDown)
+ if (m_selecting)
+ emit keySelected(e->y(), true);
+ else
+ emit keyPressed(e->y(), true); // we're swooshing
+ else
+ emit hoveredOverKeyChanged(e->y());
+}
+
+void PercussionPitchRuler::mousePressEvent(QMouseEvent *e)
+{
+ Qt::ButtonState bs = e->state();
+
+ if (e->button() == LeftButton) {
+
+ m_mouseDown = true;
+ m_selecting = (bs & Qt::ShiftButton);
+
+ if (m_selecting)
+ emit keySelected(e->y(), false);
+ else
+ emit keyPressed(e->y(), false);
+ }
+}
+
+void PercussionPitchRuler::mouseReleaseEvent(QMouseEvent *e)
+{
+ if (e->button() == LeftButton) {
+ m_mouseDown = false;
+ m_selecting = false;
+ }
+}
+
+}
+#include "PercussionPitchRuler.moc"
diff --git a/src/gui/rulers/PercussionPitchRuler.h b/src/gui/rulers/PercussionPitchRuler.h
new file mode 100644
index 0000000..cae61ec
--- /dev/null
+++ b/src/gui/rulers/PercussionPitchRuler.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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_PERCUSSIONPITCHRULER_H_
+#define _RG_PERCUSSIONPITCHRULER_H_
+
+#include "PitchRuler.h"
+#include <qsize.h>
+
+
+class QWidget;
+class QPaintEvent;
+class QMouseEvent;
+class QFontMetrics;
+class QFont;
+class QEvent;
+
+
+namespace Rosegarden
+{
+
+class MidiKeyMapping;
+
+
+class PercussionPitchRuler : public PitchRuler
+{
+ Q_OBJECT
+public:
+ PercussionPitchRuler(QWidget *parent,
+ const MidiKeyMapping *mapping,
+ int lineSpacing);
+
+ virtual QSize sizeHint() const;
+ virtual QSize minimumSizeHint() const;
+
+ void drawHoverNote(int evPitch);
+
+signals:
+ void keyPressed(unsigned int y, bool repeating);
+ void keySelected(unsigned int y, bool repeating);
+ 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 *);
+
+ const MidiKeyMapping *m_mapping;
+
+ int m_width;
+ int m_lineSpacing;
+
+ bool m_mouseDown;
+ bool m_selecting;
+
+ int m_lastHoverHighlight;
+
+ QFont *m_font;
+ QFontMetrics *m_fontMetrics;
+};
+
+
+
+}
+
+#endif
diff --git a/src/gui/rulers/PitchRuler.cpp b/src/gui/rulers/PitchRuler.cpp
new file mode 100644
index 0000000..55f4b00
--- /dev/null
+++ b/src/gui/rulers/PitchRuler.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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "PitchRuler.h"
+
+#include <qsize.h>
+#include <qwidget.h>
+
+
+namespace Rosegarden
+{
+
+PitchRuler::
+PitchRuler(QWidget *parent) :
+ QWidget(parent)
+{
+ // nothing else
+}
+
+QSize
+PitchRuler::sizeHint() const
+{
+ return QWidget::sizeHint();
+}
+
+QSize
+PitchRuler::minimumSizeHint() const
+{
+ return QWidget::minimumSizeHint();
+}
+
+}
+#include "PitchRuler.moc"
diff --git a/src/gui/rulers/PitchRuler.h b/src/gui/rulers/PitchRuler.h
new file mode 100644
index 0000000..3c47709
--- /dev/null
+++ b/src/gui/rulers/PitchRuler.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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_PITCHRULER_H_
+#define _RG_PITCHRULER_H_
+
+#include <qsize.h>
+#include <qwidget.h>
+
+
+
+
+namespace Rosegarden
+{
+
+
+
+class PitchRuler : public QWidget
+{
+ Q_OBJECT
+public:
+ PitchRuler(QWidget *parent);
+
+ virtual QSize sizeHint() const;
+ virtual QSize minimumSizeHint() const;
+
+ virtual void drawHoverNote(int evPitch) = 0;
+
+signals:
+
+ /**
+ * A pitch has been clicked.
+ * y is the simple event y-coordinate.
+ * If the user is in the middle of dragging, repeating will be set.
+ */
+ void keyPressed(unsigned int y, bool repeating);
+
+ /**
+ * A pitch has been clicked with the selection modifier pressed.
+ * y is the simple event y-coordinate.
+ * If the user is in the middle of dragging, repeating will be set.
+ */
+ void keySelected(unsigned int y, bool repeating);
+
+ /**
+ * Emitted when the mouse cursor moves to a different pitch when
+ * not clicking or selecting.
+ * y is the simple event y-coordinate.
+ */
+ void hoveredOverKeyChanged(unsigned int y);
+};
+
+
+}
+
+#endif
diff --git a/src/gui/rulers/PropertyBox.cpp b/src/gui/rulers/PropertyBox.cpp
new file mode 100644
index 0000000..38d67ef
--- /dev/null
+++ b/src/gui/rulers/PropertyBox.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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "PropertyBox.h"
+
+#include "gui/general/GUIPalette.h"
+#include <qpainter.h>
+#include <qsize.h>
+#include <qstring.h>
+#include <qwidget.h>
+
+
+namespace Rosegarden
+{
+
+PropertyBox::PropertyBox(QString label,
+ int width,
+ int height,
+ QWidget *parent,
+ const char *name):
+ QWidget(parent, name),
+ m_label(label),
+ m_width(width),
+ m_height(height)
+{}
+
+QSize
+PropertyBox::sizeHint() const
+{
+ return QSize(m_width, m_height);
+}
+
+QSize
+PropertyBox::minimumSizeHint() const
+{
+ return QSize(m_width, m_height);
+}
+
+void
+PropertyBox::paintEvent(QPaintEvent *e)
+{
+ QPainter paint(this);
+
+ paint.setPen(GUIPalette::getColour(GUIPalette::MatrixElementBorder));
+ //paint.setBrush(GUIPalette::getColour(GUIPalette::MatrixElementBlock));
+
+ paint.setClipRegion(e->region());
+ paint.setClipRect(e->rect().normalize());
+
+ paint.drawRect(2, 2, m_width - 3, m_height - 3);
+ paint.drawText(10, 2 * m_height / 3, m_label);
+}
+
+}
+#include "PropertyBox.moc"
diff --git a/src/gui/rulers/PropertyBox.h b/src/gui/rulers/PropertyBox.h
new file mode 100644
index 0000000..1b36f0b
--- /dev/null
+++ b/src/gui/rulers/PropertyBox.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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_PROPERTYBOX_H_
+#define _RG_PROPERTYBOX_H_
+
+#include <qsize.h>
+#include <qstring.h>
+#include <qwidget.h>
+
+
+class QPaintEvent;
+
+
+namespace Rosegarden
+{
+
+
+
+/**
+ * We use a ControlBox to help modify events on the ruler - set tools etc.
+ * and provide extra information or options.
+ *
+ */
+class PropertyBox : public QWidget
+{
+ Q_OBJECT
+
+public:
+ PropertyBox(QString label,
+ int width,
+ int height,
+ QWidget *parent=0,
+ const char *name = 0);
+
+ virtual QSize sizeHint() const;
+ virtual QSize minimumSizeHint() const;
+
+protected:
+ virtual void paintEvent(QPaintEvent *);
+
+ //--------------- Data members ---------------------------------
+
+ QString m_label;
+ int m_width;
+ int m_height;
+};
+
+
+}
+
+#endif
diff --git a/src/gui/rulers/PropertyControlRuler.cpp b/src/gui/rulers/PropertyControlRuler.cpp
new file mode 100644
index 0000000..7dc1258
--- /dev/null
+++ b/src/gui/rulers/PropertyControlRuler.cpp
@@ -0,0 +1,441 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
+
+/*
+ Rosegarden
+ A MIDI and audio sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "PropertyControlRuler.h"
+
+#include "ControlRuler.h"
+#include "ControlItem.h"
+#include "ViewElementAdapter.h"
+#include "misc/Debug.h"
+#include "base/BaseProperties.h"
+#include "base/NotationTypes.h"
+#include "base/PropertyName.h"
+#include "base/RulerScale.h"
+#include "base/Segment.h"
+#include "base/Selection.h"
+#include "base/Staff.h"
+#include "base/ViewElement.h"
+#include "commands/edit/SelectionPropertyCommand.h"
+#include "gui/general/EditViewBase.h"
+#include "gui/widgets/TextFloat.h"
+#include "gui/general/LinedStaff.h"
+#include <qcanvas.h>
+#include <qcolor.h>
+#include <qpoint.h>
+#include <qstring.h>
+#include <qwidget.h>
+
+
+namespace Rosegarden
+{
+
+PropertyControlRuler::PropertyControlRuler(PropertyName propertyName,
+ Staff* staff,
+ RulerScale* rulerScale,
+ EditViewBase* parentView,
+ QCanvas* c, QWidget* parent,
+ const char* name, WFlags f) :
+ ControlRuler(&(staff->getSegment()), rulerScale,
+ parentView, c, parent, name, f),
+ m_propertyName(propertyName),
+ m_staff(staff),
+ m_propertyLine(new QCanvasLine(canvas())),
+ m_propertyLineShowing(false),
+ m_propertyLineX(0),
+ m_propertyLineY(0)
+{
+ m_staff->addObserver(this);
+ m_propertyLine->setZ(1000); // bring to front
+
+ setMenuName("property_ruler_menu");
+ drawBackground();
+ init();
+}
+
+void
+PropertyControlRuler::setStaff(Staff *staff)
+{
+ RG_DEBUG << "PropertyControlRuler::setStaff(" << staff << ")" << endl;
+
+ m_staff->removeObserver(this);
+ m_segment->removeObserver(this);
+ m_staff = staff;
+ m_segment = &m_staff->getSegment();
+ m_staff->addObserver(this);
+ m_segment->addObserver(this);
+
+ //!!! need to delete the control items here
+
+ drawBackground();
+ init();
+}
+
+void
+PropertyControlRuler::drawBackground()
+{
+ // Draw some minimum and maximum controller value guide lines
+ //
+ QCanvasLine *topLine = new QCanvasLine(canvas());
+ QCanvasLine *topQLine = new QCanvasLine(canvas());
+ QCanvasLine *midLine = new QCanvasLine(canvas());
+ QCanvasLine *botQLine = new QCanvasLine(canvas());
+ QCanvasLine *bottomLine = new QCanvasLine(canvas());
+ //m_controlLine->setPoints(m_controlLineX, m_controlLineY, m_controlLineX, m_controlLineY);
+ int cHeight = canvas()->height();
+ int cWidth = canvas()->width();
+
+ topLine->setPen(QColor(127, 127, 127));
+ topLine->setPoints(0, 0, cWidth, 0);
+ topLine->setZ( -10);
+ topLine->show();
+
+ topQLine->setPen(QColor(192, 192, 192));
+ topQLine->setPoints(0, cHeight / 4, cWidth, cHeight / 4);
+ topQLine->setZ( -10);
+ topQLine->show();
+
+ midLine->setPen(QColor(127, 127, 127));
+ midLine->setPoints(0, cHeight / 2, cWidth, cHeight / 2);
+ midLine->setZ( -10);
+ midLine->show();
+
+ botQLine->setPen(QColor(192, 192, 192));
+ botQLine->setPoints(0, 3*cHeight / 4, cWidth, 3*cHeight / 4);
+ botQLine->setZ( -10);
+ botQLine->show();
+
+ bottomLine->setPen(QColor(127, 127, 127));
+ bottomLine->setPoints(0, cHeight - 1, cWidth, cHeight - 1);
+ bottomLine->setZ( -10);
+ bottomLine->show();
+}
+
+PropertyControlRuler::~PropertyControlRuler()
+{
+ if (m_staff) {
+ m_staff->removeObserver(this);
+ }
+}
+
+QString PropertyControlRuler::getName()
+{
+ return getPropertyName().c_str();
+}
+
+void PropertyControlRuler::init()
+{
+ ViewElementList* viewElementList = m_staff->getViewElementList();
+
+ LinedStaff* lStaff = dynamic_cast<LinedStaff*>(m_staff);
+
+ if (lStaff)
+ m_staffOffset = lStaff->getX();
+
+ for (ViewElementList::iterator i = viewElementList->begin();
+ i != viewElementList->end(); ++i) {
+
+ if ((*i)->event()->isa(Note::EventRestType))
+ continue;
+
+ double x = m_rulerScale->getXForTime((*i)->getViewAbsoluteTime());
+ new ControlItem(this, new ViewElementAdapter(*i, getPropertyName()), int(x + m_staffOffset),
+ int(m_rulerScale->getXForTime((*i)->getViewAbsoluteTime() +
+ (*i)->getViewDuration()) - x));
+
+ }
+}
+
+void PropertyControlRuler::elementAdded(const Staff *, ViewElement *el)
+{
+ RG_DEBUG << "PropertyControlRuler::elementAdded()\n";
+
+ if (el->event()->isa(Note::EventRestType))
+ return ;
+
+ double x = m_rulerScale->getXForTime(el->getViewAbsoluteTime());
+
+ new ControlItem(this, new ViewElementAdapter(el, getPropertyName()), int(x + m_staffOffset),
+ int(m_rulerScale->getXForTime(el->getViewAbsoluteTime() +
+ el->getViewDuration()) - x));
+}
+
+void PropertyControlRuler::elementRemoved(const Staff *, ViewElement *el)
+{
+ RG_DEBUG << "PropertyControlRuler::elementRemoved(\n";
+
+ clearSelectedItems();
+
+ QCanvasItemList allItems = canvas()->allItems();
+
+ for (QCanvasItemList::Iterator it = allItems.begin(); it != allItems.end(); ++it) {
+ if (ControlItem *item = dynamic_cast<ControlItem*>(*it)) {
+ ViewElementAdapter * adapter = dynamic_cast<ViewElementAdapter*>(item->getElementAdapter());
+ if (adapter->getViewElement() == el) {
+ delete item;
+ break;
+ }
+ }
+ }
+}
+
+void PropertyControlRuler::staffDeleted(const Staff *)
+{
+ m_staff = 0;
+}
+
+void
+PropertyControlRuler::endMarkerTimeChanged(const Segment *s, bool)
+{
+ timeT endMarkerTime = s->getEndMarkerTime();
+
+ RG_DEBUG << "PropertyControlRuler::endMarkerTimeChanged() " << endMarkerTime << endl;
+
+ clearSelectedItems();
+
+ clear();
+ init();
+}
+
+void PropertyControlRuler::computeStaffOffset()
+{
+ LinedStaff* lStaff = dynamic_cast<LinedStaff*>(m_staff);
+ if (lStaff)
+ m_staffOffset = lStaff->getX();
+}
+
+void PropertyControlRuler::startPropertyLine()
+{
+ RG_DEBUG << "PropertyControlRuler::startPropertyLine\n";
+ m_propertyLineShowing = true;
+ this->setCursor(Qt::pointingHandCursor);
+}
+
+void
+PropertyControlRuler::contentsMousePressEvent(QMouseEvent *e)
+{
+ RG_DEBUG << "PropertyControlRuler::contentsMousePressEvent\n";
+
+ if (!m_propertyLineShowing) {
+ if (e->button() == MidButton)
+ m_lastEventPos = inverseMapPoint(e->pos());
+
+ ControlRuler::contentsMousePressEvent(e); // send super
+
+ return ;
+ }
+
+ // cancel control line mode
+ if (e->button() == RightButton) {
+ m_propertyLineShowing = false;
+ m_propertyLine->hide();
+
+ this->setCursor(Qt::arrowCursor);
+ return ;
+ }
+
+ if (e->button() == LeftButton) {
+ QPoint p = inverseMapPoint(e->pos());
+
+ m_propertyLine->show();
+ m_propertyLineX = p.x();
+ m_propertyLineY = p.y();
+ m_propertyLine->setPoints(m_propertyLineX, m_propertyLineY, m_propertyLineX, m_propertyLineY);
+ canvas()->update();
+ }
+}
+
+void
+PropertyControlRuler::contentsMouseReleaseEvent(QMouseEvent *e)
+{
+ RG_DEBUG << "PropertyControlRuler::contentsMouseReleaseEvent\n";
+
+ /*
+ if (m_propertyLineShowing)
+ {
+ this->setCursor(Qt::arrowCursor);
+ m_propertyLineShowing = false;
+ canvas()->update();
+ }
+ */
+
+ if (!m_propertyLineShowing) {
+ /*
+ if (e->button() == MidButton)
+ insertControllerEvent();
+ */
+
+ ControlRuler::contentsMouseReleaseEvent(e); // send super
+ return ;
+ } else {
+ QPoint p = inverseMapPoint(e->pos());
+
+ timeT startTime = m_rulerScale->getTimeForX(m_propertyLineX);
+ timeT endTime = m_rulerScale->getTimeForX(p.x());
+
+ long startValue = heightToValue(m_propertyLineY - canvas()->height());
+ long endValue = heightToValue(p.y() - canvas()->height());
+
+ RG_DEBUG << "PropertyControlRuler::contentsMouseReleaseEvent - "
+ << "starttime = " << startTime
+ << ", endtime = " << endTime
+ << ", startValue = " << startValue
+ << ", endValue = " << endValue
+ << endl;
+
+ drawPropertyLine(startTime, endTime, startValue, endValue);
+
+ m_propertyLineShowing = false;
+ m_propertyLine->hide();
+ this->setCursor(Qt::arrowCursor);
+ canvas()->update();
+ }
+}
+
+void
+PropertyControlRuler::contentsMouseMoveEvent(QMouseEvent *e)
+{
+ RG_DEBUG << "PropertyControlRuler::contentsMouseMoveEvent\n";
+
+ if (!m_propertyLineShowing) {
+ // Don't send super if we're using the middle button
+ //
+ if (e->button() == MidButton) {
+ m_lastEventPos = inverseMapPoint(e->pos());
+ return ;
+ }
+
+ ControlRuler::contentsMouseMoveEvent(e); // send super
+ return ;
+ }
+
+ QPoint p = inverseMapPoint(e->pos());
+
+ m_propertyLine->setPoints(m_propertyLineX, m_propertyLineY, p.x(), p.y());
+ canvas()->update();
+}
+
+void PropertyControlRuler::contentsContextMenuEvent(QContextMenuEvent* e)
+{
+ RG_DEBUG << "PropertyControlRuler::contentsContextMenuEvent\n";
+
+ // check if we actually have some control items
+ QCanvasItemList list = canvas()->allItems();
+ bool haveItems = false;
+
+ QCanvasItemList::Iterator it = list.begin();
+ for (; it != list.end(); ++it) {
+ if (dynamic_cast<ControlItem*>(*it)) {
+ haveItems = true;
+ break;
+ }
+ }
+
+ RG_DEBUG << "PropertyControlRuler::contentsContextMenuEvent : haveItems = "
+ << haveItems << endl;
+
+ emit stateChange("have_note_events_in_segment", haveItems);
+
+ ControlRuler::contentsContextMenuEvent(e);
+}
+
+void
+PropertyControlRuler::drawPropertyLine(timeT startTime,
+ timeT endTime,
+ int startValue,
+ int endValue)
+{
+ if (startTime > endTime) {
+ std::swap(startTime, endTime);
+ std::swap(startValue, endValue);
+ }
+
+ RG_DEBUG << "PropertyControlRuler::drawPropertyLine - set velocity from "
+ << startTime
+ << " to " << endTime << endl;
+
+ // Add the "true" to catch Events overlapping this line
+ //
+ EventSelection selection(*m_segment, startTime, endTime, true);
+ PropertyPattern pattern = DecrescendoPattern;
+
+ bool haveNotes = selection.contains(Note::EventType);
+
+ if (haveNotes) {
+
+ SelectionPropertyCommand *command =
+ new SelectionPropertyCommand(&selection,
+ BaseProperties::VELOCITY,
+ pattern,
+ startValue,
+ endValue);
+
+ m_parentEditView->addCommandToHistory(command);
+
+ } else {
+
+ RG_DEBUG << "PropertyControlRuler::drawPropertyLine - no notes in selection\n";
+
+ }
+}
+
+void
+PropertyControlRuler::selectAllProperties()
+{
+ RG_DEBUG << "PropertyControlRuler::selectAllProperties" << endl;
+
+ /*
+ for(Segment::iterator i = m_segment.begin();
+ i != m_segment.end(); ++i)
+ if (!m_eventSelection->contains(*i)) m_eventSelection->addEvent(*i);
+ */
+
+ clearSelectedItems();
+
+ QCanvasItemList l = canvas()->allItems();
+ for (QCanvasItemList::Iterator it = l.begin(); it != l.end(); ++it) {
+ if (ControlItem *item = dynamic_cast<ControlItem*>(*it)) {
+ m_selectedItems << item;
+ (*it)->setSelected(true);
+ ElementAdapter* adapter = item->getElementAdapter();
+ m_eventSelection->addEvent(adapter->getEvent());
+ }
+ }
+
+ /*
+ m_eventSelection->addFromSelection(&selection);
+ for (QCanvasItemList::Iterator it=m_selectedItems.begin(); it!=m_selectedItems.end(); ++it) {
+ if (ControlItem *item = dynamic_cast<ControlItem*>(*it)) {
+
+ ElementAdapter* adapter = item->getElementAdapter();
+ m_eventSelection->addEvent(adapter->getEvent());
+ item->handleMouseButtonRelease(e);
+ }
+ }
+ */
+
+ emit stateChange("have_controller_item_selected", true);
+}
+
+}
diff --git a/src/gui/rulers/PropertyControlRuler.h b/src/gui/rulers/PropertyControlRuler.h
new file mode 100644
index 0000000..f94f3e1
--- /dev/null
+++ b/src/gui/rulers/PropertyControlRuler.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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_PROPERTYCONTROLRULER_H_
+#define _RG_PROPERTYCONTROLRULER_H_
+
+#include "base/PropertyName.h"
+#include "base/Staff.h"
+#include "ControlRuler.h"
+#include <qstring.h>
+#include "base/Event.h"
+
+
+class QWidget;
+class QMouseEvent;
+class QContextMenuEvent;
+class QCanvasLine;
+class QCanvas;
+
+
+namespace Rosegarden
+{
+
+class ViewElement;
+class Staff;
+class Segment;
+class RulerScale;
+class EditViewBase;
+
+
+/**
+ * PropertyControlRuler : edit a property on events on a staff (only
+ * events with a ViewElement attached, mostly notes)
+ */
+class PropertyControlRuler : public ControlRuler, public StaffObserver
+{
+public:
+ PropertyControlRuler(PropertyName propertyName,
+ Staff*,
+ RulerScale*,
+ EditViewBase* parentView,
+ QCanvas*,
+ QWidget* parent=0, const char* name=0, WFlags f=0);
+
+ virtual ~PropertyControlRuler();
+
+ virtual QString getName();
+
+ const PropertyName& getPropertyName() { return m_propertyName; }
+
+ // Allow something external to reset the selection of Events
+ // that this ruler is displaying
+ //
+ void setStaff(Staff *);
+
+ // StaffObserver interface
+ virtual void elementAdded(const Staff *, ViewElement*);
+ virtual void elementRemoved(const Staff *, ViewElement*);
+ virtual void staffDeleted(const Staff *);
+ virtual void startPropertyLine();
+ virtual void selectAllProperties();
+
+ /// SegmentObserver interface
+ virtual void endMarkerTimeChanged(const Segment *, bool shorten);
+
+protected:
+
+ virtual void contentsMousePressEvent(QMouseEvent*);
+ virtual void contentsMouseReleaseEvent(QMouseEvent*);
+ virtual void contentsMouseMoveEvent(QMouseEvent*);
+ virtual void contentsContextMenuEvent(QContextMenuEvent*);
+
+ void drawPropertyLine(timeT startTime,
+ timeT endTime,
+ int startValue,
+ int endValue);
+
+ virtual void init();
+ virtual void drawBackground();
+ virtual void computeStaffOffset();
+
+ //--------------- Data members ---------------------------------
+
+ PropertyName m_propertyName;
+ Staff* m_staff;
+
+ QCanvasLine *m_propertyLine;
+
+ bool m_propertyLineShowing;
+ int m_propertyLineX;
+ int m_propertyLineY;
+};
+
+
+
+}
+
+#endif
diff --git a/src/gui/rulers/PropertyViewRuler.cpp b/src/gui/rulers/PropertyViewRuler.cpp
new file mode 100644
index 0000000..cf5d89d
--- /dev/null
+++ b/src/gui/rulers/PropertyViewRuler.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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "PropertyViewRuler.h"
+
+#include "base/Event.h"
+#include <klocale.h>
+#include "misc/Strings.h"
+#include "base/PropertyName.h"
+#include "base/RulerScale.h"
+#include "base/Segment.h"
+#include "DefaultVelocityColour.h"
+#include "gui/general/GUIPalette.h"
+#include "gui/general/HZoomable.h"
+#include <qfont.h>
+#include <qfontmetrics.h>
+#include <qpainter.h>
+#include <qrect.h>
+#include <qsize.h>
+#include <qstring.h>
+#include <qtooltip.h>
+#include <qwidget.h>
+
+
+namespace Rosegarden
+{
+
+PropertyViewRuler::PropertyViewRuler(RulerScale *rulerScale,
+ Segment *segment,
+ const PropertyName &property,
+ double xorigin,
+ int height,
+ QWidget *parent,
+ const char *name) :
+ QWidget(parent, name),
+ m_propertyName(property),
+ m_xorigin(xorigin),
+ m_height(height),
+ m_currentXOffset(0),
+ m_width( -1),
+ m_segment(segment),
+ m_rulerScale(rulerScale),
+ m_fontMetrics(m_boldFont)
+{
+ m_boldFont.setBold(true);
+ m_fontMetrics = QFontMetrics(m_boldFont);
+
+ setBackgroundColor(GUIPalette::getColour(GUIPalette::SegmentCanvas));
+
+ QString tip = i18n("%1 controller").arg(strtoqstr(property));
+ QToolTip::add
+ (this, tip);
+}
+
+PropertyViewRuler::~PropertyViewRuler()
+{
+ // nothing
+}
+
+void
+PropertyViewRuler::slotScrollHoriz(int x)
+{
+ int w = width(), h = height();
+ x = int(double(x) / getHScaleFactor());
+ int dx = x - ( -m_currentXOffset);
+ m_currentXOffset = -x;
+
+ if (dx > w*3 / 4 || dx < -w*3 / 4) {
+ 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
+PropertyViewRuler::sizeHint() const
+{
+ double width =
+ m_rulerScale->getBarPosition(m_rulerScale->getLastVisibleBar()) +
+ m_rulerScale->getBarWidth(m_rulerScale->getLastVisibleBar()) +
+ m_xorigin;
+
+ QSize res(std::max(int(width), m_width), m_height);
+
+ return res;
+}
+
+QSize
+PropertyViewRuler::minimumSizeHint() const
+{
+ double firstBarWidth = m_rulerScale->getBarWidth(0) + m_xorigin;
+ QSize res = QSize(int(firstBarWidth), m_height);
+ return res;
+}
+
+void
+PropertyViewRuler::paintEvent(QPaintEvent* e)
+{
+ QPainter paint(this);
+
+ if (getHScaleFactor() != 1.0)
+ paint.scale(getHScaleFactor(), 1.0);
+
+ paint.setPen(GUIPalette::getColour(GUIPalette::MatrixElementBorder));
+
+ QRect clipRect = e->rect().normalize();
+
+ timeT from = m_rulerScale->getTimeForX
+ (clipRect.x() - m_currentXOffset - m_xorigin);
+
+ Segment::iterator it = m_segment->findNearestTime(from);
+
+ for (; m_segment->isBeforeEndMarker(it); it++) {
+ long value = 0;
+
+ if (!(*it)->get
+ <Int>(m_propertyName, value))
+ continue;
+
+ int x = int(m_rulerScale->getXForTime((*it)->getAbsoluteTime()))
+ + m_currentXOffset + int(m_xorigin);
+
+ int xPos = x * int(getHScaleFactor());
+
+ if (xPos < clipRect.x())
+ continue;
+
+ if (xPos > (clipRect.x() + clipRect.width()))
+ break;
+
+ // include fiddle factor (+2)
+ int width =
+ int(m_rulerScale->getXForTime((*it)->getAbsoluteTime() +
+ (*it)->getDuration()) + 2)
+ + m_currentXOffset + int(m_xorigin) - x;
+
+ int blockHeight = int(double(height()) * (value / 127.0));
+
+ paint.setBrush(DefaultVelocityColour::getInstance()->getColour(value));
+
+ paint.drawRect(x, height() - blockHeight, width, blockHeight);
+ }
+}
+
+}
+#include "PropertyViewRuler.moc"
diff --git a/src/gui/rulers/PropertyViewRuler.h b/src/gui/rulers/PropertyViewRuler.h
new file mode 100644
index 0000000..b7d479c
--- /dev/null
+++ b/src/gui/rulers/PropertyViewRuler.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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_PROPERTYVIEWRULER_H_
+#define _RG_PROPERTYVIEWRULER_H_
+
+#include "base/PropertyName.h"
+#include "gui/general/HZoomable.h"
+#include <qfont.h>
+#include <qfontmetrics.h>
+#include <qsize.h>
+#include <qwidget.h>
+
+
+class QPaintEvent;
+
+
+namespace Rosegarden
+{
+
+class Segment;
+class RulerScale;
+
+
+/**
+ * PropertyViewRuler is a widget that shows a range of Property
+ * (velocity, typically) values for a set of Rosegarden Events.
+ */
+class PropertyViewRuler : public QWidget, public HZoomable
+{
+ Q_OBJECT
+
+public:
+ PropertyViewRuler(RulerScale *rulerScale,
+ Segment *segment,
+ const PropertyName &property,
+ double xorigin = 0.0,
+ int height = 0,
+ QWidget* parent = 0,
+ const char *name = 0);
+
+ ~PropertyViewRuler();
+
+ virtual QSize sizeHint() const;
+ virtual QSize minimumSizeHint() const;
+
+ void setMinimumWidth(int width) { m_width = width; }
+
+ /**
+ * Get the property name
+ */
+ PropertyName getPropertyName() const { return m_propertyName; }
+
+public slots:
+ void slotScrollHoriz(int x);
+
+protected:
+ virtual void paintEvent(QPaintEvent *);
+
+ //--------------- Data members ---------------------------------
+
+ PropertyName m_propertyName;
+
+ double m_xorigin;
+ int m_height;
+ int m_currentXOffset;
+ int m_width;
+
+ Segment *m_segment;
+ RulerScale *m_rulerScale;
+
+ QFont m_font;
+ QFont m_boldFont;
+ QFontMetrics m_fontMetrics;
+};
+
+
+}
+
+#endif
diff --git a/src/gui/rulers/RawNoteRuler.cpp b/src/gui/rulers/RawNoteRuler.cpp
new file mode 100644
index 0000000..cc7d6e4
--- /dev/null
+++ b/src/gui/rulers/RawNoteRuler.cpp
@@ -0,0 +1,573 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
+
+/*
+ Rosegarden
+ A MIDI and audio sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "RawNoteRuler.h"
+
+#include "misc/Debug.h"
+#include "base/BaseProperties.h"
+#include "base/Composition.h"
+#include "base/NotationTypes.h"
+#include "base/NotationQuantizer.h"
+#include "base/RulerScale.h"
+#include "base/Segment.h"
+#include "DefaultVelocityColour.h"
+#include "gui/general/GUIPalette.h"
+#include <klocale.h>
+#include <qcolor.h>
+#include <qpainter.h>
+#include <qrect.h>
+#include <qsize.h>
+#include <qtooltip.h>
+#include <qwidget.h>
+
+
+namespace Rosegarden
+{
+
+RawNoteRuler::RawNoteRuler(RulerScale *rulerScale,
+ Segment *segment,
+ 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_segment(segment),
+ m_rulerScale(rulerScale)
+{
+ setBackgroundColor(GUIPalette::getColour(GUIPalette::RawNoteRulerBackground));
+ QToolTip::add(this,"");
+}
+
+RawNoteRuler::~RawNoteRuler()
+{
+ QToolTip::remove(this);
+ // nothing else
+}
+
+void
+RawNoteRuler::slotScrollHoriz(int x)
+{
+ int w = width(), h = height();
+ int dx = x - ( -m_currentXOffset);
+ if (dx == 0)
+ return ;
+ m_currentXOffset = -x;
+
+ if (dx > w*3 / 4 || dx < -w*3 / 4) {
+ 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
+RawNoteRuler::sizeHint() const
+{
+ double width =
+ m_rulerScale->getBarPosition(m_rulerScale->getLastVisibleBar()) +
+ m_rulerScale->getBarWidth(m_rulerScale->getLastVisibleBar()) +
+ m_xorigin;
+
+ QSize res(std::max(int(width), m_width), m_height);
+
+ return res;
+}
+
+QSize
+RawNoteRuler::minimumSizeHint() const
+{
+ double firstBarWidth = m_rulerScale->getBarWidth(0) + m_xorigin;
+ QSize res = QSize(int(firstBarWidth), m_height);
+ return res;
+}
+
+std::pair<timeT, timeT>
+RawNoteRuler::getExtents(Segment::iterator i)
+{
+ const Quantizer *q =
+ m_segment->getComposition()->getNotationQuantizer();
+
+ timeT u0 = (*i)->getAbsoluteTime();
+ timeT u1 = u0 + (*i)->getDuration();
+
+ timeT q0 = q->getQuantizedAbsoluteTime(*i);
+ timeT q1 = q0 + q->getQuantizedDuration(*i);
+
+ timeT t0 = std::min(u0, q0);
+ timeT t1 = std::max(u1, q1);
+
+ return std::pair<timeT, timeT>(t0, t1);
+}
+
+Segment::iterator
+RawNoteRuler::addChildren(Segment *s,
+ Segment::iterator to,
+ timeT rightBound,
+ EventTreeNode *node)
+{
+ Segment::iterator i = node->node;
+
+ std::pair<timeT, timeT> iex = getExtents(i);
+ Segment::iterator j = i;
+ Segment::iterator rightmost = to;
+
+#ifdef DEBUG_RAW_NOTE_RULER
+
+ RG_DEBUG << "addChildren called for extents " << iex.first << "->" << iex.second << ", rightBound " << rightBound << endl;
+#endif
+
+ for (++j; j != to && s->isBeforeEndMarker(j); ) {
+
+ if (!(*j)->isa(Note::EventType)) {
+ ++j;
+ continue;
+ }
+ std::pair<timeT, timeT> jex = getExtents(j);
+
+#ifdef DEBUG_RAW_NOTE_RULER
+
+ RG_DEBUG << "addChildren: event at " << (*j)->getAbsoluteTime() << ", extents " << jex.first << "->" << jex.second << endl;
+#endif
+
+ if (jex.first == jex.second) {
+ ++j;
+ continue;
+ }
+ if (jex.first >= iex.second || jex.first >= rightBound)
+ break;
+
+#ifdef DEBUG_RAW_NOTE_RULER
+
+ RG_DEBUG << "addChildren: adding" << endl;
+#endif
+
+ EventTreeNode *subnode = new EventTreeNode(j);
+
+ Segment::iterator subRightmost = addChildren(s, to, rightBound, subnode);
+ if (subRightmost != to)
+ rightmost = subRightmost;
+ else
+ rightmost = j;
+
+ node->children.push_back(subnode);
+ j = s->findTime(jex.second);
+ }
+
+ return rightmost;
+}
+
+void
+RawNoteRuler::buildForest(Segment *s,
+ Segment::iterator from,
+ Segment::iterator to)
+{
+ for (EventTreeNode::NodeList::iterator i = m_forest.begin();
+ i != m_forest.end(); ++i) {
+ delete *i;
+ }
+ m_forest.clear();
+
+ timeT endTime = (s->isBeforeEndMarker(to) ? (*to)->getAbsoluteTime() :
+ s->getEndMarkerTime());
+
+ for (Segment::iterator i = from; i != to && s->isBeforeEndMarker(i); ) {
+
+ if (!(*i)->isa(Note::EventType)) {
+ ++i;
+ continue;
+ }
+
+ std::pair<timeT, timeT> iex = getExtents(i);
+
+#ifdef DEBUG_RAW_NOTE_RULER
+
+ RG_DEBUG << "buildForest: event at " << (*i)->getAbsoluteTime() << ", extents " << iex.first << "->" << iex.second << endl;
+#endif
+
+ if (iex.first == iex.second) {
+ ++i;
+ continue;
+ }
+ if (iex.first >= endTime)
+ break;
+
+ EventTreeNode *node = new EventTreeNode(i);
+ Segment::iterator rightmost = addChildren(s, to, iex.second, node);
+ m_forest.push_back(node);
+
+ if (rightmost != to) {
+ i = rightmost;
+ ++i;
+ } else {
+ i = s->findTime(iex.second);
+ }
+
+#ifdef DEBUG_RAW_NOTE_RULER
+ RG_DEBUG << "findTime " << iex.second << " returned iterator at " << (i == s->end() ? -1 : (*i)->getAbsoluteTime()) << endl;
+#endif
+
+ }
+}
+
+void
+RawNoteRuler::dumpSubtree(EventTreeNode *node, int depth)
+{
+ if (!node)
+ return ;
+#ifdef DEBUG_RAW_NOTE_RULER
+
+ for (int i = 0; i < depth; ++i)
+ std::cerr << " ";
+ if (depth > 0)
+ std::cerr << "->";
+ std::cerr << (*node->node)->getAbsoluteTime() << ","
+ << (*node->node)->getDuration() << " [";
+ long pitch = 0;
+ if ((*node->node)->get
+ <Int>(PITCH, pitch)) {
+ std::cerr << pitch << "]" << std::endl;
+ }
+ else {
+ std::cerr << "no-pitch]" << std::endl;
+ }
+ for (EventTreeNode::NodeList::iterator i = node->children.begin();
+ i != node->children.end(); ++i) {
+ dumpSubtree(*i, depth + 1);
+ }
+#endif
+ (void)depth; // avoid warnings
+}
+
+void
+RawNoteRuler::dumpForest(EventTreeNode::NodeList *forest)
+{
+#ifdef DEBUG_RAW_NOTE_RULER
+ std::cerr << "\nFOREST:\n" << std::endl;
+
+ for (unsigned int i = 0; i < forest->size(); ++i) {
+
+ std::cerr << "\nTREE " << i << ":\n" << std::endl;
+ dumpSubtree((*forest)[i], 0);
+ }
+
+ std::cerr << std::endl;
+#endif
+
+ (void)forest; // avoid warnings
+}
+
+int
+RawNoteRuler::EventTreeNode::getDepth()
+{
+ int subchildrenDepth = 0;
+ for (NodeList::iterator i = children.begin();
+ i != children.end(); ++i) {
+ int subchildDepth = (*i)->getDepth();
+ if (subchildDepth > subchildrenDepth)
+ subchildrenDepth = subchildDepth;
+ }
+ return subchildrenDepth + 1;
+}
+
+int
+RawNoteRuler::EventTreeNode::getChildrenAboveOrBelow(bool below, int p)
+{
+ long pitch(p);
+ if (pitch < 0)
+ (*node)->get
+ <Int>(BaseProperties::PITCH, pitch);
+
+ int max = 0;
+
+ for (NodeList::iterator i = children.begin();
+ i != children.end(); ++i) {
+ int forThisChild = (*i)->getChildrenAboveOrBelow(below, pitch);
+ long thisChildPitch = pitch;
+ (*(*i)->node)->get
+ <Int>(BaseProperties::PITCH, thisChildPitch);
+ if (below ? (thisChildPitch < pitch) : (thisChildPitch > pitch)) {
+ ++forThisChild;
+ }
+ if (forThisChild > max)
+ max = forThisChild;
+ }
+
+ return max;
+}
+
+void
+RawNoteRuler::drawNode(QPainter &paint, DefaultVelocityColour &vc,
+ EventTreeNode *node, double height, double yorigin)
+{
+ int depth = node->getDepth();
+ int above = node->getChildrenAboveOrBelow(false);
+
+#ifdef DEBUG_RAW_NOTE_RULER
+
+ int below = node->getChildrenAboveOrBelow(true);
+
+ NOTATION_DEBUG << "RawNoteRuler::drawNode: children above: "
+ << above << ", below: " << below << endl;
+#endif
+
+ int toFit = depth;
+
+ double heightPer = double(height) / toFit;
+ if (heightPer > m_height / 4)
+ heightPer = m_height / 4;
+ if (heightPer < 2)
+ heightPer = 2;
+
+ double myOrigin = yorigin + (heightPer * above);
+ long myPitch = 60;
+ (*node->node)->get
+ <Int>(BaseProperties::PITCH, myPitch);
+
+ long velocity = 100;
+ (*node->node)->get
+ <Int>(BaseProperties::VELOCITY, velocity);
+ QColor colour = vc.getColour(velocity);
+
+ timeT start = (*node->node)->getAbsoluteTime();
+ timeT end = (*node->node)->getDuration() + start;
+
+ double u0 = m_rulerScale->getXForTime(start);
+ double u1 = m_rulerScale->getXForTime(end);
+
+ u0 += m_currentXOffset + m_xorigin;
+ u1 += m_currentXOffset + m_xorigin;
+
+ start = m_segment->getComposition()->getNotationQuantizer()->
+ getQuantizedAbsoluteTime(*node->node);
+ end = start + m_segment->getComposition()->getNotationQuantizer()->
+ getQuantizedDuration(*node->node);
+
+ double q0 = m_rulerScale->getXForTime(start);
+ double q1 = m_rulerScale->getXForTime(end);
+
+ q0 += m_currentXOffset + m_xorigin;
+ q1 += m_currentXOffset + m_xorigin;
+
+#ifdef DEBUG_RAW_NOTE_RULER
+
+ NOTATION_DEBUG << "RawNoteRuler: (" << int(start) << "," << myOrigin
+ << ") -> (" << int(end) << "," << myOrigin << ")" << endl;
+#endif
+
+ int qi0 = int(q0);
+ int ui0 = int(u0);
+ int qi1 = int(q1);
+ int ui1 = int(u1);
+ // int qiw = int(q1-q0) - 1;
+ int uiw = int(u1 - u0) - 1;
+ // int iy = int(myOrigin + (height - heightPer) / 2);
+ int iy = int(myOrigin);
+ int ih = int(heightPer);
+
+#ifdef DEBUG_RAW_NOTE_RULER
+
+ NOTATION_DEBUG << "RawNoteRuler: height " << height << ", heightPer "
+ << heightPer << ", iy " << iy << endl;
+#endif
+
+ paint.setPen(colour);
+ paint.setBrush(colour);
+ paint.drawRect(ui0 + 1, iy + 1, uiw, ih - 1);
+
+ paint.setPen(GUIPalette::getColour(GUIPalette::RawNoteRulerForeground));
+ paint.setBrush(GUIPalette::getColour(GUIPalette::RawNoteRulerForeground));
+ paint.drawLine(qi0, iy, qi1 - 1, iy);
+ paint.drawLine(qi0, iy + ih, qi1 - 1, iy + ih);
+ paint.drawLine(ui0, iy + 1, ui0, iy + ih - 1);
+ paint.drawLine(ui1 - 1, iy + 1, ui1 - 1, iy + ih - 1);
+
+ for (EventTreeNode::NodeList::iterator i = node->children.begin();
+ i != node->children.end(); ++i) {
+
+ long nodePitch = myPitch;
+ (*(*i)->node)->get
+ <Int>(BaseProperties::PITCH, nodePitch);
+
+ if (nodePitch < myPitch) {
+
+ drawNode(paint, vc, *i,
+ height - heightPer - myOrigin, myOrigin + heightPer);
+
+ } else {
+
+ drawNode(paint, vc, *i,
+ myOrigin - yorigin, yorigin);
+ }
+ }
+}
+
+void
+RawNoteRuler::paintEvent(QPaintEvent* e)
+{
+ if (!m_segment || !m_segment->getComposition())
+ return ;
+
+ // Tooltips
+ {
+ QToolTip::remove(this);
+ TrackId trackId = m_segment->getTrack();
+ Track *track =
+ m_segment->getComposition()->getTrackById(trackId);
+ int trackPosition = -1;
+ if (track)
+ trackPosition = track->getPosition();
+
+ QToolTip::add(this,i18n("Track #%1, Segment \"%2\" (runtime id %3)")
+ .arg(trackPosition + 1)
+ .arg(m_segment->getLabel())
+ .arg(m_segment->getRuntimeId()));
+ }
+
+ // START_TIMING;
+
+ QPainter paint(this);
+ paint.setClipRegion(e->region());
+ paint.setClipRect(e->rect().normalize());
+
+ QRect clipRect = paint.clipRegion().boundingRect();
+
+ timeT from = m_rulerScale->getTimeForX
+ (clipRect.x() - m_currentXOffset - 100 - m_xorigin);
+ timeT to = m_rulerScale->getTimeForX
+ (clipRect.x() + clipRect.width() - m_currentXOffset + 100 - m_xorigin);
+
+ paint.setPen(GUIPalette::getColour(GUIPalette::RawNoteRulerForeground));
+ paint.setBrush(GUIPalette::getColour(GUIPalette::RawNoteRulerForeground));
+ paint.drawLine(0, 0, width(), 0);
+
+ // draw the extent of the segment using its color
+
+ QColor brushColor = GUIPalette::convertColour(m_segment->getComposition()->
+ getSegmentColourMap().getColourByIndex(m_segment->getColourIndex()));
+ paint.setPen(brushColor);
+ paint.setBrush(brushColor);
+ int x0 = int(m_rulerScale->getXForTime(m_segment->getStartTime()) +
+ m_currentXOffset + m_xorigin);
+ int x1 = int(m_rulerScale->getXForTime(m_segment->getEndTime()) +
+ m_currentXOffset + m_xorigin);
+ paint.drawRect(x0, 1, x1-x0+1, height()-1);
+
+ // draw the bar divisions
+
+ int firstBar = m_segment->getComposition()->getBarNumber(from);
+ int lastBar = m_segment->getComposition()->getBarNumber(to);
+ std::vector<int> divisions;
+
+ for (int barNo = firstBar; barNo <= lastBar; ++barNo) {
+
+ bool isNew = false;
+ TimeSignature timeSig =
+ m_segment->getComposition()->getTimeSignatureInBar(barNo, isNew);
+ if (isNew || barNo == firstBar) {
+ timeSig.getDivisions(3, divisions);
+ if (timeSig == TimeSignature()) // special case for 4/4
+ divisions[0] = 2;
+ }
+
+ timeT barStart = m_segment->getComposition()->getBarStart(barNo);
+ timeT base = timeSig.getBarDuration();
+ timeT barEnd = barStart + base;
+
+ paint.setPen(GUIPalette::getColour(GUIPalette::RawNoteRulerForeground));
+ paint.setBrush(GUIPalette::getColour(GUIPalette::RawNoteRulerForeground));
+
+ int x = int(m_rulerScale->getXForTime(barStart) +
+ m_currentXOffset + m_xorigin);
+ paint.drawLine(x, 1, x, m_height);
+
+ for (int depth = 0; depth < 3; ++depth) {
+
+ int grey = depth * 60 + 60;
+ paint.setPen(QColor(grey, grey, grey));
+ paint.setBrush(QColor(grey, grey, grey));
+
+ base /= divisions[depth];
+ timeT t(barStart + base);
+ while (t < barEnd) {
+ if ((t - barStart) % (base * divisions[depth]) != 0) {
+ int x = int(m_rulerScale->getXForTime(t) +
+ m_currentXOffset + m_xorigin);
+ paint.drawLine(x, 1, x, m_height);
+ }
+ t += base;
+ }
+ }
+ }
+
+ // PRINT_ELAPSED("RawNoteRuler::paintEvent: drawing bar lines and divisions");
+
+#ifdef DEBUG_RAW_NOTE_RULER
+ NOTATION_DEBUG << "RawNoteRuler: from is " << from << ", to is " << to << endl;
+#endif
+
+ Segment::iterator i = m_segment->findNearestTime(from);
+ if (i == m_segment->end())
+ i = m_segment->begin();
+
+ // somewhat experimental, as is this whole class
+ Segment::iterator j = m_segment->findTime(to);
+ buildForest(m_segment, i, j);
+
+ // PRINT_ELAPSED("RawNoteRuler::paintEvent: buildForest");
+
+ dumpForest(&m_forest);
+
+ // PRINT_ELAPSED("RawNoteRuler::paintEvent: dumpForest");
+
+ for (EventTreeNode::NodeList::iterator fi = m_forest.begin();
+ fi != m_forest.end(); ++fi) {
+
+ // Each tree in the forest should represent a note that starts
+ // at a time when no other notes are playing (at least of
+ // those that started no earlier than the paint start time).
+ // Each node in that tree represents a note that starts
+ // playing during its parent node's note, or at the same time
+ // as it.
+
+ drawNode(paint, *DefaultVelocityColour::getInstance(), *fi, m_height - 3, 2);
+
+ }
+
+ // PRINT_ELAPSED("RawNoteRuler::paintEvent: complete");
+}
+
+}
+#include "RawNoteRuler.moc"
diff --git a/src/gui/rulers/RawNoteRuler.h b/src/gui/rulers/RawNoteRuler.h
new file mode 100644
index 0000000..f194062
--- /dev/null
+++ b/src/gui/rulers/RawNoteRuler.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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_RAWNOTERULER_H_
+#define _RG_RAWNOTERULER_H_
+
+#include "base/Segment.h"
+#include <qsize.h>
+#include <qwidget.h>
+#include <utility>
+#include <vector>
+#include "base/Event.h"
+
+
+class QPaintEvent;
+class QPainter;
+
+
+namespace Rosegarden
+{
+
+class RulerScale;
+class DefaultVelocityColour;
+
+
+/**
+ * RawNoteRuler is a ruler that shows in a vaguely matrix-like fashion
+ * when notes start and end, for use with a notation view that can't
+ * otherwise show this relatively precise unquantized information.
+ * It has no editing function (yet?)
+ */
+
+class RawNoteRuler : public QWidget
+{
+ Q_OBJECT
+
+public:
+ RawNoteRuler(RulerScale *rulerScale,
+ Segment *segment,
+ double xorigin = 0.0,
+ int height = 0,
+ QWidget* parent = 0,
+ const char *name = 0);
+
+ ~RawNoteRuler();
+
+ void setCurrentSegment(Segment *segment) {
+ m_segment = segment;
+ }
+
+ 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:
+ double m_xorigin;
+ int m_height;
+ int m_currentXOffset;
+ int m_width;
+
+ Segment *m_segment;
+ RulerScale *m_rulerScale;
+
+ struct EventTreeNode
+ {
+ typedef std::vector<EventTreeNode *> NodeList;
+
+ EventTreeNode(Segment::iterator n) : node(n) { }
+ ~EventTreeNode() {
+ for (NodeList::iterator i = children.begin();
+ i != children.end(); ++i) {
+ delete *i;
+ }
+ }
+
+ int getDepth();
+ int getChildrenAboveOrBelow(bool below = false, int pitch = -1);
+
+ Segment::iterator node;
+ NodeList children;
+ };
+
+ std::pair<timeT, timeT> getExtents(Segment::iterator);
+ Segment::iterator addChildren(Segment *, Segment::iterator, timeT, EventTreeNode *);
+ void dumpSubtree(EventTreeNode *, int);
+ void dumpForest(std::vector<EventTreeNode *> *);
+ void buildForest(Segment *, Segment::iterator, Segment::iterator);
+
+ void drawNode(QPainter &, DefaultVelocityColour &, EventTreeNode *,
+ double height, double yorigin);
+
+ // needs to be class with dtor &c and containing above methods
+ EventTreeNode::NodeList m_forest;
+};
+
+
+}
+
+#endif
diff --git a/src/gui/rulers/StandardRuler.cpp b/src/gui/rulers/StandardRuler.cpp
new file mode 100644
index 0000000..611c991
--- /dev/null
+++ b/src/gui/rulers/StandardRuler.cpp
@@ -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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "StandardRuler.h"
+
+#include <klocale.h>
+#include "misc/Debug.h"
+#include "MarkerRuler.h"
+#include "base/RulerScale.h"
+#include "document/RosegardenGUIDoc.h"
+#include "document/MultiViewCommandHistory.h"
+#include "gui/application/RosegardenGUIApp.h"
+#include "gui/general/GUIPalette.h"
+#include "gui/rulers/LoopRuler.h"
+#include "document/RosegardenGUIDoc.h"
+#include <qobject.h>
+#include <qtooltip.h>
+#include <qvbox.h>
+#include <qwidget.h>
+
+
+namespace Rosegarden
+{
+
+StandardRuler::StandardRuler(RosegardenGUIDoc *doc,
+ RulerScale *rulerScale,
+ double xorigin,
+ int barHeight,
+ bool invert,
+ QWidget* parent,
+ const char* name,
+ WFlags f):
+ QVBox(parent, name, f),
+ m_invert(invert),
+ m_loopRulerHeight(10),
+ m_currentXOffset(0),
+ m_doc(doc),
+ m_rulerScale(rulerScale),
+ m_hButtonBar(0)
+{
+ setSpacing(0);
+
+ if (!m_invert) {
+ m_hButtonBar = new MarkerRuler
+ (m_doc, m_rulerScale, barHeight - m_loopRulerHeight, xorigin, this);
+ }
+
+ m_loopRuler = new LoopRuler
+ (m_doc, m_rulerScale, m_loopRulerHeight, xorigin, m_invert, this, name);
+
+ if (m_invert) {
+ m_hButtonBar = new MarkerRuler
+ (m_doc, m_rulerScale, barHeight - m_loopRulerHeight, xorigin, this);
+ }
+
+ QObject::connect
+ (doc->getCommandHistory(), SIGNAL(commandExecuted()),
+ this, SLOT(update()));
+
+}
+
+void StandardRuler::setSnapGrid(SnapGrid *grid)
+{
+ m_loopRuler->setSnapGrid(grid);
+}
+
+void StandardRuler::connectRulerToDocPointer(RosegardenGUIDoc *doc)
+{
+
+ RG_DEBUG << "StandardRuler::connectRulerToDocPointer" << endl;
+
+ // use the document as a hub for pointer and loop set related signals
+ // pointer and loop drag signals are specific to the current view,
+ // so they are re-emitted from the loop ruler by this widget
+ //
+ QObject::connect
+ (m_loopRuler, SIGNAL(setPointerPosition(timeT)),
+ doc, SLOT(slotSetPointerPosition(timeT)));
+
+ QObject::connect
+ (m_hButtonBar, SIGNAL(setPointerPosition(timeT)),
+ doc, SLOT(slotSetPointerPosition(timeT)));
+
+ QObject::connect
+ (m_hButtonBar, SIGNAL(editMarkers()),
+ RosegardenGUIApp::self(), SLOT(slotEditMarkers()));
+
+ QObject::connect
+ (m_hButtonBar, SIGNAL(addMarker(timeT)),
+ RosegardenGUIApp::self(), SLOT(slotAddMarker(timeT)));
+
+ QObject::connect
+ (m_hButtonBar, SIGNAL(deleteMarker(int, timeT, QString, QString)),
+ RosegardenGUIApp::self(), SLOT(slotDeleteMarker(int, timeT, QString, QString)));
+
+ QObject::connect
+ (m_loopRuler, SIGNAL(dragPointerToPosition(timeT)),
+ this, SIGNAL(dragPointerToPosition(timeT)));
+
+ QObject::connect
+ (m_loopRuler, SIGNAL(dragLoopToPosition(timeT)),
+ this, SIGNAL(dragLoopToPosition(timeT)));
+
+ QObject::connect
+ (m_loopRuler, SIGNAL(setPlayPosition(timeT)),
+ RosegardenGUIApp::self(), SLOT(slotSetPlayPosition(timeT)));
+
+ QObject::connect
+ (m_hButtonBar, SIGNAL(setLoop(timeT, timeT)),
+ doc, SLOT(slotSetLoop(timeT, timeT)));
+
+ QObject::connect
+ (m_loopRuler, SIGNAL(setLoop(timeT, timeT)),
+ doc, SLOT(slotSetLoop(timeT, timeT)));
+
+ QObject::connect
+ (doc, SIGNAL(loopChanged(timeT, timeT)),
+ m_loopRuler,
+ SLOT(slotSetLoopMarker(timeT, timeT)));
+
+ m_loopRuler->setBackgroundColor(GUIPalette::getColour(GUIPalette::PointerRuler));
+}
+
+void StandardRuler::slotScrollHoriz(int x)
+{
+ m_loopRuler->scrollHoriz(x);
+ m_hButtonBar->scrollHoriz(x);
+}
+
+void StandardRuler::setMinimumWidth(int width)
+{
+ m_hButtonBar->setMinimumWidth(width);
+ m_loopRuler->setMinimumWidth(width);
+}
+
+void StandardRuler::setHScaleFactor(double dy)
+{
+ m_hButtonBar->setHScaleFactor(dy);
+ m_loopRuler->setHScaleFactor(dy);
+}
+
+void StandardRuler::paintEvent(QPaintEvent *e)
+{
+ m_hButtonBar->update();
+ m_loopRuler->update();
+ QWidget::paintEvent(e);
+}
+
+}
+#include "StandardRuler.moc"
diff --git a/src/gui/rulers/StandardRuler.h b/src/gui/rulers/StandardRuler.h
new file mode 100644
index 0000000..de9804d
--- /dev/null
+++ b/src/gui/rulers/StandardRuler.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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_STANDARDRULER_H_
+#define _RG_STANDARDRULER_H_
+
+#include <qvbox.h>
+#include "base/Event.h"
+
+
+class QWidget;
+class QPaintEvent;
+
+
+namespace Rosegarden
+{
+
+class RulerScale;
+class RosegardenGUIDoc;
+class LoopRuler;
+class MarkerRuler;
+class SnapGrid;
+
+
+class StandardRuler : public QVBox
+{
+ Q_OBJECT
+
+public:
+ StandardRuler(RosegardenGUIDoc *doc,
+ RulerScale *rulerScale,
+ double xorigin,
+ int buttonHeight,
+ bool invert = false, // draw upside-down
+ QWidget* parent = 0,
+ const char* name = 0,
+ WFlags f=0);
+
+ void setSnapGrid(SnapGrid *grid);
+
+ LoopRuler* getLoopRuler() { return m_loopRuler; }
+
+ /**
+ * Make connections from the LoopRuler to the document's
+ * position pointer -- the standard use for a LoopRuler.
+ * If you don't call this, you'll have to connect the
+ * LoopRuler's signals up to something yourself.
+ */
+ void connectRulerToDocPointer(RosegardenGUIDoc *doc);
+
+ void setMinimumWidth(int width);
+
+ void setHScaleFactor(double dy);
+
+public slots:
+ void slotScrollHoriz(int x);
+
+signals:
+ /// reflected from the loop ruler
+ void dragPointerToPosition(timeT);
+
+ /// reflected from the loop ruler
+ void dragLoopToPosition(timeT);
+
+
+protected:
+ virtual void paintEvent(QPaintEvent *);
+
+private:
+ //--------------- Data members ---------------------------------
+ bool m_invert;
+ int m_loopRulerHeight;
+ int m_currentXOffset;
+
+ RosegardenGUIDoc *m_doc;
+ RulerScale *m_rulerScale;
+
+ MarkerRuler *m_hButtonBar;
+ LoopRuler *m_loopRuler;
+};
+
+
+
+}
+
+#endif
diff --git a/src/gui/rulers/TempoColour.cpp b/src/gui/rulers/TempoColour.cpp
new file mode 100644
index 0000000..5ab396a
--- /dev/null
+++ b/src/gui/rulers/TempoColour.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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "TempoColour.h"
+
+#include "gui/general/GUIPalette.h"
+#include <qcolor.h>
+
+
+namespace Rosegarden
+{
+
+QColor
+TempoColour::getColour(double tempo)
+{
+ int h, s, v;
+ QColor c = GUIPalette::getColour(GUIPalette::TempoBase);
+ c.hsv(&h, &s, &v);
+ v += 20;
+ if (v > 255)
+ v = 255;
+
+ h = (90 + int(tempo));
+
+ while (h < 0)
+ h += 360;
+ while (h >= 360)
+ h -= 360;
+
+ return QColor(h, s, v, QColor::Hsv);
+}
+
+}
diff --git a/src/gui/rulers/TempoColour.h b/src/gui/rulers/TempoColour.h
new file mode 100644
index 0000000..be5e3fa
--- /dev/null
+++ b/src/gui/rulers/TempoColour.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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_TEMPOCOLOUR_H_
+#define _RG_TEMPOCOLOUR_H_
+
+#include <qcolor.h>
+
+
+
+
+namespace Rosegarden
+{
+
+
+
+class TempoColour
+{
+
+public:
+ TempoColour():m_tempo(0) {;}
+ TempoColour(double tempo):m_tempo(tempo) {;}
+
+ // Get the colour for a tempo
+ //
+ QColor getColour() { return getColour(m_tempo); }
+ static QColor getColour(double tempo);
+
+private:
+
+ double m_tempo;
+
+};
+
+
+}
+
+#endif
diff --git a/src/gui/rulers/TempoRuler.cpp b/src/gui/rulers/TempoRuler.cpp
new file mode 100644
index 0000000..270b224
--- /dev/null
+++ b/src/gui/rulers/TempoRuler.cpp
@@ -0,0 +1,1091 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
+
+/*
+ Rosegarden
+ A MIDI and audio sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "TempoRuler.h"
+
+#include <klocale.h>
+#include <kstddirs.h>
+#include "misc/Debug.h"
+#include "base/Composition.h"
+#include "base/NotationTypes.h"
+#include "base/RealTime.h"
+#include "base/RulerScale.h"
+#include "base/SnapGrid.h"
+#include "document/RosegardenGUIDoc.h"
+#include "document/MultiViewCommandHistory.h"
+#include "gui/application/RosegardenGUIApp.h"
+#include "gui/dialogs/TempoDialog.h"
+#include "gui/general/GUIPalette.h"
+#include "gui/widgets/TextFloat.h"
+#include "TempoColour.h"
+#include <kaction.h>
+#include <kglobal.h>
+#include <kxmlguiclient.h>
+#include <kxmlguifactory.h>
+#include <qcolor.h>
+#include <qcursor.h>
+#include <qevent.h>
+#include <qfont.h>
+#include <qfontmetrics.h>
+#include <qiconset.h>
+#include <qobject.h>
+#include <qpainter.h>
+#include <qpixmap.h>
+#include <qpoint.h>
+#include <qpopupmenu.h>
+#include <qrect.h>
+#include <qsize.h>
+#include <qstring.h>
+#include <qwidget.h>
+
+
+namespace Rosegarden
+{
+
+TempoRuler::TempoRuler(RulerScale *rulerScale,
+ RosegardenGUIDoc *doc,
+ KMainWindow *parentMainWindow,
+ double xorigin,
+ int height,
+ bool small,
+ QWidget *parent,
+ const char *name) :
+ QWidget(parent, name),
+ m_xorigin(xorigin),
+ m_height(height),
+ m_currentXOffset(0),
+ m_width( -1),
+ m_small(small),
+ m_illuminate( -1),
+ m_illuminatePoint(false),
+ m_illuminateTarget(false),
+ m_refreshLinesOnly(false),
+ m_dragVert(false),
+ m_dragTarget(false),
+ m_dragHoriz(false),
+ m_dragStartY(0),
+ m_dragStartX(0),
+ m_dragFine(false),
+ m_clickX(0),
+ m_dragStartTempo( -1),
+ m_dragStartTarget( -1),
+ m_dragOriginalTempo( -1),
+ m_dragOriginalTarget( -1),
+ m_composition(&doc->getComposition()),
+ m_rulerScale(rulerScale),
+ m_menu(0),
+ m_parentMainWindow(parentMainWindow),
+ m_fontMetrics(m_boldFont)
+{
+ // m_font.setPointSize(m_small ? 9 : 11);
+ // m_boldFont.setPointSize(m_small ? 9 : 11);
+
+ // m_font.setPixelSize(m_height * 2 / 3);
+ // m_boldFont.setPixelSize(m_height * 2 / 3);
+
+ m_font.setPixelSize(m_height / 3);
+ m_boldFont.setPixelSize(m_height * 2 / 5);
+ m_boldFont.setBold(true);
+ m_fontMetrics = QFontMetrics(m_boldFont);
+
+ m_textFloat = new TextFloat(this);
+ m_textFloat->hide();
+
+ // setBackgroundColor(GUIPalette::getColour(GUIPalette::TextRulerBackground));
+ setBackgroundMode(Qt::NoBackground);
+
+ QObject::connect
+ (doc->getCommandHistory(), SIGNAL(commandExecuted()),
+ this, SLOT(update()));
+
+ QString pixmapDir = KGlobal::dirs()->findResource("appdata", "pixmaps/");
+ QIconSet icon;
+
+ icon = QIconSet(QPixmap(pixmapDir + "/toolbar/event-insert-tempo.png"));
+ new KAction(i18n("Insert Tempo Change"), icon, 0, this,
+ SLOT(slotInsertTempoHere()), actionCollection(),
+ "insert_tempo_here");
+
+ new KAction(i18n("Insert Tempo Change at Playback Position"), 0, 0, this,
+ SLOT(slotInsertTempoAtPointer()), actionCollection(),
+ "insert_tempo_at_pointer");
+
+ icon = QIconSet(QPixmap(pixmapDir + "/toolbar/event-delete.png"));
+ new KAction(i18n("Delete Tempo Change"), icon, 0, this,
+ SLOT(slotDeleteTempoChange()), actionCollection(),
+ "delete_tempo");
+
+ new KAction(i18n("Ramp Tempo to Next Tempo"), 0, 0, this,
+ SLOT(slotRampToNext()), actionCollection(),
+ "ramp_to_next");
+
+ new KAction(i18n("Un-Ramp Tempo"), 0, 0, this,
+ SLOT(slotUnramp()), actionCollection(),
+ "unramp");
+
+ icon = QIconSet(QPixmap(pixmapDir + "/toolbar/event-edit.png"));
+ new KAction(i18n("Edit Tempo..."), icon, 0, this,
+ SLOT(slotEditTempo()), actionCollection(),
+ "edit_tempo");
+
+ new KAction(i18n("Edit Time Signature..."), 0, 0, this,
+ SLOT(slotEditTimeSignature()), actionCollection(),
+ "edit_time_signature");
+
+ new KAction(i18n("Open Tempo and Time Signature Editor"), 0, 0, this,
+ SLOT(slotEditTempos()), actionCollection(),
+ "edit_tempos");
+
+ setMouseTracking(false);
+}
+
+TempoRuler::~TempoRuler()
+{
+ // we have to do this so that the menu is re-created properly
+ // when the main window is itself recreated (on a File->New for instance)
+ KXMLGUIFactory* factory = m_parentMainWindow->factory();
+ if (factory)
+ factory->removeClient(this);
+}
+
+void
+TempoRuler::connectSignals()
+{
+ connect(this,
+ SIGNAL(doubleClicked(timeT)),
+ RosegardenGUIApp::self(),
+ SLOT(slotEditTempos(timeT)));
+
+ connect(this,
+ SIGNAL(changeTempo(timeT,
+ tempoT,
+ tempoT,
+ TempoDialog::TempoDialogAction)),
+ RosegardenGUIApp::self(),
+ SLOT(slotChangeTempo(timeT,
+ tempoT,
+ tempoT,
+ TempoDialog::TempoDialogAction)));
+
+ connect(this,
+ SIGNAL(moveTempo(timeT,
+ timeT)),
+ RosegardenGUIApp::self(),
+ SLOT(slotMoveTempo(timeT,
+ timeT)));
+
+ connect(this,
+ SIGNAL(deleteTempo(timeT)),
+ RosegardenGUIApp::self(),
+ SLOT(slotDeleteTempo(timeT)));
+
+ connect(this,
+ SIGNAL(editTempo(timeT)),
+ RosegardenGUIApp::self(),
+ SLOT(slotEditTempo(timeT)));
+
+ connect(this,
+ SIGNAL(editTimeSignature(timeT)),
+ RosegardenGUIApp::self(),
+ SLOT(slotEditTimeSignature(timeT)));
+
+ connect(this,
+ SIGNAL(editTempos(timeT)),
+ RosegardenGUIApp::self(),
+ SLOT(slotEditTempos(timeT)));
+}
+
+void
+TempoRuler::slotScrollHoriz(int x)
+{
+ int w = width(), h = height();
+ int dx = x - ( -m_currentXOffset);
+ m_currentXOffset = -x;
+
+ if (dx > w*3 / 4 || dx < -w*3 / 4) {
+ 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);
+ }
+}
+
+void
+TempoRuler::mousePressEvent(QMouseEvent *e)
+{
+ if (e->button() == LeftButton) {
+
+ if (e->type() == QEvent::MouseButtonDblClick) {
+ timeT t = m_rulerScale->getTimeForX
+ (e->x() - m_currentXOffset - m_xorigin);
+ emit doubleClicked(t);
+ return ;
+ }
+
+ int x = e->x() + 1;
+ int y = e->y();
+ timeT t = m_rulerScale->getTimeForX(x - m_currentXOffset - m_xorigin);
+ int tcn = m_composition->getTempoChangeNumberAt(t);
+
+ if (tcn < 0 || tcn >= m_composition->getTempoChangeCount())
+ return ;
+
+ std::pair<timeT, tempoT> tc = m_composition->getTempoChange(tcn);
+ std::pair<bool, tempoT> tr = m_composition->getTempoRamping(tcn, true);
+
+ m_dragStartY = y;
+ m_dragStartX = x;
+ m_dragStartTime = tc.first;
+ m_dragPreviousTime = m_dragStartTime;
+ m_dragStartTempo = tc.second;
+ m_dragStartTarget = tr.first ? tr.second : -1;
+ m_dragOriginalTempo = m_dragStartTempo;
+ m_dragOriginalTarget = m_dragStartTarget;
+ m_dragFine = ((e->state() & Qt::ShiftButton) != 0);
+
+ int px = m_rulerScale->getXForTime(tc.first) + m_currentXOffset + m_xorigin;
+ if (x >= px && x < px + 5) {
+ m_dragHoriz = true;
+ m_dragVert = false;
+ setCursor(Qt::SplitHCursor);
+ } else {
+ timeT nt = m_composition->getEndMarker();
+ if (tcn < m_composition->getTempoChangeCount() - 1) {
+ nt = m_composition->getTempoChange(tcn + 1).first;
+ }
+ int nx = m_rulerScale->getXForTime(nt) + m_currentXOffset + m_xorigin;
+ if (x > px + 5 && x > nx - 5) {
+ m_dragTarget = true;
+ setCursor(Qt::SizeVerCursor);
+ } else {
+ m_dragTarget = false;
+ setCursor(Qt::SplitVCursor);
+ }
+ m_dragVert = true;
+ m_dragHoriz = false;
+ }
+
+ } else if (e->button() == RightButton) {
+
+ m_clickX = e->x();
+ if (!m_menu)
+ createMenu();
+ if (m_menu) {
+ // enable 'delete' action only if cursor is actually over a tempo change
+ actionCollection()->action("delete_tempo")->setEnabled(m_illuminatePoint);
+ m_menu->exec(QCursor::pos());
+ }
+
+ }
+}
+
+void
+TempoRuler::mouseReleaseEvent(QMouseEvent *e)
+{
+ if (m_dragVert) {
+
+ m_dragVert = false;
+ unsetCursor();
+
+ if (e->x() < 0 || e->x() >= width() ||
+ e->y() < 0 || e->y() >= height()) {
+ leaveEvent(0);
+ }
+
+ // First we make a note of the values that we just set and
+ // restore the tempo to whatever it was previously, so that
+ // the undo for any following command will work correctly.
+ // Then we emit so that our user can issue the right command.
+
+ int tcn = m_composition->getTempoChangeNumberAt(m_dragStartTime);
+ std::pair<timeT, tempoT> tc = m_composition->getTempoChange(tcn);
+ std::pair<bool, tempoT> tr = m_composition->getTempoRamping(tcn, true);
+
+ if (tc.second != m_dragOriginalTempo) {
+ m_composition->addTempoAtTime(m_dragStartTime,
+ m_dragOriginalTempo,
+ m_dragOriginalTarget);
+ emit changeTempo(m_dragStartTime, tc.second,
+ tr.first ? tr.second : -1,
+ TempoDialog::AddTempo);
+ }
+
+ return ;
+
+ } else if (m_dragHoriz) {
+
+ m_dragHoriz = false;
+ unsetCursor();
+
+ if (e->x() < 0 || e->x() >= width() ||
+ e->y() < 0 || e->y() >= height()) {
+ leaveEvent(0);
+ }
+
+ if (m_dragPreviousTime != m_dragStartTime) {
+
+ // As above, restore the original tempo and then emit a
+ // signal to ensure a proper command happens.
+
+ int tcn = m_composition->getTempoChangeNumberAt(m_dragPreviousTime);
+ m_composition->removeTempoChange(tcn);
+ m_composition->addTempoAtTime(m_dragStartTime,
+ m_dragStartTempo,
+ m_dragStartTarget);
+
+ emit moveTempo(m_dragStartTime, m_dragPreviousTime);
+ }
+
+ return ;
+ }
+}
+
+void
+TempoRuler::mouseMoveEvent(QMouseEvent *e)
+{
+ bool shiftPressed = ((e->state() & Qt::ShiftButton) != 0);
+
+ if (m_dragVert) {
+
+ if (shiftPressed != m_dragFine) {
+
+ m_dragFine = shiftPressed;
+ m_dragStartY = e->y();
+
+ // reset the start tempi to whatever we last updated them
+ // to as we switch into or out of fine mode
+ int tcn = m_composition->getTempoChangeNumberAt(m_dragStartTime);
+ std::pair<timeT, tempoT> tc = m_composition->getTempoChange(tcn);
+ std::pair<bool, tempoT> tr = m_composition->getTempoRamping(tcn, true);
+ m_dragStartTempo = tc.second;
+ m_dragStartTarget = tr.first ? tr.second : -1;
+ }
+
+ int diff = m_dragStartY - e->y(); // +ve for upwards drag
+ tempoT newTempo = m_dragStartTempo;
+ tempoT newTarget = m_dragStartTarget;
+
+ if (diff != 0) {
+
+ float qpm = m_composition->getTempoQpm(newTempo);
+
+ if (m_dragTarget && newTarget > 0) {
+ qpm = m_composition->getTempoQpm(newTarget);
+ }
+
+ float qdiff = (m_dragFine ? diff * 0.05 : diff * 0.5);
+ qpm += qdiff;
+ if (qpm < 1)
+ qpm = 1;
+
+ if (m_dragTarget) {
+
+ newTarget = m_composition->getTempoForQpm(qpm);
+
+ } else {
+
+ newTempo = m_composition->getTempoForQpm(qpm);
+
+ if (newTarget >= 0) {
+ qpm = m_composition->getTempoQpm(newTarget);
+ qpm += qdiff;
+ if (qpm < 1)
+ qpm = 1;
+ newTarget = m_composition->getTempoForQpm(qpm);
+ }
+ }
+ }
+
+ showTextFloat(newTempo, newTarget, m_dragStartTime);
+ m_composition->addTempoAtTime(m_dragStartTime, newTempo, newTarget);
+ update();
+
+ } else if (m_dragHoriz) {
+
+ int x = e->x();
+
+ SnapGrid grid(m_rulerScale);
+ if (shiftPressed) {
+ grid.setSnapTime(SnapGrid::NoSnap);
+ } else {
+ grid.setSnapTime(SnapGrid::SnapToUnit);
+ }
+ timeT newTime = grid.snapX(x - m_currentXOffset - m_xorigin,
+ SnapGrid::SnapEither);
+
+ int tcn = m_composition->getTempoChangeNumberAt(m_dragPreviousTime);
+ int ncn = m_composition->getTempoChangeNumberAt(newTime);
+ if (ncn > tcn || ncn < tcn - 1)
+ return ;
+ if (ncn >= 0 && ncn == tcn - 1) {
+ std::pair<timeT, tempoT> nc = m_composition->getTempoChange(ncn);
+ if (nc.first == newTime)
+ return ;
+ }
+
+ // std::cerr << " -> " << newTime << std::endl;
+
+ m_composition->removeTempoChange(tcn);
+ m_composition->addTempoAtTime(newTime,
+ m_dragStartTempo,
+ m_dragStartTarget);
+ showTextFloat(m_dragStartTempo, m_dragStartTarget, newTime, true);
+ m_dragPreviousTime = newTime;
+ update();
+
+ } else {
+
+ int x = e->x() + 1;
+ timeT t = m_rulerScale->getTimeForX(x - m_currentXOffset - m_xorigin);
+ int tcn = m_composition->getTempoChangeNumberAt(t);
+
+ if (tcn < 0 || tcn >= m_composition->getTempoChangeCount())
+ return ;
+
+ std::pair<timeT, tempoT> tc = m_composition->getTempoChange(tcn);
+ std::pair<bool, tempoT> tr = m_composition->getTempoRamping(tcn, true);
+
+ int bar, beat, fraction, remainder;
+ m_composition->getMusicalTimeForAbsoluteTime(tc.first, bar, beat,
+ fraction, remainder);
+ RG_DEBUG << "Tempo change: tempo " << m_composition->getTempoQpm(tc.second) << " at " << bar << ":" << beat << ":" << fraction << ":" << remainder << endl;
+
+ m_illuminate = tcn;
+ m_illuminatePoint = false;
+ m_illuminateTarget = false;
+ //!!! m_refreshLinesOnly = true;
+
+ //!!! merge this test with the one in mousePressEvent as
+ //isCloseToStart or equiv, and likewise for close to end
+
+ int px = m_rulerScale->getXForTime(tc.first) + m_currentXOffset + m_xorigin;
+ if (x >= px && x < px + 5) {
+ m_illuminatePoint = true;
+ } else {
+ timeT nt = m_composition->getEndMarker();
+ if (tcn < m_composition->getTempoChangeCount() - 1) {
+ nt = m_composition->getTempoChange(tcn + 1).first;
+ }
+ int nx = m_rulerScale->getXForTime(nt) + m_currentXOffset + m_xorigin;
+ if (x > px + 5 && x > nx - 5) {
+ m_illuminateTarget = true;
+ }
+
+ // std::cerr << "nt = " << nt << ", nx = " << nx << ", x = " << x << ", m_illuminateTarget = " << m_illuminateTarget << std::endl;
+ }
+
+ showTextFloat(tc.second, tr.first ? tr.second : -1,
+ tc.first, m_illuminatePoint);
+
+ update();
+ }
+}
+
+void
+TempoRuler::wheelEvent(QWheelEvent *e)
+{}
+
+void
+TempoRuler::enterEvent(QEvent *)
+{
+ setMouseTracking(true);
+}
+
+void
+TempoRuler::leaveEvent(QEvent *)
+{
+ if (!m_dragVert && !m_dragHoriz) {
+ setMouseTracking(false);
+ m_illuminate = -1;
+ m_illuminatePoint = false;
+ //!!! m_refreshLinesOnly = true;
+ m_textFloat->hide();
+ update();
+ }
+}
+
+void
+TempoRuler::showTextFloat(tempoT tempo, tempoT target,
+ timeT time, bool showTime)
+{
+ float qpm = m_composition->getTempoQpm(tempo);
+ int qi = int(qpm + 0.0001);
+ int q0 = int(qpm * 10 + 0.0001) % 10;
+ int q00 = int(qpm * 100 + 0.0001) % 10;
+
+ bool haveSet = false;
+
+ QString tempoText, timeText;
+
+ if (time >= 0) {
+
+ if (showTime) {
+ int bar, beat, fraction, remainder;
+ m_composition->getMusicalTimeForAbsoluteTime
+ (time, bar, beat, fraction, remainder);
+ RealTime rt = m_composition->getElapsedRealTime(time);
+
+ // blargh -- duplicated with TempoView::makeTimeString
+ timeText = 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);
+
+ timeText = QString("%1\n%2")
+ .arg(timeText)
+ // .arg(rt.toString().c_str());
+ .arg(rt.toText(true).c_str());
+ }
+
+ TimeSignature sig =
+ m_composition->getTimeSignatureAt(time);
+
+ if (sig.getBeatDuration() !=
+ Note(Note::Crotchet).getDuration()) {
+
+ float bpm =
+ (qpm *
+ Note(Note::Crotchet).getDuration())
+ / sig.getBeatDuration();
+ int bi = int(bpm + 0.0001);
+ int b0 = int(bpm * 10 + 0.0001) % 10;
+ int b00 = int(bpm * 100 + 0.0001) % 10;
+
+ tempoText = i18n("%1.%2%3 (%4.%5%6 bpm)")
+ .arg(qi).arg(q0).arg(q00)
+ .arg(bi).arg(b0).arg(b00);
+ haveSet = true;
+ }
+ }
+
+ if (!haveSet) {
+ tempoText = i18n("%1.%2%3 bpm").arg(qi).arg(q0).arg(q00);
+ }
+
+ if (target > 0 && target != tempo) {
+ float tq = m_composition->getTempoQpm(target);
+ int tqi = int(tq + 0.0001);
+ int tq0 = int(tq * 10 + 0.0001) % 10;
+ int tq00 = int(tq * 100 + 0.0001) % 10;
+ tempoText = i18n("%1 - %2.%3%4").arg(tempoText).arg(tqi).arg(tq0).arg(tq00);
+ }
+
+ if (showTime && time >= 0) {
+ m_textFloat->setText(QString("%1\n%2").arg(timeText).arg(tempoText));
+ } else {
+ m_textFloat->setText(tempoText);
+ }
+
+ QPoint cp = mapFromGlobal(QPoint(QCursor::pos()));
+ // std::cerr << "cp = " << cp.x() << "," << cp.y() << ", tempo = " << qpm << std::endl;
+ QPoint mp = cp + pos();
+
+ QWidget *parent = parentWidget();
+ while (parent->parentWidget() &&
+ !parent->isTopLevel() &&
+ !parent->isDialog()) {
+ mp += parent->pos();
+ parent = parent->parentWidget();
+ }
+
+ int yoff = cp.y() + m_textFloat->height() + 3;
+ mp = QPoint(mp.x() + 10, mp.y() > yoff ? mp.y() - yoff : 0);
+
+ m_textFloat->move(mp);
+ m_textFloat->show();
+}
+
+QSize
+TempoRuler::sizeHint() const
+{
+ double width =
+ m_rulerScale->getBarPosition(m_rulerScale->getLastVisibleBar()) +
+ m_rulerScale->getBarWidth(m_rulerScale->getLastVisibleBar()) +
+ m_xorigin;
+
+ QSize res(std::max(int(width), m_width), m_height);
+
+ return res;
+}
+
+QSize
+TempoRuler::minimumSizeHint() const
+{
+ double firstBarWidth = m_rulerScale->getBarWidth(0) + m_xorigin;
+ QSize res = QSize(int(firstBarWidth), m_height);
+ return res;
+}
+
+int
+TempoRuler::getYForTempo(tempoT tempo)
+{
+ int drawh = height() - 4;
+ int y = drawh / 2;
+
+ tempoT minTempo = m_composition->getMinTempo();
+ tempoT maxTempo = m_composition->getMaxTempo();
+
+ if (maxTempo > minTempo) {
+ y = drawh -
+ int((double(tempo - minTempo) / double(maxTempo - minTempo))
+ * drawh + 0.5);
+ }
+
+ return y;
+}
+
+tempoT
+TempoRuler::getTempoForY(int y)
+{
+ int drawh = height() - 4;
+
+ tempoT minTempo = m_composition->getMinTempo();
+ tempoT maxTempo = m_composition->getMaxTempo();
+
+ tempoT tempo = minTempo;
+
+ if (maxTempo > minTempo) {
+ tempo = (maxTempo - minTempo) *
+ (double(drawh - y) / double(drawh)) + minTempo + 0.5;
+ }
+
+ return tempo;
+}
+
+void
+TempoRuler::paintEvent(QPaintEvent* e)
+{
+ QRect clipRect = e->rect();
+
+ if (m_buffer.width() < width() || m_buffer.height() < height()) {
+ m_buffer = QPixmap(width(), height());
+ }
+
+ m_buffer.fill(GUIPalette::getColour
+ (GUIPalette::TextRulerBackground));
+
+ QPainter paint(&m_buffer);
+ paint.setPen(GUIPalette::getColour
+ (GUIPalette::TextRulerForeground));
+
+ paint.setClipRegion(e->region());
+ paint.setClipRect(clipRect);
+
+ if (m_xorigin > 0) {
+ paint.fillRect(0, 0, m_xorigin, height(), paletteBackgroundColor());
+ }
+
+ timeT from = m_rulerScale->getTimeForX
+ (clipRect.x() - m_currentXOffset - 100 - m_xorigin);
+ timeT to = m_rulerScale->getTimeForX
+ (clipRect.x() + clipRect.width() - m_currentXOffset + 100 - m_xorigin);
+
+ QRect boundsForHeight = m_fontMetrics.boundingRect("019");
+ int fontHeight = boundsForHeight.height();
+ int textY = fontHeight + 2;
+
+ double prevEndX = -1000.0;
+ double prevTempo = 0.0;
+ long prevBpm = 0;
+
+ typedef std::map<timeT, int> TimePoints;
+ int tempoChangeHere = 1;
+ int timeSigChangeHere = 2;
+ TimePoints timePoints;
+
+ for (int tempoNo = m_composition->getTempoChangeNumberAt(from);
+ tempoNo <= m_composition->getTempoChangeNumberAt(to) + 1; ++tempoNo) {
+
+ if (tempoNo >= 0 && tempoNo < m_composition->getTempoChangeCount()) {
+ timePoints.insert
+ (TimePoints::value_type
+ (m_composition->getTempoChange(tempoNo).first,
+ tempoChangeHere));
+ }
+ }
+
+ for (int sigNo = m_composition->getTimeSignatureNumberAt(from);
+ sigNo <= m_composition->getTimeSignatureNumberAt(to) + 1; ++sigNo) {
+
+ if (sigNo >= 0 && sigNo < m_composition->getTimeSignatureCount()) {
+ timeT time(m_composition->getTimeSignatureChange(sigNo).first);
+ if (timePoints.find(time) != timePoints.end()) {
+ timePoints[time] |= timeSigChangeHere;
+ } else {
+ timePoints.insert(TimePoints::value_type(time, timeSigChangeHere));
+ }
+ }
+ }
+
+ int lastx = 0, lasty = 0, lastx1 = 0;
+ bool haveSome = false;
+ // tempoT minTempo = m_composition->getMinTempo();
+ // tempoT maxTempo = m_composition->getMaxTempo();
+ bool illuminate = false;
+
+ if (m_illuminate >= 0) {
+ int tcn = m_composition->getTempoChangeNumberAt(from);
+ illuminate = (m_illuminate == tcn);
+ }
+
+ for (TimePoints::iterator i = timePoints.begin(); ; ++i) {
+
+ timeT t0, t1;
+
+ if (i == timePoints.begin()) {
+ t0 = from;
+ } else {
+ TimePoints::iterator j(i);
+ --j;
+ t0 = j->first;
+ }
+
+ if (i == timePoints.end()) {
+ t1 = to;
+ } else {
+ t1 = i->first;
+ }
+
+ if (t1 <= t0)
+ t1 = to;
+
+ int tcn = m_composition->getTempoChangeNumberAt(t0);
+ tempoT tempo = m_composition->getTempoAtTime(t0);
+
+ std::pair<bool, tempoT> ramping(false, tempo);
+ if (tcn > 0 && tcn < m_composition->getTempoChangeCount() + 1) {
+ ramping = m_composition->getTempoRamping(tcn - 1, true);
+ }
+
+ double x0, x1;
+ x0 = m_rulerScale->getXForTime(t0) + m_currentXOffset + m_xorigin;
+ x1 = m_rulerScale->getXForTime(t1) + m_currentXOffset + m_xorigin;
+ /*!!!
+ if (x0 > e->rect().x()) {
+ paint.fillRect(e->rect().x(), 0, x0 - e->rect().x(), height(),
+ paletteBackgroundColor());
+ }
+ */
+ QColor colour = TempoColour::getColour(m_composition->getTempoQpm(tempo));
+ paint.setPen(colour);
+ paint.setBrush(colour);
+
+ if (!m_refreshLinesOnly) {
+ // RG_DEBUG << "TempoRuler: draw rect from " << x0 << " to " << x1 << endl;
+ paint.drawRect(int(x0), 0, int(x1 - x0) + 1, height());
+ }
+
+ int y = getYForTempo(tempo);
+ /*!!!
+ int drawh = height() - 4;
+ int y = drawh / 2;
+ if (maxTempo > minTempo) {
+ y = drawh -
+ int((double(tempo - minTempo) / double(maxTempo - minTempo))
+ * drawh + 0.5);
+ }
+ */
+ y += 2;
+
+ if (haveSome) {
+
+ int x = int(x0) + 1;
+ int ry = lasty;
+
+ bool illuminateLine = (illuminate &&
+ !m_illuminatePoint && !m_illuminateTarget);
+
+ paint.setPen(illuminateLine ? Qt::white : Qt::black);
+
+ if (ramping.first) {
+ ry = getYForTempo(ramping.second);
+ ry += 2;
+ /*!!!
+ ry = drawh -
+ int((double(ramping.second - minTempo) /
+ double(maxTempo - minTempo))
+ * drawh + 0.5);
+ */
+ }
+
+ paint.drawLine(lastx + 1, lasty, x - 2, ry);
+
+ if (!illuminateLine && illuminate && m_illuminateTarget) {
+ if (x > lastx) {
+ paint.setPen(Qt::white);
+ paint.drawLine(x - 6, ry - ((ry - lasty) * 6) / (x - lastx),
+ x - 2, ry);
+ }
+ }
+
+ if (m_illuminate >= 0) {
+ illuminate = (m_illuminate == tcn);
+ }
+
+ bool illuminatePoint = (illuminate && m_illuminatePoint);
+
+ paint.setPen(illuminatePoint ? Qt::white : Qt::black);
+ paint.drawRect(x - 1, y - 1, 3, 3);
+
+ paint.setPen(illuminatePoint ? Qt::black : Qt::white);
+ paint.drawPoint(x, y);
+ }
+
+ lastx = int(x0) + 1;
+ lastx1 = int(x1) + 1;
+ lasty = y;
+ if (i == timePoints.end())
+ break;
+ haveSome = true;
+ }
+
+ if (lastx1 < e->rect().x() + e->rect().width()) {
+ /*!!!
+ paint.fillRect(lastx1, 0,
+ e->rect().x() + e->rect().width() - lastx1, height(),
+ paletteBackgroundColor());
+ */
+ }
+
+ if (haveSome) {
+ bool illuminateLine = (illuminate && !m_illuminatePoint);
+ paint.setPen(illuminateLine ? Qt::white : Qt::black);
+ paint.drawLine(lastx + 1, lasty, width(), lasty);
+ } else if (!m_refreshLinesOnly) {
+ tempoT tempo = m_composition->getTempoAtTime(from);
+ QColor colour = TempoColour::getColour(m_composition->getTempoQpm(tempo));
+ paint.setPen(colour);
+ paint.setBrush(colour);
+ paint.drawRect(e->rect());
+ }
+
+ paint.setPen(Qt::black);
+ paint.setBrush(Qt::black);
+ paint.drawLine(0, 0, width(), 0);
+
+ for (TimePoints::iterator i = timePoints.begin();
+ i != timePoints.end(); ++i) {
+
+ timeT time = i->first;
+ double x = m_rulerScale->getXForTime(time) + m_currentXOffset
+ + m_xorigin;
+
+ /*
+ paint.drawLine(static_cast<int>(x),
+ height() - (height()/4),
+ static_cast<int>(x),
+ height());
+ */
+
+ if ((i->second & timeSigChangeHere) && !m_refreshLinesOnly) {
+
+ TimeSignature sig =
+ m_composition->getTimeSignatureAt(time);
+
+ QString str = QString("%1/%2")
+ .arg(sig.getNumerator())
+ .arg(sig.getDenominator());
+
+ paint.setFont(m_boldFont);
+ paint.drawText(static_cast<int>(x) + 2, m_height - 2, str);
+ }
+
+ if ((i->second & tempoChangeHere) && !m_refreshLinesOnly) {
+
+ double tempo = m_composition->getTempoQpm(m_composition->getTempoAtTime(time));
+ long bpm = long(tempo);
+ // long frac = long(tempo * 100 + 0.001) - 100 * bpm;
+
+ QString tempoString = QString("%1").arg(bpm);
+
+ if (tempo == prevTempo) {
+ if (m_small)
+ continue;
+ tempoString = "=";
+ } else if (bpm == prevBpm) {
+ tempoString = (tempo > prevTempo ? "+" : "-");
+ } else {
+ if (m_small && (bpm != (bpm / 10 * 10))) {
+ if (bpm == prevBpm + 1)
+ tempoString = "+";
+ else if (bpm == prevBpm - 1)
+ tempoString = "-";
+ }
+ }
+ prevTempo = tempo;
+ prevBpm = bpm;
+
+ QRect bounds = m_fontMetrics.boundingRect(tempoString);
+
+ paint.setFont(m_font);
+ if (time > 0)
+ x -= bounds.width() / 2;
+ // if (x > bounds.width() / 2) x -= bounds.width() / 2;
+ if (prevEndX >= x - 3)
+ x = prevEndX + 3;
+ paint.drawText(static_cast<int>(x), textY, tempoString);
+ prevEndX = x + bounds.width();
+ }
+ }
+
+ paint.end();
+
+ QPainter dbpaint(this);
+ // dbpaint.drawPixmap(0, 0, m_buffer);
+ dbpaint.drawPixmap(clipRect.x(), clipRect.y(),
+ m_buffer,
+ clipRect.x(), clipRect.y(),
+ clipRect.width(), clipRect.height());
+
+ dbpaint.end();
+
+ m_refreshLinesOnly = false;
+}
+
+void
+TempoRuler::slotInsertTempoHere()
+{
+ SnapGrid grid(m_rulerScale);
+ grid.setSnapTime(SnapGrid::SnapToUnit);
+ timeT t = grid.snapX(m_clickX - m_currentXOffset - m_xorigin,
+ SnapGrid::SnapLeft);
+ tempoT tempo = Composition::getTempoForQpm(120.0);
+
+ int tcn = m_composition->getTempoChangeNumberAt(t);
+ if (tcn >= 0 && tcn < m_composition->getTempoChangeCount()) {
+ std::pair<timeT, tempoT> tc = m_composition->getTempoChange(tcn);
+ if (tc.first == t)
+ return ;
+ tempo = tc.second;
+ }
+
+ emit changeTempo(t, tempo, -1, TempoDialog::AddTempo);
+}
+
+void
+TempoRuler::slotInsertTempoAtPointer()
+{
+ timeT t = m_composition->getPosition();
+ tempoT tempo = Composition::getTempoForQpm(120.0);
+
+ int tcn = m_composition->getTempoChangeNumberAt(t);
+ if (tcn >= 0 && tcn < m_composition->getTempoChangeCount()) {
+ std::pair<timeT, tempoT> tc = m_composition->getTempoChange(tcn);
+ if (tc.first == t)
+ return ;
+ tempo = tc.second;
+ }
+
+ emit changeTempo(t, tempo, -1, TempoDialog::AddTempo);
+}
+
+void
+TempoRuler::slotDeleteTempoChange()
+{
+ timeT t = m_rulerScale->getTimeForX(m_clickX - m_currentXOffset - m_xorigin);
+ emit deleteTempo(t);
+}
+
+void
+TempoRuler::slotRampToNext()
+{
+ timeT t = m_rulerScale->getTimeForX(m_clickX - m_currentXOffset - m_xorigin);
+
+ int tcn = m_composition->getTempoChangeNumberAt(t);
+ if (tcn < 0 || tcn >= m_composition->getTempoChangeCount())
+ return ;
+
+ std::pair<timeT, tempoT> tc = m_composition->getTempoChange(tcn);
+
+ emit changeTempo(tc.first, tc.second, 0, TempoDialog::AddTempo);
+}
+
+void
+TempoRuler::slotUnramp()
+{
+ timeT t = m_rulerScale->getTimeForX(m_clickX - m_currentXOffset - m_xorigin);
+
+ int tcn = m_composition->getTempoChangeNumberAt(t);
+ if (tcn < 0 || tcn >= m_composition->getTempoChangeCount())
+ return ;
+
+ std::pair<timeT, tempoT> tc = m_composition->getTempoChange(tcn);
+
+ emit changeTempo(tc.first, tc.second, -1, TempoDialog::AddTempo);
+}
+
+void
+TempoRuler::slotEditTempo()
+{
+ timeT t = m_rulerScale->getTimeForX(m_clickX - m_currentXOffset - m_xorigin);
+ emit editTempo(t);
+}
+
+void
+TempoRuler::slotEditTimeSignature()
+{
+ timeT t = m_rulerScale->getTimeForX(m_clickX - m_currentXOffset - m_xorigin);
+ emit editTimeSignature(t);
+}
+
+void
+TempoRuler::slotEditTempos()
+{
+ timeT t = m_rulerScale->getTimeForX(m_clickX - m_currentXOffset - m_xorigin);
+ emit editTempos(t);
+}
+
+void
+TempoRuler::createMenu()
+{
+ setXMLFile("temporuler.rc");
+
+ KXMLGUIFactory* factory = m_parentMainWindow->factory();
+ factory->addClient(this);
+
+ QWidget* tmp = factory->container("tempo_ruler_menu", this);
+
+ m_menu = dynamic_cast<QPopupMenu*>(tmp);
+
+ if (!m_menu) {
+ RG_DEBUG << "MarkerRuler::createMenu() failed\n";
+ }
+}
+
+
+}
+#include "TempoRuler.moc"
diff --git a/src/gui/rulers/TempoRuler.h b/src/gui/rulers/TempoRuler.h
new file mode 100644
index 0000000..1d54e9d
--- /dev/null
+++ b/src/gui/rulers/TempoRuler.h
@@ -0,0 +1,180 @@
+
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
+
+/*
+ Rosegarden
+ A MIDI and audio sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_TEMPORULER_H_
+#define _RG_TEMPORULER_H_
+
+#include "gui/dialogs/TempoDialog.h"
+#include <kxmlguiclient.h>
+#include <qfont.h>
+#include <qfontmetrics.h>
+#include <qpixmap.h>
+#include <qsize.h>
+#include <qwidget.h>
+#include "base/Event.h"
+
+
+class QWheelEvent;
+class QPopupMenu;
+class QPaintEvent;
+class QMouseEvent;
+class QEvent;
+class KMainWindow;
+
+
+namespace Rosegarden
+{
+
+class TextFloat;
+class RulerScale;
+class RosegardenGUIDoc;
+class Composition;
+
+
+/**
+ * TempoRuler is a widget that shows a strip of tempo values at
+ * x-coordinates corresponding to tempo changes in a Composition.
+ */
+
+class TempoRuler : public QWidget, public KXMLGUIClient
+{
+ Q_OBJECT
+
+public:
+ /**
+ * Construct a TempoRuler that displays and allows editing of the
+ * tempo changes found in the given Composition, with positions
+ * calculated by the given RulerScale.
+ *
+ * The RulerScale will not be destroyed along with the TempoRuler.
+ */
+ TempoRuler(RulerScale *rulerScale,
+ RosegardenGUIDoc *doc,
+ KMainWindow *parentMainWindow,
+ double xorigin = 0.0,
+ int height = 0,
+ bool small = false,
+ QWidget* parent = 0,
+ const char *name = 0);
+
+ ~TempoRuler();
+
+ virtual QSize sizeHint() const;
+ virtual QSize minimumSizeHint() const;
+
+ void setMinimumWidth(int width) { m_width = width; }
+
+ void connectSignals();
+
+signals:
+ void doubleClicked(timeT);
+
+ void changeTempo(timeT, // tempo change time
+ tempoT, // tempo value
+ tempoT, // tempo target
+ TempoDialog::TempoDialogAction); // tempo action
+
+ void moveTempo(timeT, // old time
+ timeT); // new time
+
+ void deleteTempo(timeT);
+
+ void editTempo(timeT);
+ void editTimeSignature(timeT);
+ void editTempos(timeT);
+
+public slots:
+ void slotScrollHoriz(int x);
+
+protected slots:
+ void slotInsertTempoHere();
+ void slotInsertTempoAtPointer();
+ void slotDeleteTempoChange();
+ void slotRampToNext();
+ void slotUnramp();
+ void slotEditTempo();
+ void slotEditTimeSignature();
+ void slotEditTempos();
+
+protected:
+ virtual void paintEvent(QPaintEvent *);
+ virtual void enterEvent(QEvent *);
+ virtual void leaveEvent(QEvent *);
+ virtual void mousePressEvent(QMouseEvent *);
+ virtual void mouseReleaseEvent(QMouseEvent *);
+ virtual void mouseMoveEvent(QMouseEvent *);
+ virtual void wheelEvent(QWheelEvent *);
+
+ void createMenu();
+
+private:
+ double m_xorigin;
+ int m_height;
+ int m_currentXOffset;
+ int m_width;
+ bool m_small;
+ int m_illuminate;
+ bool m_illuminatePoint;
+ bool m_illuminateTarget;
+ bool m_refreshLinesOnly;
+
+ bool m_dragVert;
+ bool m_dragTarget;
+ bool m_dragHoriz;
+ int m_dragStartY;
+ int m_dragStartX;
+ bool m_dragFine;
+ int m_clickX;
+
+ timeT m_dragStartTime;
+ timeT m_dragPreviousTime;
+ tempoT m_dragStartTempo;
+ tempoT m_dragStartTarget;
+ tempoT m_dragOriginalTempo;
+ tempoT m_dragOriginalTarget;
+
+ int getYForTempo(tempoT tempo);
+ tempoT getTempoForY(int y);
+ void showTextFloat(tempoT tempo,
+ tempoT target = -1,
+ timeT time = -1,
+ bool showTime = false);
+
+ Composition *m_composition;
+ RulerScale *m_rulerScale;
+ TextFloat *m_textFloat;
+ QPopupMenu *m_menu;
+ KMainWindow *m_parentMainWindow;
+
+ QFont m_font;
+ QFont m_boldFont;
+ QFontMetrics m_fontMetrics;
+ QPixmap m_buffer;
+};
+
+
+}
+
+#endif
diff --git a/src/gui/rulers/TextRuler.cpp b/src/gui/rulers/TextRuler.cpp
new file mode 100644
index 0000000..0acb3ea
--- /dev/null
+++ b/src/gui/rulers/TextRuler.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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "TextRuler.h"
+
+#include "base/Event.h"
+#include "misc/Debug.h"
+#include "misc/Strings.h"
+#include "base/NotationTypes.h"
+#include "base/RulerScale.h"
+#include "base/Segment.h"
+#include "gui/general/GUIPalette.h"
+#include <qfont.h>
+#include <qfontmetrics.h>
+#include <qpainter.h>
+#include <qrect.h>
+#include <qsize.h>
+#include <qwidget.h>
+
+
+namespace Rosegarden
+{
+
+TextRuler::TextRuler(RulerScale *rulerScale,
+ Segment *segment,
+ int height,
+ QWidget *parent,
+ const char *name)
+ : QWidget(parent, name),
+ m_height(height),
+ m_currentXOffset(0),
+ m_width( -1),
+ m_segment(segment),
+ m_rulerScale(rulerScale),
+ m_font("helvetica", 12),
+ m_fontMetrics(m_font)
+{
+ m_mySegmentMaybe = (m_segment->getComposition() != 0);
+ setBackgroundColor(GUIPalette::getColour(GUIPalette::TextRulerBackground));
+
+ m_font.setPixelSize(10);
+}
+
+TextRuler::~TextRuler()
+{
+ if (m_mySegmentMaybe && !m_segment->getComposition()) {
+ delete m_segment;
+ }
+}
+
+void
+TextRuler::slotScrollHoriz(int x)
+{
+ int w = width(), h = height();
+ int dx = x - ( -m_currentXOffset);
+ m_currentXOffset = -x;
+
+ if (dx > w*3 / 4 || dx < -w*3 / 4) {
+ 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
+TextRuler::sizeHint() const
+{
+ double width =
+ m_rulerScale->getBarPosition(m_rulerScale->getLastVisibleBar()) +
+ m_rulerScale->getBarWidth(m_rulerScale->getLastVisibleBar());
+
+ QSize res(std::max(int(width), m_width), m_height);
+
+ return res;
+}
+
+QSize
+TextRuler::minimumSizeHint() const
+{
+ double firstBarWidth = m_rulerScale->getBarWidth(0);
+ QSize res = QSize(int(firstBarWidth), m_height);
+ return res;
+}
+
+void
+TextRuler::paintEvent(QPaintEvent* e)
+{
+ QPainter paint(this);
+ paint.setPen(GUIPalette::getColour(GUIPalette::TextRulerForeground));
+
+ paint.setClipRegion(e->region());
+ paint.setClipRect(e->rect().normalize());
+
+ QRect clipRect = paint.clipRegion().boundingRect();
+
+ timeT from = m_rulerScale->getTimeForX
+ (clipRect.x() - m_currentXOffset - 100);
+ timeT to = m_rulerScale->getTimeForX
+ (clipRect.x() + clipRect.width() - m_currentXOffset + 100);
+
+ for (Segment::iterator i = m_segment->findTime(from);
+ i != m_segment->findTime(to) && i != m_segment->end(); ++i) {
+
+ if (!(*i)->isa(Text::EventType))
+ continue;
+
+ std::string text;
+ if (!(*i)->get
+ <String>(Text::TextPropertyName, text)) {
+ RG_DEBUG
+ << "Warning: TextRuler::paintEvent: No text in text event"
+ << endl;
+ continue;
+ }
+
+ QRect bounds = m_fontMetrics.boundingRect(strtoqstr(text));
+
+ double x = m_rulerScale->getXForTime((*i)->getAbsoluteTime()) +
+ m_currentXOffset - bounds.width() / 2;
+
+ int y = height() / 2 + bounds.height() / 2;
+
+ paint.drawText(static_cast<int>(x), y, strtoqstr(text));
+ }
+}
+
+}
+#include "TextRuler.moc"
diff --git a/src/gui/rulers/TextRuler.h b/src/gui/rulers/TextRuler.h
new file mode 100644
index 0000000..7d554cb
--- /dev/null
+++ b/src/gui/rulers/TextRuler.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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_TEXTRULER_H_
+#define _RG_TEXTRULER_H_
+
+#include <qfont.h>
+#include <qfontmetrics.h>
+#include <qsize.h>
+#include <qwidget.h>
+
+
+class QPaintEvent;
+
+
+namespace Rosegarden
+{
+
+class Segment;
+class RulerScale;
+
+
+/**
+ * TextRuler is a widget that shows a strip of text strings at
+ * x-coordinates corresponding to specified times. The strings
+ * are obtained from a Segment, in which they are stored as
+ * text events. (The Segment does not have to be part of a
+ * Composition.)
+ *
+ * By design, this is more suitable for the display of single-purpose
+ * read-only data such as calculated chord names or (at a pinch)
+ * lyrics; it's not really suitable for displaying text data
+ * associated with a staff.
+ */
+
+class TextRuler : public QWidget
+{
+ Q_OBJECT
+
+public:
+ /**
+ * Construct a TextRuler that displays the text events found
+ * in the given Segment at positions calculated by the given
+ * RulerScale.
+ *
+ * The Segment will be destroyed along with the TextRuler, if
+ * it was not in a Composition when the TextRuler was created
+ * and still is not when it's destroyed. If the Segment was
+ * found to be in a Composition on either occasion, it will be
+ * assumed to be someone else's responsibility.
+ *
+ * The RulerScale will not be destroyed along with the TextRuler.
+ */
+ TextRuler(RulerScale *rulerScale,
+ Segment *segment,
+ int height = 0,
+ QWidget* parent = 0,
+ const char *name = 0);
+
+ ~TextRuler();
+
+ 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:
+ int m_height;
+ int m_currentXOffset;
+ int m_width;
+
+ bool m_mySegmentMaybe;
+
+ Segment *m_segment;
+ RulerScale *m_rulerScale;
+
+ QFont m_font;
+ QFontMetrics m_fontMetrics;
+};
+
+
+}
+
+#endif
diff --git a/src/gui/rulers/VelocityColour.cpp b/src/gui/rulers/VelocityColour.cpp
new file mode 100644
index 0000000..02a2f90
--- /dev/null
+++ b/src/gui/rulers/VelocityColour.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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "VelocityColour.h"
+
+#include <qcolor.h>
+
+// #include <cassert>
+
+
+namespace Rosegarden
+{
+
+VelocityColour::VelocityColour(const QColor &loud,
+ const QColor &medium,
+ const QColor &quiet,
+ int maxValue,
+ int loudKnee,
+ int mediumKnee,
+ int quietKnee):
+ m_loudColour(loud),
+ m_mediumColour(medium),
+ m_quietColour(quiet),
+ m_loudKnee(loudKnee),
+ m_mediumKnee(mediumKnee),
+ m_quietKnee(quietKnee),
+ m_maxValue(maxValue),
+ m_mixedColour(QColor(0, 0, 0)), // black as default
+ m_multiplyFactor(1000)
+{
+
+// assert(maxValue > loudKnee);
+// assert(loudKnee > mediumKnee);
+// assert(mediumKnee > quietKnee);
+
+ // These are the colours for the first band from Quiet to Medium.
+ // We use a multiplication factor to keep ourselves in the realms
+ // of integer arithmetic as we can potentially be doing a lot of
+ // these calculations when playing.
+ //
+ //
+ m_loStartRed = m_quietColour.red() * m_multiplyFactor;
+ m_loStartGreen = m_quietColour.green() * m_multiplyFactor;
+ m_loStartBlue = m_quietColour.blue() * m_multiplyFactor;
+
+ m_loStepRed = ( m_mediumColour.red() * m_multiplyFactor
+ - m_loStartRed ) / m_mediumKnee;
+ m_loStepGreen = ( m_mediumColour.green() * m_multiplyFactor
+ - m_loStartGreen ) / m_mediumKnee;
+ m_loStepBlue = ( m_mediumColour.blue() * m_multiplyFactor
+ - m_loStartBlue ) / m_mediumKnee;
+
+ m_hiStartRed = m_mediumColour.red() * m_multiplyFactor;
+ m_hiStartGreen = m_mediumColour.green() * m_multiplyFactor;
+ m_hiStartBlue = m_mediumColour.blue() * m_multiplyFactor;
+
+ m_hiStepRed = ( m_loudColour.red() * m_multiplyFactor
+ - m_hiStartRed ) / m_mediumKnee;
+ m_hiStepGreen = ( m_loudColour.green() * m_multiplyFactor
+ - m_hiStartGreen ) / m_mediumKnee;
+ m_hiStepBlue = ( m_loudColour.blue() * m_multiplyFactor
+ - m_hiStartBlue ) / m_mediumKnee;
+
+}
+
+VelocityColour::~VelocityColour()
+{}
+
+const QColor&
+VelocityColour::getColour(int value)
+{
+ if (value > m_maxValue)
+ value = m_maxValue;
+
+ if (value < m_quietKnee) {
+ return m_quietColour;
+ } else if (value < m_mediumKnee) {
+ m_mixedColour.setRgb(
+ ( m_loStartRed + m_loStepRed * value ) / m_multiplyFactor,
+ ( m_loStartGreen + m_loStepGreen * value ) / m_multiplyFactor,
+ ( m_loStartBlue + m_loStepBlue * value ) / m_multiplyFactor);
+
+ } else if (value >= m_mediumKnee < m_loudKnee) {
+ int mixFactor = value - m_mediumKnee;
+
+ m_mixedColour.setRgb(
+ ( m_hiStartRed + m_hiStepRed * mixFactor ) / m_multiplyFactor,
+ ( m_hiStartGreen + m_hiStepGreen * mixFactor ) / m_multiplyFactor,
+ ( m_hiStartBlue + m_hiStepBlue * mixFactor ) / m_multiplyFactor);
+ } else {
+ return m_loudColour;
+ return m_loudColour;
+ }
+
+ return m_mixedColour;
+}
+
+}
diff --git a/src/gui/rulers/VelocityColour.h b/src/gui/rulers/VelocityColour.h
new file mode 100644
index 0000000..0e555e1
--- /dev/null
+++ b/src/gui/rulers/VelocityColour.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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_VELOCITYCOLOUR_H_
+#define _RG_VELOCITYCOLOUR_H_
+
+#include <qcolor.h>
+
+
+
+
+namespace Rosegarden
+{
+
+
+
+/**
+ * Returns a QColour according to a formula. We provide three colours
+ * to mix, a maximum value and three knees at which points the
+ * intermediate colours max out. Play around to your satisfaction.
+ */
+class VelocityColour
+{
+
+public:
+ VelocityColour(const QColor &loud,
+ const QColor &medium,
+ const QColor &quiet,
+ int maxValue,
+ int loudKnee,
+ int mediumKnee,
+ int quietKnee);
+ ~VelocityColour();
+
+ const QColor& getColour(int value);
+
+ int getLoudKnee() const { return m_loudKnee; }
+ int getMediumKnee() const { return m_mediumKnee; }
+ int getQuietKnee() const { return m_quietKnee; }
+
+ QColor getLoudColour() const { return m_loudColour; }
+ QColor getMediumColour() const { return m_mediumColour; }
+ QColor getQuietColour() const { return m_quietColour; }
+
+ int getMaxValue() const { return m_maxValue; }
+
+private:
+
+ QColor m_loudColour;
+ QColor m_mediumColour;
+ QColor m_quietColour;
+ int m_loudKnee;
+ int m_mediumKnee;
+ int m_quietKnee;
+ int m_maxValue;
+
+ // the mixed colour that we can return
+ QColor m_mixedColour;
+
+
+ int m_loStartRed;
+ int m_loStartGreen;
+ int m_loStartBlue;
+
+ int m_loStepRed;
+ int m_loStepGreen;
+ int m_loStepBlue;
+
+ int m_hiStartRed;
+ int m_hiStartGreen;
+ int m_hiStartBlue;
+
+ int m_hiStepRed;
+ int m_hiStepGreen;
+ int m_hiStepBlue;
+
+
+ int m_multiplyFactor;
+};
+
+
+}
+
+#endif
diff --git a/src/gui/rulers/ViewElementAdapter.cpp b/src/gui/rulers/ViewElementAdapter.cpp
new file mode 100644
index 0000000..550ddcf
--- /dev/null
+++ b/src/gui/rulers/ViewElementAdapter.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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#include "ViewElementAdapter.h"
+
+namespace Rosegarden
+{
+
+ViewElementAdapter::ViewElementAdapter(ViewElement* el, const PropertyName& p)
+ : m_viewElement(el),
+ m_propertyName(p)
+{
+}
+
+bool ViewElementAdapter::getValue(long& val)
+{
+ return m_viewElement->event()->get<Rosegarden::Int>(m_propertyName, val);
+}
+
+void ViewElementAdapter::setValue(long val)
+{
+ m_viewElement->event()->set<Rosegarden::Int>(m_propertyName, val);
+}
+
+timeT ViewElementAdapter::getTime()
+{
+ return m_viewElement->getViewAbsoluteTime();
+}
+
+timeT ViewElementAdapter::getDuration()
+{
+ return m_viewElement->getViewDuration();
+}
+
+}
diff --git a/src/gui/rulers/ViewElementAdapter.h b/src/gui/rulers/ViewElementAdapter.h
new file mode 100644
index 0000000..1207522
--- /dev/null
+++ b/src/gui/rulers/ViewElementAdapter.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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_VIEWELEMENTADAPTER_H_
+#define _RG_VIEWELEMENTADAPTER_H_
+
+#include "ElementAdapter.h"
+#include "base/ViewElement.h"
+
+namespace Rosegarden
+{
+
+class Event;
+
+class ViewElementAdapter : public ElementAdapter
+{
+public:
+ ViewElementAdapter(ViewElement*, const PropertyName&);
+
+ virtual bool getValue(long&);
+ virtual void setValue(long);
+ virtual timeT getTime();
+ virtual timeT getDuration();
+
+ virtual Event* getEvent() { return m_viewElement->event(); }
+ ViewElement* getViewElement() { return m_viewElement; }
+
+protected:
+
+ //--------------- Data members ---------------------------------
+
+ ViewElement* m_viewElement;
+ const PropertyName& m_propertyName;
+};
+
+}
+
+#endif /*VIEWELEMENTADAPTER_H_*/
diff --git a/src/gui/seqmanager/AudioSegmentMmapper.cpp b/src/gui/seqmanager/AudioSegmentMmapper.cpp
new file mode 100644
index 0000000..8933b39
--- /dev/null
+++ b/src/gui/seqmanager/AudioSegmentMmapper.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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "AudioSegmentMmapper.h"
+
+#include "base/Event.h"
+#include "base/Composition.h"
+#include "base/RealTime.h"
+#include "base/Segment.h"
+#include "base/TriggerSegment.h"
+#include "document/RosegardenGUIDoc.h"
+#include "SegmentMmapper.h"
+#include "sound/MappedEvent.h"
+#include <qstring.h>
+
+
+namespace Rosegarden
+{
+
+AudioSegmentMmapper::AudioSegmentMmapper(RosegardenGUIDoc* doc, Segment* s,
+ const QString& fileName)
+ : SegmentMmapper(doc, s, fileName)
+{}
+
+void AudioSegmentMmapper::dump()
+{
+ Composition &comp = m_doc->getComposition();
+
+ RealTime eventTime;
+ Track* track = comp.getTrackById(m_segment->getTrack());
+
+ // Can't write out if no track
+ if (!track) {
+ std::cerr << "AudioSegmentMmapper::dump: ERROR: No track for segment!"
+ << std::endl;
+ return ;
+ }
+
+ timeT segmentStartTime = m_segment->getStartTime();
+ timeT segmentEndTime = m_segment->getEndMarkerTime();
+ timeT segmentDuration = segmentEndTime - segmentStartTime;
+ timeT repeatEndTime = segmentEndTime;
+
+ //!!! The repeat count is actually not quite right for audio
+ // segments -- it returns one too many for repeating segments,
+ // because in midi segments you want that (to deal with partial
+ // repeats). Here we really need to find a better way to deal
+ // with partial repeats...
+
+ int repeatCount = getSegmentRepeatCount();
+ if (repeatCount > 0)
+ repeatEndTime = m_segment->getRepeatEndTime();
+
+ MappedEvent* bufPos = m_mmappedEventBuffer;
+
+ for (int repeatNo = 0; repeatNo <= repeatCount; ++repeatNo) {
+
+ timeT playTime =
+ segmentStartTime + repeatNo * segmentDuration;
+ if (playTime >= repeatEndTime)
+ break;
+
+ playTime = playTime + m_segment->getDelay();
+ eventTime = comp.getElapsedRealTime(playTime);
+ eventTime = eventTime + m_segment->getRealTimeDelay();
+
+ RealTime audioStart = m_segment->getAudioStartTime();
+ RealTime audioDuration = m_segment->getAudioEndTime() - audioStart;
+ MappedEvent *mE =
+ new (bufPos) MappedEvent(track->getInstrument(), // send instrument for audio
+ m_segment->getAudioFileId(),
+ eventTime,
+ audioDuration,
+ audioStart);
+ mE->setTrackId(track->getId());
+ mE->setRuntimeSegmentId(m_segment->getRuntimeId());
+
+ // Send the autofade if required
+ //
+ if (m_segment->isAutoFading()) {
+ mE->setAutoFade(true);
+ mE->setFadeInTime(m_segment->getFadeInTime());
+ mE->setFadeOutTime(m_segment->getFadeOutTime());
+ std::cout << "AudioSegmentMmapper::dump - "
+ << "SETTING AUTOFADE "
+ << "in = " << m_segment->getFadeInTime()
+ << ", out = " << m_segment->getFadeOutTime()
+ << std::endl;
+ } else {
+ // std::cout << "AudioSegmentMmapper::dump - "
+ // << "NO AUTOFADE SET ON SEGMENT" << std::endl;
+ }
+
+ ++bufPos;
+ }
+
+ *(size_t *)m_mmappedRegion = repeatCount + 1;
+}
+
+size_t AudioSegmentMmapper::computeMmappedSize()
+{
+ if (!m_segment) return 0;
+
+ int repeatCount = getSegmentRepeatCount();
+
+ return (repeatCount + 1) * 1 * sizeof(MappedEvent);
+ // audio segments don't have events, we just need room for 1 MappedEvent
+}
+
+}
+
diff --git a/src/gui/seqmanager/AudioSegmentMmapper.h b/src/gui/seqmanager/AudioSegmentMmapper.h
new file mode 100644
index 0000000..bc4d798
--- /dev/null
+++ b/src/gui/seqmanager/AudioSegmentMmapper.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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_AUDIOSEGMENTMMAPPER_H_
+#define _RG_AUDIOSEGMENTMMAPPER_H_
+
+#include "SegmentMmapper.h"
+
+
+class QString;
+
+
+namespace Rosegarden
+{
+
+class Segment;
+class RosegardenGUIDoc;
+
+
+class AudioSegmentMmapper : public SegmentMmapper
+{
+ friend class SegmentMmapperFactory;
+
+protected:
+ AudioSegmentMmapper(RosegardenGUIDoc*, Segment*,
+ const QString& fileName);
+
+ virtual size_t computeMmappedSize();
+
+ /// dump all segment data in the file
+ virtual void dump();
+};
+
+//----------------------------------------
+
+
+}
+
+#endif
diff --git a/src/gui/seqmanager/CompositionMmapper.cpp b/src/gui/seqmanager/CompositionMmapper.cpp
new file mode 100644
index 0000000..094f562
--- /dev/null
+++ b/src/gui/seqmanager/CompositionMmapper.cpp
@@ -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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "CompositionMmapper.h"
+#include "misc/Debug.h"
+
+#include <kstddirs.h>
+#include "base/Composition.h"
+#include "base/Segment.h"
+#include "document/RosegardenGUIDoc.h"
+#include "gui/application/RosegardenApplication.h"
+#include "SegmentMmapperFactory.h"
+#include "SegmentMmapper.h"
+#include <kglobal.h>
+#include <qdir.h>
+#include <qfile.h>
+#include <qstring.h>
+#include <qstringlist.h>
+#include <stdint.h>
+
+
+namespace Rosegarden
+{
+
+CompositionMmapper::CompositionMmapper(RosegardenGUIDoc *doc)
+ : m_doc(doc)
+{
+ cleanup();
+
+ SEQMAN_DEBUG << "CompositionMmapper() - doc = " << doc << endl;
+ Composition &comp = m_doc->getComposition();
+
+ for (Composition::iterator it = comp.begin(); it != comp.end(); it++) {
+
+ Track* track = comp.getTrackById((*it)->getTrack());
+
+ // check to see if track actually exists
+ //
+ if (track == 0)
+ continue;
+
+ mmapSegment(*it);
+ }
+}
+
+CompositionMmapper::~CompositionMmapper()
+{
+ SEQMAN_DEBUG << "~CompositionMmapper()\n";
+
+ //
+ // Clean up possible left-overs
+ //
+ cleanup();
+
+ for (segmentmmapers::iterator i = m_segmentMmappers.begin();
+ i != m_segmentMmappers.end(); ++i)
+ delete i->second;
+}
+
+void CompositionMmapper::cleanup()
+{
+ // In case the sequencer is still running, mapping some segments
+ //
+ rgapp->sequencerSend("closeAllSegments()");
+
+ // Erase all 'segment_*' files
+ //
+ QString tmpPath = KGlobal::dirs()->resourceDirs("tmp").last();
+
+ QDir segmentsDir(tmpPath, "segment_*");
+ for (unsigned int i = 0; i < segmentsDir.count(); ++i) {
+ QString segmentName = tmpPath + '/' + segmentsDir[i];
+ SEQMAN_DEBUG << "CompositionMmapper : cleaning up " << segmentName << endl;
+ QFile::remove
+ (segmentName);
+ }
+
+}
+
+bool CompositionMmapper::segmentModified(Segment* segment)
+{
+ SegmentMmapper* mmapper = m_segmentMmappers[segment];
+
+ if (!mmapper)
+ return false; // this can happen with the SegmentSplitCommand, where the new segment's transpose is set
+ // even though it's not mapped yet
+
+ SEQMAN_DEBUG << "CompositionMmapper::segmentModified(" << segment << ") - mmapper = "
+ << mmapper << endl;
+
+ return mmapper->refresh();
+}
+
+void CompositionMmapper::segmentAdded(Segment* segment)
+{
+ SEQMAN_DEBUG << "CompositionMmapper::segmentAdded(" << segment << ")\n";
+
+ mmapSegment(segment);
+}
+
+void CompositionMmapper::segmentDeleted(Segment* segment)
+{
+ SEQMAN_DEBUG << "CompositionMmapper::segmentDeleted(" << segment << ")\n";
+ SegmentMmapper* mmapper = m_segmentMmappers[segment];
+ m_segmentMmappers.erase(segment);
+ SEQMAN_DEBUG << "CompositionMmapper::segmentDeleted() : deleting SegmentMmapper " << mmapper << endl;
+
+ delete mmapper;
+}
+
+void CompositionMmapper::mmapSegment(Segment* segment)
+{
+ SEQMAN_DEBUG << "CompositionMmapper::mmapSegment(" << segment << ")\n";
+
+ SegmentMmapper* mmapper = SegmentMmapperFactory::makeMmapperForSegment(m_doc,
+ segment,
+ makeFileName(segment));
+
+ if (mmapper)
+ m_segmentMmappers[segment] = mmapper;
+}
+
+QString CompositionMmapper::makeFileName(Segment* segment)
+{
+ QStringList tmpDirs = KGlobal::dirs()->resourceDirs("tmp");
+
+ return QString("%1/segment_%2")
+ .arg(tmpDirs.last())
+ .arg((uintptr_t)segment, 0, 16);
+}
+
+QString CompositionMmapper::getSegmentFileName(Segment* s)
+{
+ SegmentMmapper* mmapper = m_segmentMmappers[s];
+
+ if (mmapper)
+ return mmapper->getFileName();
+ else
+ return QString::null;
+}
+
+size_t CompositionMmapper::getSegmentFileSize(Segment* s)
+{
+ SegmentMmapper* mmapper = m_segmentMmappers[s];
+
+ if (mmapper)
+ return mmapper->getFileSize();
+ else
+ return 0;
+}
+
+}
diff --git a/src/gui/seqmanager/CompositionMmapper.h b/src/gui/seqmanager/CompositionMmapper.h
new file mode 100644
index 0000000..8bf997d
--- /dev/null
+++ b/src/gui/seqmanager/CompositionMmapper.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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_COMPOSITIONMMAPPER_H_
+#define _RG_COMPOSITIONMMAPPER_H_
+
+#include <map>
+#include <qstring.h>
+
+
+
+
+namespace Rosegarden
+{
+
+class SegmentMmapper;
+class Segment;
+class RosegardenGUIDoc;
+
+
+class CompositionMmapper
+{
+ friend class SequenceManager;
+
+public:
+ CompositionMmapper(RosegardenGUIDoc *doc);
+ ~CompositionMmapper();
+
+ QString getSegmentFileName(Segment*);
+ size_t getSegmentFileSize(Segment*);
+
+ void cleanup();
+
+protected:
+ bool segmentModified(Segment*);
+ void segmentAdded(Segment*);
+ void segmentDeleted(Segment*);
+
+ void mmapSegment(Segment*);
+ QString makeFileName(Segment*);
+
+ //--------------- Data members ---------------------------------
+
+ RosegardenGUIDoc* m_doc;
+ typedef std::map<Segment*, SegmentMmapper*> segmentmmapers;
+
+ segmentmmapers m_segmentMmappers;
+};
+
+
+}
+
+#endif
diff --git a/src/gui/seqmanager/ControlBlockMmapper.cpp b/src/gui/seqmanager/ControlBlockMmapper.cpp
new file mode 100644
index 0000000..90ab6c2
--- /dev/null
+++ b/src/gui/seqmanager/ControlBlockMmapper.cpp
@@ -0,0 +1,226 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
+
+/*
+ Rosegarden
+ A MIDI and audio sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "ControlBlockMmapper.h"
+#include "misc/Debug.h"
+
+#include <kstddirs.h>
+#include "misc/Strings.h"
+#include "base/Composition.h"
+#include "base/Exception.h"
+#include "base/MidiProgram.h"
+#include "base/Track.h"
+#include "document/RosegardenGUIDoc.h"
+#include "sound/ControlBlock.h"
+#include <kglobal.h>
+#include <qfile.h>
+#include <qstring.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <sys/mman.h>
+#include <errno.h>
+
+namespace Rosegarden
+{
+
+ControlBlockMmapper::ControlBlockMmapper(RosegardenGUIDoc* doc)
+ : m_doc(doc),
+ m_fileName(createFileName()),
+ m_fd( -1),
+ m_mmappedBuffer(0),
+ m_mmappedSize(sizeof(ControlBlock)),
+ m_controlBlock(0)
+{
+ // just in case
+ QFile::remove
+ (m_fileName);
+
+ m_fd = ::open(m_fileName.latin1(), O_RDWR | O_CREAT | O_TRUNC,
+ S_IRUSR | S_IWUSR);
+ if (m_fd < 0) {
+ SEQMAN_DEBUG << "ControlBlockMmapper : Couldn't open " << m_fileName
+ << endl;
+ throw Exception("Couldn't open " + qstrtostr(m_fileName));
+ }
+
+ setFileSize(m_mmappedSize);
+
+ //
+ // mmap() file for writing
+ //
+ m_mmappedBuffer = ::mmap(0, m_mmappedSize,
+ PROT_READ | PROT_WRITE,
+ MAP_SHARED, m_fd, 0);
+
+ if (m_mmappedBuffer == (void*) - 1) {
+ SEQMAN_DEBUG << QString("mmap failed : (%1) %2\n").arg(errno).arg(strerror(errno));
+ throw Exception("mmap failed");
+ }
+
+ SEQMAN_DEBUG << "ControlBlockMmapper : mmap size : " << m_mmappedSize
+ << " at " << (void*)m_mmappedBuffer << endl;
+
+ // Create new control block on file
+ initControlBlock();
+}
+
+ControlBlockMmapper::~ControlBlockMmapper()
+{
+ ::munmap(m_mmappedBuffer, m_mmappedSize);
+ ::close(m_fd);
+ QFile::remove
+ (m_fileName);
+}
+
+QString ControlBlockMmapper::createFileName()
+{
+ return KGlobal::dirs()->resourceDirs("tmp").last() + "/rosegarden_control_block";
+}
+
+void ControlBlockMmapper::updateTrackData(Track *t)
+{
+ m_controlBlock->updateTrackData(t);
+}
+
+void ControlBlockMmapper::setTrackDeleted(TrackId t)
+{
+ m_controlBlock->setTrackDeleted(t, true);
+}
+
+void ControlBlockMmapper::updateMidiFilters(MidiFilter thruFilter,
+ MidiFilter recordFilter)
+{
+ m_controlBlock->setThruFilter(thruFilter);
+ m_controlBlock->setRecordFilter(recordFilter);
+}
+
+void ControlBlockMmapper::updateMetronomeData(InstrumentId instId)
+{
+ m_controlBlock->setInstrumentForMetronome(instId);
+}
+
+void ControlBlockMmapper::updateMetronomeForPlayback()
+{
+ bool muted = !m_doc->getComposition().usePlayMetronome();
+ SEQMAN_DEBUG << "ControlBlockMmapper::updateMetronomeForPlayback: muted=" << muted << endl;
+ if (m_controlBlock->isMetronomeMuted() == muted)
+ return ;
+ m_controlBlock->setMetronomeMuted(muted);
+}
+
+void ControlBlockMmapper::updateMetronomeForRecord()
+{
+ bool muted = !m_doc->getComposition().useRecordMetronome();
+ SEQMAN_DEBUG << "ControlBlockMmapper::updateMetronomeForRecord: muted=" << muted << endl;
+ if (m_controlBlock->isMetronomeMuted() == muted)
+ return ;
+ m_controlBlock->setMetronomeMuted(muted);
+}
+
+bool ControlBlockMmapper::updateSoloData(bool solo,
+ TrackId selectedTrack)
+{
+ bool changed = false;
+
+ if (solo != m_controlBlock->isSolo()) {
+
+ changed = true;
+
+ } else if (solo &&
+ (selectedTrack != m_controlBlock->getSelectedTrack())) {
+
+ changed = true;
+ }
+
+ m_controlBlock->setSolo(solo);
+ m_controlBlock->setSelectedTrack(selectedTrack);
+
+ return changed;
+}
+
+void ControlBlockMmapper::setDocument(RosegardenGUIDoc* doc)
+{
+ SEQMAN_DEBUG << "ControlBlockMmapper::setDocument()\n";
+ m_doc = doc;
+ initControlBlock();
+}
+
+void ControlBlockMmapper::initControlBlock()
+{
+ SEQMAN_DEBUG << "ControlBlockMmapper::initControlBlock()\n";
+
+ m_controlBlock = new (m_mmappedBuffer) ControlBlock(m_doc->getComposition().getMaxTrackId());
+
+ Composition& comp = m_doc->getComposition();
+
+ for (Composition::trackiterator i = comp.getTracks().begin(); i != comp.getTracks().end(); ++i) {
+ Track* track = i->second;
+ if (track == 0)
+ continue;
+
+ m_controlBlock->updateTrackData(track);
+ }
+
+ m_controlBlock->setMetronomeMuted(!comp.usePlayMetronome());
+
+ m_controlBlock->setThruFilter(m_doc->getStudio().getMIDIThruFilter());
+ m_controlBlock->setRecordFilter(m_doc->getStudio().getMIDIRecordFilter());
+
+ ::msync(m_mmappedBuffer, m_mmappedSize, MS_ASYNC);
+}
+
+void ControlBlockMmapper::setFileSize(size_t size)
+{
+ SEQMAN_DEBUG << "ControlBlockMmapper : setting size of "
+ << m_fileName << " to " << size << endl;
+ // rewind
+ ::lseek(m_fd, 0, SEEK_SET);
+
+ //
+ // enlarge the file
+ // (seek() to wanted size, then write a byte)
+ //
+ if (::lseek(m_fd, size - 1, SEEK_SET) == -1) {
+ std::cerr << "WARNING: ControlBlockMmapper : Couldn't lseek in " << m_fileName
+ << " to " << size << std::endl;
+ throw Exception("lseek failed");
+ }
+
+ if (::write(m_fd, "\0", 1) != 1) {
+ std::cerr << "WARNING: ControlBlockMmapper : Couldn't write byte in "
+ << m_fileName << std::endl;
+ throw Exception("write failed");
+ }
+
+}
+
+void
+ControlBlockMmapper::enableMIDIThruRouting(bool state)
+{
+ m_controlBlock->setMidiRoutingEnabled(state);
+}
+
+}
diff --git a/src/gui/seqmanager/ControlBlockMmapper.h b/src/gui/seqmanager/ControlBlockMmapper.h
new file mode 100644
index 0000000..a96e742
--- /dev/null
+++ b/src/gui/seqmanager/ControlBlockMmapper.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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_CONTROLBLOCKMMAPPER_H_
+#define _RG_CONTROLBLOCKMMAPPER_H_
+
+#include "base/MidiProgram.h"
+#include "base/Track.h"
+#include <qstring.h>
+
+
+
+
+namespace Rosegarden
+{
+
+class Track;
+class RosegardenGUIDoc;
+class ControlBlock;
+
+
+class ControlBlockMmapper
+{
+public:
+ ControlBlockMmapper(RosegardenGUIDoc*);
+ ~ControlBlockMmapper();
+
+ QString getFileName() { return m_fileName; }
+ void updateTrackData(Track*);
+ void setTrackDeleted(TrackId);
+ void updateMetronomeData(InstrumentId instId);
+ void updateMetronomeForPlayback();
+ void updateMetronomeForRecord();
+ bool updateSoloData(bool solo, TrackId selectedTrack);
+ void updateMidiFilters(MidiFilter thruFilter,
+ MidiFilter recordFilter);
+ void setDocument(RosegardenGUIDoc*);
+ void enableMIDIThruRouting(bool state);
+
+protected:
+ void initControlBlock();
+ void setFileSize(size_t);
+ QString createFileName();
+
+ //--------------- Data members ---------------------------------
+ RosegardenGUIDoc* m_doc;
+ QString m_fileName;
+ int m_fd;
+ void* m_mmappedBuffer;
+ size_t m_mmappedSize;
+ ControlBlock* m_controlBlock;
+};
+
+
+//----------------------------------------
+
+
+
+}
+
+#endif
diff --git a/src/gui/seqmanager/MetronomeMmapper.cpp b/src/gui/seqmanager/MetronomeMmapper.cpp
new file mode 100644
index 0000000..9ca9af7
--- /dev/null
+++ b/src/gui/seqmanager/MetronomeMmapper.cpp
@@ -0,0 +1,268 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
+
+/*
+ Rosegarden
+ A MIDI and audio sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "MetronomeMmapper.h"
+#include "misc/Debug.h"
+#include <kapplication.h>
+
+#include "sound/Midi.h"
+#include <kstddirs.h>
+#include "document/ConfigGroups.h"
+#include "base/Event.h"
+#include "base/MidiProgram.h"
+#include "base/NotationTypes.h"
+#include "base/RealTime.h"
+#include "base/Segment.h"
+#include "base/Studio.h"
+#include "base/TriggerSegment.h"
+#include "document/RosegardenGUIDoc.h"
+#include "SegmentMmapper.h"
+#include "sound/MappedEvent.h"
+#include <kconfig.h>
+#include <kglobal.h>
+#include <qstring.h>
+#include <algorithm>
+
+
+namespace Rosegarden
+{
+
+MetronomeMmapper::MetronomeMmapper(RosegardenGUIDoc* doc)
+ : SegmentMmapper(doc, 0, createFileName()),
+ m_metronome(0), // no metronome to begin with
+ m_tickDuration(0, 100000000)
+{
+ SEQMAN_DEBUG << "MetronomeMmapper ctor : " << this << endl;
+
+ // get metronome device
+ Studio &studio = m_doc->getStudio();
+ int device = studio.getMetronomeDevice();
+
+ const MidiMetronome *metronome =
+ m_doc->getStudio().getMetronomeFromDevice(device);
+
+ if (metronome) {
+
+ SEQMAN_DEBUG << "MetronomeMmapper: have metronome, it's on instrument " << metronome->getInstrument() << endl;
+
+ m_metronome = new MidiMetronome(*metronome);
+ } else {
+ m_metronome = new MidiMetronome
+ (SystemInstrumentBase);
+ SEQMAN_DEBUG << "MetronomeMmapper: no metronome for device " << device << endl;
+ }
+
+ Composition& c = m_doc->getComposition();
+ timeT t = c.getBarStart( -20); // somewhat arbitrary
+ int depth = m_metronome->getDepth();
+
+ if (depth > 0) {
+ while (t < c.getEndMarker()) {
+
+ TimeSignature sig = c.getTimeSignatureAt(t);
+ timeT barDuration = sig.getBarDuration();
+ std::vector<int> divisions;
+ if (depth > 0)
+ sig.getDivisions(depth - 1, divisions);
+ int ticks = 1;
+
+ for (int i = -1; i < (int)divisions.size(); ++i) {
+ if (i >= 0)
+ ticks *= divisions[i];
+
+ for (int tick = 0; tick < ticks; ++tick) {
+ if (i >= 0 && (tick % divisions[i] == 0))
+ continue;
+ timeT tickTime = t + (tick * barDuration) / ticks;
+ m_ticks.push_back(Tick(tickTime, i + 1));
+ }
+ }
+
+ t = c.getBarEndForTime(t);
+ }
+ }
+
+ KConfig *config = kapp->config();
+ config->setGroup(SequencerOptionsConfigGroup);
+ int midiClock = config->readNumEntry("midiclock", 0);
+ int mtcMode = config->readNumEntry("mtcmode", 0);
+
+ if (midiClock == 1) {
+ timeT quarterNote = Note(Note::Crotchet).getDuration();
+
+ // Insert 24 clocks per quarter note
+ //
+ for (timeT insertTime = c.getStartMarker();
+ insertTime < c.getEndMarker();
+ insertTime += quarterNote / 24) {
+ m_ticks.push_back(Tick(insertTime, 3));
+ }
+ }
+
+
+ if (mtcMode > 0) {
+ // do something
+ }
+
+ sortTicks();
+
+ if (m_ticks.size() == 0) {
+ SEQMAN_DEBUG << "MetronomeMmapper : WARNING no ticks generated\n";
+ }
+
+ // Done by init()
+
+ // m_mmappedSize = computeMmappedSize();
+ // if (m_mmappedSize > 0) {
+ // setFileSize(m_mmappedSize);
+ // doMmap();
+ // dump();
+ // }
+}
+
+MetronomeMmapper::~MetronomeMmapper()
+{
+ SEQMAN_DEBUG << "~MetronomeMmapper " << this << endl;
+ delete m_metronome;
+}
+
+InstrumentId MetronomeMmapper::getMetronomeInstrument()
+{
+ return m_metronome->getInstrument();
+}
+
+QString MetronomeMmapper::createFileName()
+{
+ return KGlobal::dirs()->resourceDirs("tmp").last() + "/rosegarden_metronome";
+}
+
+void MetronomeMmapper::dump()
+{
+ RealTime eventTime;
+ Composition& comp = m_doc->getComposition();
+
+ SEQMAN_DEBUG << "MetronomeMmapper::dump: instrument is " << m_metronome->getInstrument() << endl;
+
+ MappedEvent* bufPos = m_mmappedEventBuffer, *mE;
+
+ for (TickContainer::iterator i = m_ticks.begin(); i != m_ticks.end(); ++i) {
+
+ /*
+ SEQMAN_DEBUG << "MetronomeMmapper::dump: velocity = "
+ << int(velocity) << endl;
+ */
+
+ eventTime = comp.getElapsedRealTime(i->first);
+
+ if (i->second == 3) // MIDI Clock
+ {
+ mE = new (bufPos) MappedEvent(0, MappedEvent::MidiSystemMessage);
+ mE->setData1(MIDI_TIMING_CLOCK);
+ mE->setEventTime(eventTime);
+ } else {
+ MidiByte velocity;
+ MidiByte pitch;
+ switch (i->second) {
+ case 0:
+ velocity = m_metronome->getBarVelocity();
+ pitch = m_metronome->getBarPitch();
+ break;
+ case 1:
+ velocity = m_metronome->getBeatVelocity();
+ pitch = m_metronome->getBeatPitch();
+ break;
+ default:
+ velocity = m_metronome->getSubBeatVelocity();
+ pitch = m_metronome->getSubBeatPitch();
+ break;
+ }
+
+ new (bufPos) MappedEvent(m_metronome->getInstrument(),
+ MappedEvent::MidiNoteOneShot,
+ pitch,
+ velocity,
+ eventTime,
+ m_tickDuration,
+ RealTime::zeroTime);
+ }
+
+ ++bufPos;
+ }
+
+ // Store the number of events at the start of the shared memory region
+ *(size_t *)m_mmappedRegion = (bufPos - m_mmappedEventBuffer);
+
+ SEQMAN_DEBUG << "MetronomeMmapper::dump: - "
+ << "Total events written = " << *(size_t *)m_mmappedRegion
+ << endl;
+}
+
+void MetronomeMmapper::sortTicks()
+{
+ sort(m_ticks.begin(), m_ticks.end());
+}
+
+size_t MetronomeMmapper::computeMmappedSize()
+{
+ KConfig *config = kapp->config();
+ config->setGroup(Rosegarden::SequencerOptionsConfigGroup);
+ int midiClock = config->readNumEntry("midiclock", 0);
+ int mtcMode = config->readNumEntry("mtcmode", 0);
+
+ // base size for Metronome ticks
+ size_t size = m_ticks.size() * sizeof(MappedEvent);
+ Composition& comp = m_doc->getComposition();
+
+ if (midiClock == 1)
+ {
+ using Rosegarden::Note;
+
+ // Allow room for MIDI clocks
+ int clocks = ( 24 * ( comp.getEndMarker() - comp.getStartMarker() ) ) /
+ Note(Note::Crotchet).getDuration();
+
+ /*
+ SEQMAN_DEBUG << "MetronomeMmapper::computeMmappedSize - "
+ << "Number of clock events catered for = " << clocks
+ << endl;
+ */
+
+ size += clocks * sizeof(MappedEvent);
+ }
+
+ if (mtcMode > 0)
+ {
+ // Allow room for MTC timing messages (how?)
+ }
+
+ return size;
+}
+
+unsigned int MetronomeMmapper::getSegmentRepeatCount()
+{
+ return 1;
+}
+
+}
diff --git a/src/gui/seqmanager/MetronomeMmapper.h b/src/gui/seqmanager/MetronomeMmapper.h
new file mode 100644
index 0000000..1e18171
--- /dev/null
+++ b/src/gui/seqmanager/MetronomeMmapper.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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_METRONOMEMMAPPER_H_
+#define _RG_METRONOMEMMAPPER_H_
+
+#include "base/MidiProgram.h"
+#include "base/RealTime.h"
+#include "SegmentMmapper.h"
+#include <qstring.h>
+#include <utility>
+#include <vector>
+#include "base/Event.h"
+
+
+
+
+namespace Rosegarden
+{
+
+class RosegardenGUIDoc;
+class MidiMetronome;
+
+
+class MetronomeMmapper : public SegmentMmapper
+{
+ friend class SegmentMmapperFactory;
+
+public:
+
+ virtual ~MetronomeMmapper();
+
+ InstrumentId getMetronomeInstrument();
+
+ // overrides from SegmentMmapper
+ virtual unsigned int getSegmentRepeatCount();
+
+protected:
+ MetronomeMmapper(RosegardenGUIDoc* doc);
+
+ virtual size_t computeMmappedSize();
+
+ void sortTicks();
+ QString createFileName();
+
+ // override from SegmentMmapper
+ virtual void dump();
+
+ //--------------- Data members ---------------------------------
+ typedef std::pair<timeT, int> Tick;
+ typedef std::vector<Tick> TickContainer;
+ friend bool operator<(Tick, Tick);
+
+ TickContainer m_ticks;
+ bool m_deleteMetronome;
+ const MidiMetronome* m_metronome;
+ RealTime m_tickDuration;
+};
+
+//----------------------------------------
+
+
+}
+
+#endif
diff --git a/src/gui/seqmanager/MidiFilterDialog.cpp b/src/gui/seqmanager/MidiFilterDialog.cpp
new file mode 100644
index 0000000..69d687a
--- /dev/null
+++ b/src/gui/seqmanager/MidiFilterDialog.cpp
@@ -0,0 +1,229 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
+
+/*
+ Rosegarden
+ A MIDI and audio sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "MidiFilterDialog.h"
+
+#include <klocale.h>
+#include "base/MidiProgram.h"
+#include "base/NotationTypes.h"
+#include "document/RosegardenGUIDoc.h"
+#include "gui/seqmanager/SequenceManager.h"
+#include "sound/MappedEvent.h"
+#include <kdialogbase.h>
+#include <qbuttongroup.h>
+#include <qcheckbox.h>
+#include <qhbox.h>
+#include <qwidget.h>
+
+
+namespace Rosegarden
+{
+
+MidiFilterDialog::MidiFilterDialog(QWidget *parent,
+ RosegardenGUIDoc *doc):
+ KDialogBase(parent, 0, true, i18n("Modify MIDI filters..."),
+ Ok | Apply | Close | Help),
+ m_doc(doc),
+ m_modified(true)
+{
+ setHelp("studio-midi-filters");
+
+ QHBox *hBox = makeHBoxMainWidget();
+
+ m_thruBox =
+ new QButtonGroup(1,
+ Qt::Horizontal,
+ i18n("THRU events to ignore"), hBox);
+
+ QCheckBox *noteThru = new QCheckBox(i18n("Note"), m_thruBox);
+ QCheckBox *progThru = new QCheckBox(i18n("Program Change"), m_thruBox);
+ QCheckBox *keyThru = new QCheckBox(i18n("Key Pressure"), m_thruBox);
+ QCheckBox *chanThru = new QCheckBox(i18n("Channel Pressure"), m_thruBox);
+ QCheckBox *pitchThru = new QCheckBox(i18n("Pitch Bend"), m_thruBox);
+ QCheckBox *contThru = new QCheckBox(i18n("Controller"), m_thruBox);
+ QCheckBox *sysThru = new QCheckBox(i18n("System Exclusive"), m_thruBox);
+
+ MidiFilter thruFilter = m_doc->getStudio().getMIDIThruFilter();
+
+ if (thruFilter & MappedEvent::MidiNote)
+ noteThru->setChecked(true);
+
+ if (thruFilter & MappedEvent::MidiProgramChange)
+ progThru->setChecked(true);
+
+ if (thruFilter & MappedEvent::MidiKeyPressure)
+ keyThru->setChecked(true);
+
+ if (thruFilter & MappedEvent::MidiChannelPressure)
+ chanThru->setChecked(true);
+
+ if (thruFilter & MappedEvent::MidiPitchBend)
+ pitchThru->setChecked(true);
+
+ if (thruFilter & MappedEvent::MidiController)
+ contThru->setChecked(true);
+
+ if (thruFilter & MappedEvent::MidiSystemMessage)
+ sysThru->setChecked(true);
+
+ m_recordBox =
+ new QButtonGroup(1,
+ Qt::Horizontal,
+ i18n("RECORD events to ignore"), hBox);
+
+ QCheckBox *noteRecord = new QCheckBox(i18n("Note"), m_recordBox);
+ QCheckBox *progRecord = new QCheckBox(i18n("Program Change"), m_recordBox);
+ QCheckBox *keyRecord = new QCheckBox(i18n("Key Pressure"), m_recordBox);
+ QCheckBox *chanRecord = new QCheckBox(i18n("Channel Pressure"), m_recordBox);
+ QCheckBox *pitchRecord = new QCheckBox(i18n("Pitch Bend"), m_recordBox);
+ QCheckBox *contRecord = new QCheckBox(i18n("Controller"), m_recordBox);
+ QCheckBox *sysRecord = new QCheckBox(i18n("System Exclusive"), m_recordBox);
+
+ MidiFilter recordFilter =
+ m_doc->getStudio().getMIDIRecordFilter();
+
+ if (recordFilter & MappedEvent::MidiNote)
+ noteRecord->setChecked(true);
+
+ if (recordFilter & MappedEvent::MidiProgramChange)
+ progRecord->setChecked(true);
+
+ if (recordFilter & MappedEvent::MidiKeyPressure)
+ keyRecord->setChecked(true);
+
+ if (recordFilter & MappedEvent::MidiChannelPressure)
+ chanRecord->setChecked(true);
+
+ if (recordFilter & MappedEvent::MidiPitchBend)
+ pitchRecord->setChecked(true);
+
+ if (recordFilter & MappedEvent::MidiController)
+ contRecord->setChecked(true);
+
+ if (recordFilter & MappedEvent::MidiSystemMessage)
+ sysRecord->setChecked(true);
+
+
+ connect(m_thruBox, SIGNAL(released(int)),
+ this, SLOT(slotSetModified()));
+
+ connect(m_recordBox, SIGNAL(released(int)),
+ this, SLOT(slotSetModified()));
+
+ setModified(false);
+}
+
+void
+MidiFilterDialog::slotApply()
+{
+ MidiFilter thruFilter = 0,
+ recordFilter = 0;
+
+ if (dynamic_cast<QCheckBox*>(m_thruBox->find(0))->isChecked())
+ thruFilter |= MappedEvent::MidiNote;
+
+ if (dynamic_cast<QCheckBox*>(m_thruBox->find(1))->isChecked())
+ thruFilter |= MappedEvent::MidiProgramChange;
+
+ if (dynamic_cast<QCheckBox*>(m_thruBox->find(2))->isChecked())
+ thruFilter |= MappedEvent::MidiKeyPressure;
+
+ if (dynamic_cast<QCheckBox*>(m_thruBox->find(3))->isChecked())
+ thruFilter |= MappedEvent::MidiChannelPressure;
+
+ if (dynamic_cast<QCheckBox*>(m_thruBox->find(4))->isChecked())
+ thruFilter |= MappedEvent::MidiPitchBend;
+
+ if (dynamic_cast<QCheckBox*>(m_thruBox->find(5))->isChecked())
+ thruFilter |= MappedEvent::MidiController;
+
+ if (dynamic_cast<QCheckBox*>(m_thruBox->find(6))->isChecked())
+ thruFilter |= MappedEvent::MidiSystemMessage;
+
+ if (dynamic_cast<QCheckBox*>(m_recordBox->find(0))->isChecked())
+ recordFilter |= MappedEvent::MidiNote;
+
+ if (dynamic_cast<QCheckBox*>(m_recordBox->find(1))->isChecked())
+ recordFilter |= MappedEvent::MidiProgramChange;
+
+ if (dynamic_cast<QCheckBox*>(m_recordBox->find(2))->isChecked())
+ recordFilter |= MappedEvent::MidiKeyPressure;
+
+ if (dynamic_cast<QCheckBox*>(m_recordBox->find(3))->isChecked())
+ recordFilter |= MappedEvent::MidiChannelPressure;
+
+ if (dynamic_cast<QCheckBox*>(m_recordBox->find(4))->isChecked())
+ recordFilter |= MappedEvent::MidiPitchBend;
+
+ if (dynamic_cast<QCheckBox*>(m_recordBox->find(5))->isChecked())
+ recordFilter |= MappedEvent::MidiController;
+
+ if (dynamic_cast<QCheckBox*>(m_recordBox->find(6))->isChecked())
+ recordFilter |= MappedEvent::MidiSystemMessage;
+
+
+ //if (m_thruBox->
+
+ m_doc->getStudio().setMIDIThruFilter(thruFilter);
+ m_doc->getStudio().setMIDIRecordFilter(recordFilter);
+
+ if (m_doc->getSequenceManager()) {
+ m_doc->getSequenceManager()->filtersChanged(thruFilter, recordFilter);
+ }
+
+ setModified(false);
+}
+
+void
+MidiFilterDialog::slotOk()
+{
+ slotApply();
+ accept();
+}
+
+void
+MidiFilterDialog::slotSetModified()
+{
+ setModified(true);
+}
+
+void
+MidiFilterDialog::setModified(bool value)
+{
+ if (m_modified == value)
+ return ;
+
+ if (value) {
+ enableButtonApply(true);
+ } else {
+ enableButtonApply(false);
+ }
+
+ m_modified = value;
+
+}
+
+}
+#include "MidiFilterDialog.moc"
diff --git a/src/gui/seqmanager/MidiFilterDialog.h b/src/gui/seqmanager/MidiFilterDialog.h
new file mode 100644
index 0000000..06099ac
--- /dev/null
+++ b/src/gui/seqmanager/MidiFilterDialog.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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_MIDIFILTERDIALOG_H_
+#define _RG_MIDIFILTERDIALOG_H_
+
+#include <kdialogbase.h>
+
+
+class QWidget;
+class QButtonGroup;
+
+
+namespace Rosegarden
+{
+
+class RosegardenGUIDoc;
+
+
+class MidiFilterDialog : public KDialogBase
+{
+ Q_OBJECT
+public:
+ MidiFilterDialog(QWidget *parent,
+ RosegardenGUIDoc *doc);
+
+ void setModified(bool value);
+
+public slots:
+
+ void slotOk();
+ void slotApply();
+ void slotSetModified();
+
+protected:
+
+ RosegardenGUIDoc *m_doc;
+
+ QButtonGroup *m_thruBox;
+ QButtonGroup *m_recordBox;
+
+ bool m_modified;
+
+};
+
+
+}
+
+#endif
diff --git a/src/gui/seqmanager/SegmentMmapper.cpp b/src/gui/seqmanager/SegmentMmapper.cpp
new file mode 100644
index 0000000..5cf117d
--- /dev/null
+++ b/src/gui/seqmanager/SegmentMmapper.cpp
@@ -0,0 +1,562 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
+
+/*
+ Rosegarden
+ A MIDI and audio sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "SegmentMmapper.h"
+#include "misc/Debug.h"
+
+#include "misc/Strings.h"
+#include "base/BaseProperties.h"
+#include "base/Composition.h"
+#include "base/Event.h"
+#include "base/Exception.h"
+#include "base/NotationTypes.h"
+#include "base/RealTime.h"
+#include "base/Segment.h"
+#include "base/SegmentPerformanceHelper.h"
+#include "base/TriggerSegment.h"
+#include "document/RosegardenGUIDoc.h"
+#include "sound/MappedEvent.h"
+#include <qfile.h>
+#include <qstring.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <sys/mman.h>
+#include <errno.h>
+
+
+namespace Rosegarden
+{
+
+SegmentMmapper::SegmentMmapper(RosegardenGUIDoc* doc,
+ Segment* segment, const QString& fileName)
+ : m_doc(doc),
+ m_segment(segment),
+ m_fileName(fileName),
+ m_fd( -1),
+ m_mmappedSize(0),
+ m_mmappedRegion(0),
+ m_mmappedEventBuffer((MappedEvent*)0)
+{
+ SEQMAN_DEBUG << "SegmentMmapper : " << this
+ << " trying to mmap " << m_fileName
+ << endl;
+
+ m_fd = ::open(m_fileName.latin1(), O_RDWR | O_CREAT | O_TRUNC,
+ S_IRUSR | S_IWUSR);
+ if (m_fd < 0) {
+ perror("SegmentMmapper::SegmentMmapper: Failed to open mmap file for writing");
+ SEQMAN_DEBUG << "SegmentMmapper : Couldn't open " << m_fileName
+ << endl;
+ throw Exception("Couldn't open " + qstrtostr(m_fileName));
+ }
+
+ // SEQMAN_DEBUG << "SegmentMmapper : mmap size = " << m_mmappedSize
+ // << endl;
+}
+
+void SegmentMmapper::init()
+{
+ m_mmappedSize = computeMmappedSize() + sizeof(size_t);
+
+ if (m_mmappedSize > 0) {
+ setFileSize(m_mmappedSize);
+ doMmap();
+ dump();
+ if (m_segment != 0) {
+ SEQMAN_DEBUG << "SegmentMmapper::init : mmap size = " << m_mmappedSize
+ << " for segment " << m_segment->getLabel() << endl;
+ }
+
+ } else {
+ SEQMAN_DEBUG << "SegmentMmapper::init : mmap size = 0 - skipping mmapping for now\n";
+ }
+}
+
+SegmentMmapper::~SegmentMmapper()
+{
+ SEQMAN_DEBUG << "~SegmentMmapper : " << this
+ << " unmapping " << (void*)m_mmappedRegion
+ << " of size " << m_mmappedSize
+ << endl;
+
+ if (m_mmappedRegion && m_mmappedSize)
+ ::munmap(m_mmappedRegion, m_mmappedSize);
+
+ ::close(m_fd);
+ SEQMAN_DEBUG << "~SegmentMmapper : removing " << m_fileName << endl;
+
+ QFile::remove
+ (m_fileName);
+}
+
+bool SegmentMmapper::refresh()
+{
+ bool res = false;
+
+ size_t newMmappedSize = computeMmappedSize() + sizeof(size_t);
+
+ SEQMAN_DEBUG << "SegmentMmapper::refresh() - " << getFileName()
+ << " - m_mmappedRegion = " << (void*)m_mmappedRegion
+ << " - m_mmappedEventBuffer = " << (void*)m_mmappedEventBuffer
+ << " - new size = " << newMmappedSize
+ << " - old size = " << m_mmappedSize
+ << endl;
+
+ // We can't zero out the buffer here because if the mmapped
+ // segment is being read from by the sequencer in the interval of
+ // time between the memset() and the dump(), the sequencer will go
+ // over all the zeros up to the end of the segment and reach its
+ // end, and therefore will stop playing it.
+ //
+
+ if (newMmappedSize != m_mmappedSize) {
+
+ res = true;
+
+ if (newMmappedSize == 0) {
+
+ // nothing to do, just msync and go
+ ::msync(m_mmappedRegion, m_mmappedSize, MS_ASYNC);
+ m_mmappedSize = 0;
+ return true;
+
+ } else {
+
+ setFileSize(newMmappedSize);
+ remap(newMmappedSize);
+ }
+ }
+
+ dump();
+
+ return res;
+}
+
+void SegmentMmapper::setFileSize(size_t size)
+{
+ SEQMAN_DEBUG << "SegmentMmapper::setFileSize() : setting size of "
+ << m_fileName << " to " << size
+ << " - current size = " << m_mmappedSize << endl;
+
+ if (size < m_mmappedSize) {
+
+ // Don't truncate the file here: that will cause trouble for
+ // the sequencer
+ // ftruncate(m_fd, size);
+
+ } else {
+
+ // On linux, ftruncate can enlarge a file, but this isn't specified by POSIX
+ // so go the safe way
+
+ if (size == 0) {
+ SEQMAN_DEBUG << "SegmentMmapper : size == 0 : no resize to do\n";
+ return ;
+ }
+
+ // rewind
+ ::lseek(m_fd, 0, SEEK_SET);
+
+ //
+ // enlarge the file
+ // (seek() to wanted size, then write a byte)
+ //
+ if (::lseek(m_fd, size - 1, SEEK_SET) == -1) {
+ std::cerr << "WARNING: SegmentMmapper : Couldn't lseek in "
+ << m_fileName << " to " << size << std::endl;
+ throw Exception("lseek failed");
+ }
+
+ if (::write(m_fd, "\0", 1) != 1) {
+ std::cerr << "WARNING: SegmentMmapper : Couldn't write byte in "
+ << m_fileName << std::endl;
+ throw Exception("write failed");
+ }
+
+ }
+
+
+}
+
+void SegmentMmapper::remap(size_t newsize)
+{
+ SEQMAN_DEBUG << "SegmentMmapper : remapping " << m_fileName
+ << " from size " << m_mmappedSize
+ << " to size " << newsize << endl;
+
+ if (!m_mmappedRegion) { // nothing to mremap, just mmap
+
+ SEQMAN_DEBUG << "SegmentMmapper : nothing to remap - mmap instead\n";
+ m_mmappedSize = newsize;
+ doMmap();
+
+ } else {
+
+#ifdef linux
+ void *oldBuffer = m_mmappedRegion;
+ m_mmappedRegion = (MappedEvent*)::mremap(m_mmappedRegion, m_mmappedSize,
+ newsize, MREMAP_MAYMOVE);
+ m_mmappedEventBuffer = (MappedEvent *)((size_t *)m_mmappedRegion + 1);
+
+ if (m_mmappedRegion != oldBuffer) {
+ SEQMAN_DEBUG << "NOTE: buffer moved from " << oldBuffer <<
+ " to " << (void *)m_mmappedRegion << endl;
+ }
+#else
+ ::munmap(m_mmappedRegion, m_mmappedSize);
+ m_mmappedRegion = ::mmap(0, newsize,
+ PROT_READ | PROT_WRITE,
+ MAP_SHARED, m_fd, 0);
+ m_mmappedEventBuffer = (MappedEvent *)((size_t *)m_mmappedRegion + 1);
+#endif
+
+ if (m_mmappedRegion == (void*) - 1) {
+ SEQMAN_DEBUG << QString("mremap failed : (%1) %2\n").arg(errno).arg(strerror(errno));
+ throw Exception("mremap failed");
+ }
+
+ m_mmappedSize = newsize;
+ }
+}
+
+void SegmentMmapper::doMmap()
+{
+ //
+ // mmap() file for writing
+ //
+ m_mmappedRegion = ::mmap(0, m_mmappedSize,
+ PROT_READ | PROT_WRITE,
+ MAP_SHARED, m_fd, 0);
+ m_mmappedEventBuffer = (MappedEvent *)((size_t *)m_mmappedRegion + 1);
+
+ if (m_mmappedRegion == (void*) - 1) {
+ SEQMAN_DEBUG << QString("mmap failed : (%1) %2\n").arg(errno).arg(strerror(errno));
+ throw Exception("mmap failed");
+ }
+
+ SEQMAN_DEBUG << "SegmentMmapper::doMmap() - mmap size : " << m_mmappedSize
+ << " at " << (void*)m_mmappedRegion << endl;
+
+}
+
+void SegmentMmapper::dump()
+{
+ Composition &comp = m_doc->getComposition();
+
+ RealTime eventTime;
+ RealTime duration;
+ Track* track = comp.getTrackById(m_segment->getTrack());
+
+ timeT segmentStartTime = m_segment->getStartTime();
+ timeT segmentEndTime = m_segment->getEndMarkerTime();
+ timeT segmentDuration = segmentEndTime - segmentStartTime;
+ timeT repeatEndTime = segmentEndTime;
+
+ int repeatCount = getSegmentRepeatCount();
+
+ if (repeatCount > 0)
+ repeatEndTime = m_segment->getRepeatEndTime();
+
+ MappedEvent* bufPos = m_mmappedEventBuffer;
+
+ for (int repeatNo = 0; repeatNo <= repeatCount; ++repeatNo) {
+
+ Segment *triggered = 0;
+ Segment::iterator *i = 0;
+
+ for (Segment::iterator j = m_segment->begin();
+ m_segment->isBeforeEndMarker(j) || (i && *i != triggered->end()); ) {
+
+ bool usingi = false;
+ Segment::iterator *k = &j;
+
+ if (i && *i != triggered->end() &&
+ (!m_segment->isBeforeEndMarker(j) ||
+ (**i)->getAbsoluteTime() < (*j)->getAbsoluteTime())) {
+ k = i;
+ usingi = true;
+ }
+
+ if (!usingi) { // don't permit nested triggered segments
+
+ long triggerId = -1;
+ (**k)->get
+ <Int>(BaseProperties::TRIGGER_SEGMENT_ID, triggerId);
+
+ if (triggerId >= 0) {
+
+ TriggerSegmentRec *rec =
+ comp.getTriggerSegmentRec(triggerId);
+
+ if (rec && rec->getSegment()) {
+ timeT performanceDuration =
+ SegmentPerformanceHelper(*m_segment).
+ getSoundingDuration(j);
+ if (performanceDuration > 0) {
+ mergeTriggerSegment(&triggered, *j,
+ performanceDuration, rec);
+ size_t sz = addMmappedSize(rec->getSegment());
+ size_t offset = bufPos - m_mmappedEventBuffer;
+ setFileSize(sz);
+ remap(sz);
+ bufPos = m_mmappedEventBuffer + offset;
+ }
+ }
+
+ if (triggered) {
+ if (i)
+ delete i;
+ i = new Segment::iterator
+ (triggered->findTime((*j)->getAbsoluteTime()));
+ }
+
+ // Use the next triggered event (presumably the
+ // first of the current triggered segment) instead
+ // of the one that triggered it
+
+ ++j; // whatever happens, we don't want to write this one
+
+ if (i && *i != triggered->end() &&
+ (!m_segment->isBeforeEndMarker(j) ||
+ ((**i)->getAbsoluteTime() < (*j)->getAbsoluteTime()))) {
+ k = i;
+ usingi = true;
+ } else {
+ // no joy at all
+ continue;
+ }
+ }
+ }
+
+ // Ignore rests
+ //
+ if (!(**k)->isa(Note::EventRestType)) {
+
+ SegmentPerformanceHelper helper
+ (usingi ? *triggered : *m_segment);
+
+ timeT playTime =
+ helper.getSoundingAbsoluteTime(*k) + repeatNo * segmentDuration;
+ if (playTime >= repeatEndTime)
+ break;
+
+ timeT playDuration = helper.getSoundingDuration(*k);
+
+ // Ignore notes without duration -- they're probably in a tied
+ // series but not as first note
+ //
+ if (playDuration > 0 || !(**k)->isa(Note::EventType)) {
+
+ if (playTime + playDuration > repeatEndTime)
+ playDuration = repeatEndTime - playTime;
+
+ playTime = playTime + m_segment->getDelay();
+ eventTime = comp.getElapsedRealTime(playTime);
+
+ // slightly quicker than calling helper.getRealSoundingDuration()
+ duration =
+ comp.getElapsedRealTime(playTime + playDuration) - eventTime;
+
+ eventTime = eventTime + m_segment->getRealTimeDelay();
+
+ try {
+ // Create mapped event in mmapped buffer. The
+ // instrument will be extracted from the ControlBlock
+ // by the sequencer, so we set it to zero here.
+ MappedEvent *mE = new (bufPos)
+ MappedEvent(0,
+ ***k, // three stars! what an accolade
+ eventTime,
+ duration);
+ mE->setTrackId(track->getId());
+
+ if (m_segment->getTranspose() != 0 &&
+ (**k)->isa(Note::EventType)) {
+ mE->setPitch(mE->getPitch() + m_segment->getTranspose());
+ }
+
+ ++bufPos;
+
+ } catch (...) {
+ SEQMAN_DEBUG << "SegmentMmapper::dump - caught exception while trying to create MappedEvent\n";
+ }
+ }
+ }
+
+ ++*k; // increment either i or j, whichever one we just used
+ }
+
+ delete i;
+ delete triggered;
+ }
+
+ // Store the number of events at the start of the shared memory region
+ *(size_t *)m_mmappedRegion = (bufPos - m_mmappedEventBuffer);
+
+ size_t coveredArea = (bufPos - m_mmappedEventBuffer) * sizeof(MappedEvent);
+ memset(bufPos, 0, m_mmappedSize - coveredArea - sizeof(size_t));
+
+ ::msync(m_mmappedRegion, m_mmappedSize, MS_ASYNC);
+}
+
+void
+SegmentMmapper::mergeTriggerSegment(Segment **target,
+ Event *trigger,
+ timeT evDuration,
+ TriggerSegmentRec *rec)
+{
+ if (!rec || !rec->getSegment() || rec->getSegment()->empty())
+ return ;
+ if (!*target)
+ *target = new Segment;
+
+ timeT evTime = trigger->getAbsoluteTime();
+ timeT trStart = rec->getSegment()->getStartTime();
+ timeT trEnd = rec->getSegment()->getEndMarkerTime();
+ timeT trDuration = trEnd - trStart;
+ if (trDuration == 0)
+ return ;
+
+ bool retune = false;
+ std::string timeAdjust = BaseProperties::TRIGGER_SEGMENT_ADJUST_NONE;
+
+ trigger->get
+ <Bool>
+ (BaseProperties::TRIGGER_SEGMENT_RETUNE, retune);
+
+ trigger->get
+ <String>
+ (BaseProperties::TRIGGER_SEGMENT_ADJUST_TIMES, timeAdjust);
+
+ long evPitch = rec->getBasePitch();
+ (void)trigger->get
+ <Int>(BaseProperties::PITCH, evPitch);
+ int pitchDiff = evPitch - rec->getBasePitch();
+
+ long evVelocity = rec->getBaseVelocity();
+ (void)trigger->get
+ <Int>(BaseProperties::VELOCITY, evVelocity);
+ int velocityDiff = evVelocity - rec->getBaseVelocity();
+
+ timeT offset = 0;
+ if (timeAdjust == BaseProperties::TRIGGER_SEGMENT_ADJUST_SYNC_END) {
+ offset = evDuration - trDuration;
+ }
+
+ for (Segment::iterator i = rec->getSegment()->begin();
+ rec->getSegment()->isBeforeEndMarker(i); ++i) {
+
+ timeT t = (*i)->getAbsoluteTime() - trStart;
+ timeT d = (*i)->getDuration();
+
+ if (evDuration != trDuration &&
+ timeAdjust == BaseProperties::TRIGGER_SEGMENT_ADJUST_SQUISH) {
+ t = timeT(double(t * evDuration) / double(trDuration));
+ d = timeT(double(d * evDuration) / double(trDuration));
+ }
+
+ t += evTime + offset;
+
+ if (t < evTime) {
+ if (t + d <= evTime)
+ continue;
+ else {
+ d -= (evTime - t);
+ t = evTime;
+ }
+ }
+
+ if (timeAdjust == BaseProperties::TRIGGER_SEGMENT_ADJUST_SYNC_START) {
+ if (t + d > evTime + evDuration) {
+ if (t >= evTime + evDuration)
+ continue;
+ else {
+ d = evTime + evDuration - t;
+ }
+ }
+ }
+
+ Event *newEvent = new Event(**i, t, d);
+
+ if (retune && newEvent->has(BaseProperties::PITCH)) {
+ int pitch = newEvent->get
+ <Int>(BaseProperties::PITCH) + pitchDiff;
+ if (pitch > 127)
+ pitch = 127;
+ if (pitch < 0)
+ pitch = 0;
+ newEvent->set
+ <Int>(BaseProperties::PITCH, pitch);
+ }
+
+ if (newEvent->has(BaseProperties::VELOCITY)) {
+ int velocity = newEvent->get
+ <Int>(BaseProperties::VELOCITY) + velocityDiff;
+ if (velocity > 127)
+ velocity = 127;
+ if (velocity < 0)
+ velocity = 0;
+ newEvent->set
+ <Int>(BaseProperties::VELOCITY, velocity);
+ }
+
+ (*target)->insert(newEvent);
+ }
+}
+
+unsigned int SegmentMmapper::getSegmentRepeatCount()
+{
+ int repeatCount = 0;
+
+ timeT segmentStartTime = m_segment->getStartTime();
+ timeT segmentEndTime = m_segment->getEndMarkerTime();
+ timeT segmentDuration = segmentEndTime - segmentStartTime;
+ timeT repeatEndTime = segmentEndTime;
+
+ if (m_segment->isRepeating() && segmentDuration > 0) {
+ repeatEndTime = m_segment->getRepeatEndTime();
+ repeatCount = 1 + (repeatEndTime - segmentEndTime) / segmentDuration;
+ }
+
+ return repeatCount;
+}
+
+size_t SegmentMmapper::addMmappedSize(Segment *s)
+{
+ int repeatCount = getSegmentRepeatCount();
+ return m_mmappedSize + (repeatCount + 1) * s->size() * sizeof(MappedEvent);
+}
+
+size_t SegmentMmapper::computeMmappedSize()
+{
+ if (!m_segment) return 0;
+
+ int repeatCount = getSegmentRepeatCount();
+
+ return (repeatCount + 1) * m_segment->size() * sizeof(MappedEvent);
+}
+
+}
diff --git a/src/gui/seqmanager/SegmentMmapper.h b/src/gui/seqmanager/SegmentMmapper.h
new file mode 100644
index 0000000..ab25aab
--- /dev/null
+++ b/src/gui/seqmanager/SegmentMmapper.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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_SEGMENTMMAPPER_H_
+#define _RG_SEGMENTMMAPPER_H_
+
+#include <qstring.h>
+#include "base/Event.h"
+
+
+
+
+namespace Rosegarden
+{
+
+class TriggerSegmentRec;
+class Segment;
+class RosegardenGUIDoc;
+class MappedEvent;
+class Event;
+
+
+class SegmentMmapper
+{
+ friend class SegmentMmapperFactory;
+public:
+ virtual ~SegmentMmapper();
+
+ /**
+ * refresh the object after the segment has been modified
+ * returns true if size changed (and thus the sequencer
+ * needs to be told about it
+ */
+ bool refresh();
+
+ QString getFileName() { return m_fileName; }
+ size_t getFileSize() const { return m_mmappedSize; }
+
+ virtual unsigned int getSegmentRepeatCount();
+
+protected:
+ SegmentMmapper(RosegardenGUIDoc*, Segment*,
+ const QString& fileName);
+
+ virtual size_t computeMmappedSize();
+
+ virtual size_t addMmappedSize(Segment *);
+
+ /// actual setup, must be called after ctor, calls virtual methods
+ virtual void init();
+
+ /// set the size of the mmapped file
+ void setFileSize(size_t);
+
+ /// perform the mmap() of the file
+ void doMmap();
+
+ /// mremap() the file after a size change
+ void remap(size_t newsize);
+
+ /// dump all segment data in the file
+ virtual void dump();
+
+ void mergeTriggerSegment(Segment **target,
+ Event *trigger,
+ timeT performanceDuration,
+ TriggerSegmentRec *rec);
+
+ //--------------- Data members ---------------------------------
+ RosegardenGUIDoc* m_doc;
+ Segment* m_segment;
+ QString m_fileName;
+
+ int m_fd;
+ size_t m_mmappedSize;
+
+ // The shared memory region starts with a size_t value
+ // representing the number of MappedEvents that follow.
+ void *m_mmappedRegion;
+
+ // And this points to the next byte in the shared memory region.
+ MappedEvent* m_mmappedEventBuffer;
+};
+
+//----------------------------------------
+
+
+}
+
+#endif
diff --git a/src/gui/seqmanager/SegmentMmapperFactory.cpp b/src/gui/seqmanager/SegmentMmapperFactory.cpp
new file mode 100644
index 0000000..085d842
--- /dev/null
+++ b/src/gui/seqmanager/SegmentMmapperFactory.cpp
@@ -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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "SegmentMmapperFactory.h"
+
+#include "base/Segment.h"
+#include "document/RosegardenGUIDoc.h"
+#include "misc/Debug.h"
+#include "MetronomeMmapper.h"
+#include "SegmentMmapper.h"
+#include "AudioSegmentMmapper.h"
+#include "TempoSegmentMmapper.h"
+#include "TimeSigSegmentMmapper.h"
+#include <qstring.h>
+
+
+namespace Rosegarden
+{
+
+SegmentMmapper* SegmentMmapperFactory::makeMmapperForSegment(RosegardenGUIDoc* doc,
+ Rosegarden::Segment* segment,
+ const QString& fileName)
+{
+ SegmentMmapper* mmapper = 0;
+
+ if (segment == 0) {
+ SEQMAN_DEBUG << "SegmentMmapperFactory::makeMmapperForSegment() segment == 0\n";
+ return 0;
+ }
+
+ switch (segment->getType()) {
+ case Segment::Internal :
+ mmapper = new SegmentMmapper(doc, segment, fileName);
+ break;
+ case Segment::Audio :
+ mmapper = new AudioSegmentMmapper(doc, segment, fileName);
+ break;
+ default:
+ SEQMAN_DEBUG << "SegmentMmapperFactory::makeMmapperForSegment(" << segment
+ << ") : can't map, unknown segment type " << segment->getType() << endl;
+ mmapper = 0;
+ }
+
+ if (mmapper)
+ mmapper->init();
+
+ return mmapper;
+}
+
+MetronomeMmapper* SegmentMmapperFactory::makeMetronome(RosegardenGUIDoc* doc)
+{
+ MetronomeMmapper* mmapper = new MetronomeMmapper(doc);
+ mmapper->init();
+
+ return mmapper;
+}
+
+TimeSigSegmentMmapper* SegmentMmapperFactory::makeTimeSig(RosegardenGUIDoc* doc)
+{
+ TimeSigSegmentMmapper* mmapper = new TimeSigSegmentMmapper(doc, "rosegarden_timesig");
+
+ mmapper->init();
+ return mmapper;
+}
+
+TempoSegmentMmapper* SegmentMmapperFactory::makeTempo(RosegardenGUIDoc* doc)
+{
+ TempoSegmentMmapper* mmapper = new TempoSegmentMmapper(doc, "rosegarden_tempo");
+
+ mmapper->init();
+ return mmapper;
+}
+
+}
diff --git a/src/gui/seqmanager/SegmentMmapperFactory.h b/src/gui/seqmanager/SegmentMmapperFactory.h
new file mode 100644
index 0000000..2970024
--- /dev/null
+++ b/src/gui/seqmanager/SegmentMmapperFactory.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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_SEGMENTMMAPPERFACTORY_H_
+#define _RG_SEGMENTMMAPPERFACTORY_H_
+
+
+
+class QString;
+
+
+namespace Rosegarden
+{
+
+class TimeSigSegmentMmapper;
+class TempoSegmentMmapper;
+class SegmentMmapper;
+class Segment;
+class RosegardenGUIDoc;
+class MetronomeMmapper;
+
+
+class SegmentMmapperFactory
+{
+public:
+
+ static SegmentMmapper* makeMmapperForSegment(RosegardenGUIDoc*, Segment*,
+ const QString& fileName);
+
+ static MetronomeMmapper* makeMetronome(RosegardenGUIDoc*);
+ static TimeSigSegmentMmapper* makeTimeSig(RosegardenGUIDoc*);
+ static TempoSegmentMmapper* makeTempo(RosegardenGUIDoc*);
+};
+
+//----------------------------------------
+
+
+
+}
+
+#endif
diff --git a/src/gui/seqmanager/SequenceManager.cpp b/src/gui/seqmanager/SequenceManager.cpp
new file mode 100644
index 0000000..12334f6
--- /dev/null
+++ b/src/gui/seqmanager/SequenceManager.cpp
@@ -0,0 +1,2141 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
+
+/*
+ Rosegarden
+ A MIDI and audio sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "SequenceManager.h"
+
+#include "sound/Midi.h"
+#include "misc/Debug.h"
+#include "document/ConfigGroups.h"
+#include "base/Composition.h"
+#include "base/Device.h"
+#include "base/Exception.h"
+#include "base/Instrument.h"
+#include "base/MidiProgram.h"
+#include "base/RealTime.h"
+#include "base/Segment.h"
+#include "base/Studio.h"
+#include "base/Track.h"
+#include "base/TriggerSegment.h"
+#include "CompositionMmapper.h"
+#include "document/RosegardenGUIDoc.h"
+#include "document/MultiViewCommandHistory.h"
+#include "gui/application/RosegardenApplication.h"
+#include "gui/application/RosegardenGUIApp.h"
+#include "gui/application/RosegardenGUIView.h"
+#include "gui/dialogs/AudioManagerDialog.h"
+#include "gui/dialogs/CountdownDialog.h"
+#include "gui/dialogs/TransportDialog.h"
+#include "gui/kdeext/KStartupLogo.h"
+#include "gui/studio/StudioControl.h"
+#include "gui/widgets/CurrentProgressDialog.h"
+#include "MetronomeMmapper.h"
+#include "SegmentMmapperFactory.h"
+#include "SequencerMapper.h"
+#include "ControlBlockMmapper.h"
+#include "sound/AudioFile.h"
+#include "sound/MappedComposition.h"
+#include "sound/MappedEvent.h"
+#include "sound/MappedInstrument.h"
+#include "sound/SoundDriver.h"
+#include "TempoSegmentMmapper.h"
+#include "TimeSigSegmentMmapper.h"
+#include <klocale.h>
+#include <kstddirs.h>
+#include <kconfig.h>
+#include <kglobal.h>
+#include <kmessagebox.h>
+#include <qapplication.h>
+#include <qcstring.h>
+#include <qcursor.h>
+#include <qdatastream.h>
+#include <qevent.h>
+#include <qobject.h>
+#include <qpushbutton.h>
+#include <qstring.h>
+#include <qstringlist.h>
+#include <qtimer.h>
+#include <algorithm>
+
+
+namespace Rosegarden
+{
+
+SequenceManager::SequenceManager(RosegardenGUIDoc *doc,
+ TransportDialog *transport):
+ m_doc(doc),
+ m_compositionMmapper(new CompositionMmapper(m_doc)),
+ m_controlBlockMmapper(new ControlBlockMmapper(m_doc)),
+ m_metronomeMmapper(SegmentMmapperFactory::makeMetronome(m_doc)),
+ m_tempoSegmentMmapper(SegmentMmapperFactory::makeTempo(m_doc)),
+ m_timeSigSegmentMmapper(SegmentMmapperFactory::makeTimeSig(m_doc)),
+ m_transportStatus(STOPPED),
+ m_soundDriverStatus(NO_DRIVER),
+ m_transport(transport),
+ m_lastRewoundAt(clock()),
+ m_countdownDialog(0),
+ m_countdownTimer(new QTimer(m_doc)),
+ m_shownOverrunWarning(false),
+ m_recordTime(new QTime()),
+ m_compositionRefreshStatusId(m_doc->getComposition().getNewRefreshStatusId()),
+ m_updateRequested(true),
+ m_compositionMmapperResetTimer(new QTimer(m_doc)),
+ m_sequencerMapper(0),
+ m_reportTimer(new QTimer(m_doc)),
+ m_canReport(true),
+ m_lastLowLatencySwitchSent(false),
+ m_lastTransportStartPosition(0),
+ m_sampleRate(0)
+{
+ // Replaced this with a call to cleanup() from composition mmapper ctor:
+ // if done here, this removes the mmapped versions of any segments stored
+ // in the autoload (that have only just been mapped by the ctor!)
+ // m_compositionMmapper->cleanup();
+
+ m_countdownDialog = new CountdownDialog(dynamic_cast<QWidget*>
+ (m_doc->parent())->parentWidget());
+ // Connect these for use later
+ //
+ connect(m_countdownTimer, SIGNAL(timeout()),
+ this, SLOT(slotCountdownTimerTimeout()));
+
+ connect(m_reportTimer, SIGNAL(timeout()),
+ this, SLOT(slotAllowReport()));
+
+ connect(m_compositionMmapperResetTimer, SIGNAL(timeout()),
+ this, SLOT(slotScheduledCompositionMmapperReset()));
+
+
+ connect(doc->getCommandHistory(), SIGNAL(commandExecuted()),
+ this, SLOT(update()));
+
+ m_doc->getComposition().addObserver(this);
+
+ // The owner of this sequence manager will need to call
+ // checkSoundDriverStatus on it to set up its status appropriately
+ // immediately after construction; we used to do it from here but
+ // we're not well placed to handle reporting to the user if it
+ // throws an exception (and we don't want to leave the object half
+ // constructed).
+
+ // Try to map the sequencer file
+ //
+ mapSequencer();
+}
+
+SequenceManager::~SequenceManager()
+{
+ m_doc->getComposition().removeObserver(this);
+
+ SEQMAN_DEBUG << "SequenceManager::~SequenceManager()\n";
+ delete m_compositionMmapper;
+ delete m_controlBlockMmapper;
+ delete m_metronomeMmapper;
+ delete m_tempoSegmentMmapper;
+ delete m_timeSigSegmentMmapper;
+ delete m_sequencerMapper;
+}
+
+void SequenceManager::setDocument(RosegardenGUIDoc* doc)
+{
+ SEQMAN_DEBUG << "SequenceManager::setDocument(" << doc << ")\n";
+
+ DataBlockRepository::clear();
+
+ m_doc->getComposition().removeObserver(this);
+ disconnect(m_doc->getCommandHistory(), SIGNAL(commandExecuted()));
+
+ m_segments.clear();
+ m_triggerSegments.clear();
+
+ m_doc = doc;
+ Composition &comp = m_doc->getComposition();
+
+ // Must recreate and reconnect the countdown timer and dialog
+ // (bug 729039)
+ //
+ delete m_countdownDialog;
+ delete m_countdownTimer;
+ delete m_compositionMmapperResetTimer;
+
+ m_countdownDialog = new CountdownDialog(dynamic_cast<QWidget*>
+ (m_doc->parent())->parentWidget());
+
+ // Bug 933041: no longer connect the CountdownDialog from
+ // SequenceManager; instead let the RosegardenGUIApp connect it to
+ // its own slotStop to ensure the right housekeeping is done
+
+ m_countdownTimer = new QTimer(m_doc);
+
+ // Connect this for use later
+ //
+ connect(m_countdownTimer, SIGNAL(timeout()),
+ this, SLOT(slotCountdownTimerTimeout()));
+
+ m_compositionRefreshStatusId = comp.getNewRefreshStatusId();
+ comp.addObserver(this);
+
+ connect(m_doc->getCommandHistory(), SIGNAL(commandExecuted()),
+ this, SLOT(update()));
+
+ for (Composition::iterator i = comp.begin(); i != comp.end(); ++i) {
+
+ SEQMAN_DEBUG << "Adding segment with rid " << (*i)->getRuntimeId() << endl;
+
+ m_segments.insert(SegmentRefreshMap::value_type
+ (*i, (*i)->getNewRefreshStatusId()));
+ }
+
+ for (Composition::triggersegmentcontaineriterator i =
+ comp.getTriggerSegments().begin();
+ i != comp.getTriggerSegments().end(); ++i) {
+ m_triggerSegments.insert(SegmentRefreshMap::value_type
+ ((*i)->getSegment(),
+ (*i)->getSegment()->getNewRefreshStatusId()));
+ }
+
+
+ m_compositionMmapperResetTimer = new QTimer(m_doc);
+ connect(m_compositionMmapperResetTimer, SIGNAL(timeout()),
+ this, SLOT(slotScheduledCompositionMmapperReset()));
+
+ resetCompositionMmapper();
+
+ // Try to map the sequencer file
+ //
+ mapSequencer();
+}
+
+void
+SequenceManager::setTransportStatus(const TransportStatus &status)
+{
+ m_transportStatus = status;
+}
+
+void
+SequenceManager::mapSequencer()
+{
+ if (m_sequencerMapper)
+ return ;
+
+ try {
+ m_sequencerMapper = new SequencerMapper(
+ KGlobal::dirs()->resourceDirs("tmp").last() + "/rosegarden_sequencer_timing_block");
+ } catch (Exception) {
+ m_sequencerMapper = 0;
+ }
+}
+
+bool
+SequenceManager::play()
+{
+ mapSequencer();
+
+ Composition &comp = m_doc->getComposition();
+
+ // If already playing or recording then stop
+ //
+ if (m_transportStatus == PLAYING ||
+ m_transportStatus == RECORDING ) {
+ stopping();
+ return true;
+ }
+
+ // This check may throw an exception
+ checkSoundDriverStatus(false);
+
+ // Align Instrument lists and send initial program changes
+ //
+ preparePlayback();
+
+ m_lastTransportStartPosition = comp.getPosition();
+
+ // Update play metronome status
+ //
+ m_controlBlockMmapper->updateMetronomeData
+ (m_metronomeMmapper->getMetronomeInstrument());
+ m_controlBlockMmapper->updateMetronomeForPlayback();
+
+ // make sure we toggle the play button
+ //
+ m_transport->PlayButton()->setOn(true);
+
+ //!!! disable the record button, because recording while playing is horribly
+ // broken, and disabling it is less complicated than fixing it
+ // see #1223025 - DMM
+ // SEQMAN_DEBUG << "SequenceManager::play() - disabling record button, as we are playing\n";
+ // m_transport->RecordButton()->setEnabled(false);
+
+ if (comp.getCurrentTempo() == 0) {
+ comp.setCompositionDefaultTempo(comp.getTempoForQpm(120.0));
+
+ SEQMAN_DEBUG << "SequenceManager::play() - setting Tempo to Default value of 120.000\n";
+ } else {
+ SEQMAN_DEBUG << "SequenceManager::play() - starting to play\n";
+ }
+
+ // Send initial tempo
+ //
+ double qnD = 60.0 / comp.getTempoQpm(comp.getCurrentTempo());
+ RealTime qnTime =
+ RealTime(long(qnD),
+ long((qnD - double(long(qnD))) * 1000000000.0));
+ StudioControl::sendQuarterNoteLength(qnTime);
+
+ // set the tempo in the transport
+ m_transport->setTempo(comp.getCurrentTempo());
+
+ // The arguments for the Sequencer
+ RealTime startPos = comp.getElapsedRealTime(comp.getPosition());
+
+ // If we're looping then jump to loop start
+ if (comp.isLooping())
+ startPos = comp.getElapsedRealTime(comp.getLoopStart());
+
+ KConfig* config = kapp->config();
+ config->setGroup(SequencerOptionsConfigGroup);
+
+ bool lowLat = config->readBoolEntry("audiolowlatencymonitoring", true);
+
+ if (lowLat != m_lastLowLatencySwitchSent) {
+
+ QByteArray data;
+ QDataStream streamOut(data, IO_WriteOnly);
+ streamOut << lowLat;
+
+ rgapp->sequencerSend("setLowLatencyMode(bool)", data);
+ m_lastLowLatencySwitchSent = lowLat;
+ }
+
+ QByteArray data;
+ QCString replyType;
+ QByteArray replyData;
+ QDataStream streamOut(data, IO_WriteOnly);
+
+ // playback start position
+ streamOut << (long)startPos.sec;
+ streamOut << (long)startPos.nsec;
+
+ // Apart from perhaps the small file size, I think with hindsight
+ // that these options are more easily set to reasonable defaults
+ // here than left to the user. Mostly.
+
+ //!!! need some cleverness somewhere to ensure the read-ahead
+ //is larger than the JACK period size
+
+ if (lowLat) {
+ streamOut << 0L; // read-ahead sec
+ streamOut << 160000000L; // read-ahead nsec
+ streamOut << 0L; // audio mix sec
+ streamOut << 60000000L; // audio mix nsec: ignored in lowlat mode
+ streamOut << 2L; // audio read sec
+ streamOut << 500000000L; // audio read nsec
+ streamOut << 4L; // audio write sec
+ streamOut << 0L; // audio write nsec
+ streamOut << 256L; // cacheable small file size in K
+ } else {
+ streamOut << 0L; // read-ahead sec
+ streamOut << 500000000L; // read-ahead nsec
+ streamOut << 0L; // audio mix sec
+ streamOut << 400000000L; // audio mix nsec
+ streamOut << 2L; // audio read sec
+ streamOut << 500000000L; // audio read nsec
+ streamOut << 4L; // audio write sec
+ streamOut << 0L; // audio write nsec
+ streamOut << 256L; // cacheable small file size in K
+ }
+
+ // Send Play to the Sequencer
+ if (!rgapp->sequencerCall("play(long int, long int, long int, long int, long int, long int, long int, long int, long int, long int, long int)",
+ replyType, replyData, data)) {
+ m_transportStatus = STOPPED;
+ return false;
+ }
+
+ // ensure the return type is ok
+ QDataStream streamIn(replyData, IO_ReadOnly);
+ int result;
+ streamIn >> result;
+
+ if (result) {
+ // completed successfully
+ m_transportStatus = STARTING_TO_PLAY;
+ } else {
+ m_transportStatus = STOPPED;
+ std::cerr << "ERROR: SequenceManager::play(): Failed to start playback!" << std::endl;
+ }
+
+ return false;
+}
+
+void
+SequenceManager::stopping()
+{
+ if (m_countdownTimer)
+ m_countdownTimer->stop();
+ if (m_countdownDialog)
+ m_countdownDialog->hide();
+
+ // Do this here rather than in stop() to avoid any potential
+ // race condition (we use setPointerPosition() during stop()).
+ //
+ if (m_transportStatus == STOPPED) {
+ /*!!!
+ if (m_doc->getComposition().isLooping())
+ m_doc->slotSetPointerPosition(m_doc->getComposition().getLoopStart());
+ else
+ m_doc->slotSetPointerPosition(m_doc->getComposition().getStartMarker());
+ */
+ m_doc->slotSetPointerPosition(m_lastTransportStartPosition);
+
+ return ;
+ }
+
+ // Disarm recording and drop back to STOPPED
+ //
+ if (m_transportStatus == RECORDING_ARMED) {
+ m_transportStatus = STOPPED;
+ m_transport->RecordButton()->setOn(false);
+ m_transport->MetronomeButton()->
+ setOn(m_doc->getComposition().usePlayMetronome());
+ return ;
+ }
+
+ SEQMAN_DEBUG << "SequenceManager::stopping() - preparing to stop\n";
+
+ // SEQMAN_DEBUG << kdBacktrace() << endl;
+
+ stop();
+
+ m_shownOverrunWarning = false;
+}
+
+void
+SequenceManager::stop()
+{
+ // Toggle off the buttons - first record
+ //
+ if (m_transportStatus == RECORDING) {
+ m_transport->RecordButton()->setOn(false);
+ m_transport->MetronomeButton()->
+ setOn(m_doc->getComposition().usePlayMetronome());
+
+ // Remove the countdown dialog and stop the timer
+ //
+ m_countdownDialog->hide();
+ m_countdownTimer->stop();
+ }
+
+ // Now playback
+ m_transport->PlayButton()->setOn(false);
+
+ // re-enable the record button if it was previously disabled when
+ // going into play mode - DMM
+ // SEQMAN_DEBUG << "SequenceManager::stop() - re-enabling record button\n";
+ // m_transport->RecordButton()->setEnabled(true);
+
+
+ // "call" the sequencer with a stop so we get a synchronous
+ // response - then we can fiddle about with the audio file
+ // without worrying about the sequencer causing problems
+ // with access to the same audio files.
+ //
+
+ // wait cursor
+ //
+ QApplication::setOverrideCursor(QCursor(Qt::waitCursor));
+
+ QCString replyType;
+ QByteArray replyData;
+
+ bool failed = false;
+ if (!rgapp->sequencerCall("stop()", replyType, replyData)) {
+ failed = true;
+ }
+
+ // restore
+ QApplication::restoreOverrideCursor();
+
+ TransportStatus status = m_transportStatus;
+
+ // set new transport status first, so that if we're stopping
+ // recording we don't risk the record segment being restored by a
+ // timer while the document is busy trying to do away with it
+ m_transportStatus = STOPPED;
+
+ // if we're recording MIDI or Audio then tidy up the recording Segment
+ if (status == RECORDING) {
+ m_doc->stopRecordingMidi();
+ m_doc->stopRecordingAudio();
+
+ SEQMAN_DEBUG << "SequenceManager::stop() - stopped recording\n";
+ }
+
+ // always untoggle the play button at this stage
+ //
+ m_transport->PlayButton()->setOn(false);
+ SEQMAN_DEBUG << "SequenceManager::stop() - stopped playing\n";
+
+ // We don't reset controllers at this point - what happens with static
+ // controllers the next time we play otherwise? [rwb]
+ //resetControllers();
+
+ if (failed) {
+ throw(Exception("Failed to contact Rosegarden sequencer with stop command. Please save your composition and restart Rosegarden to continue."));
+ }
+}
+
+void
+SequenceManager::rewind()
+{
+ Composition &composition = m_doc->getComposition();
+
+ timeT position = composition.getPosition();
+ std::pair<timeT, timeT> barRange =
+ composition.getBarRangeForTime(position - 1);
+
+ if (m_transportStatus == PLAYING) {
+
+ // if we're playing and we had a rewind request less than 200ms
+ // ago and we're some way into the bar but less than half way
+ // through it, rewind two barlines instead of one
+
+ clock_t now = clock();
+ int elapsed = (now - m_lastRewoundAt) * 1000 / CLOCKS_PER_SEC;
+
+ SEQMAN_DEBUG << "That was " << m_lastRewoundAt << ", this is " << now << ", elapsed is " << elapsed << endl;
+
+ if (elapsed >= 0 && elapsed <= 200) {
+ if (position > barRange.first &&
+ position < barRange.second &&
+ position <= (barRange.first + (barRange.second -
+ barRange.first) / 2)) {
+ barRange = composition.getBarRangeForTime(barRange.first - 1);
+ }
+ }
+
+ m_lastRewoundAt = now;
+ }
+
+ if (barRange.first < composition.getStartMarker()) {
+ m_doc->slotSetPointerPosition(composition.getStartMarker());
+ } else {
+ m_doc->slotSetPointerPosition(barRange.first);
+ }
+}
+
+void
+SequenceManager::fastforward()
+{
+ Composition &composition = m_doc->getComposition();
+
+ timeT position = composition.getPosition() + 1;
+ timeT newPosition = composition.getBarRangeForTime(position).second;
+
+ // Don't skip past end marker
+ //
+ if (newPosition > composition.getEndMarker())
+ newPosition = composition.getEndMarker();
+
+ m_doc->slotSetPointerPosition(newPosition);
+
+}
+
+void
+SequenceManager::notifySequencerStatus(TransportStatus status)
+{
+ // for the moment we don't do anything fancy
+ m_transportStatus = status;
+
+}
+
+void
+SequenceManager::sendSequencerJump(const RealTime &time)
+{
+ QByteArray data;
+ QDataStream streamOut(data, IO_WriteOnly);
+ streamOut << (long)time.sec;
+ streamOut << (long)time.nsec;
+
+ rgapp->sequencerSend("jumpTo(long int, long int)", data);
+}
+
+void
+SequenceManager::record(bool toggled)
+{
+ mapSequencer();
+
+ SEQMAN_DEBUG << "SequenceManager::record(" << toggled << ")" << endl;
+
+ Composition &comp = m_doc->getComposition();
+ Studio &studio = m_doc->getStudio();
+ KConfig* config = kapp->config();
+ config->setGroup(GeneralOptionsConfigGroup);
+
+ bool punchIn = false; // are we punching in?
+
+ // If we have any audio tracks armed, then we need to check for
+ // a valid audio record path and a working audio subsystem before
+ // we go any further
+
+ const Composition::recordtrackcontainer &recordTracks =
+ comp.getRecordTracks();
+
+ for (Composition::recordtrackcontainer::const_iterator i =
+ recordTracks.begin();
+ i != recordTracks.end(); ++i) {
+
+ Track *track = comp.getTrackById(*i);
+ InstrumentId instrId = track->getInstrument();
+ Instrument *instr = studio.getInstrumentById(instrId);
+
+ if (instr && instr->getType() == Instrument::Audio) {
+ if (!m_doc || !(m_soundDriverStatus & AUDIO_OK)) {
+ m_transport->RecordButton()->setOn(false);
+ throw(Exception("Audio subsystem is not available - can't record audio"));
+ }
+ // throws BadAudioPathException if path is not valid:
+ m_doc->getAudioFileManager().testAudioPath();
+ break;
+ }
+ }
+
+ if (toggled) { // preparing record or punch-in record
+
+ if (m_transportStatus == RECORDING_ARMED) {
+ SEQMAN_DEBUG << "SequenceManager::record - unarming record\n";
+ m_transportStatus = STOPPED;
+
+ // Toggle the buttons
+ m_transport->MetronomeButton()->setOn(comp.usePlayMetronome());
+ m_transport->RecordButton()->setOn(false);
+
+ return ;
+ }
+
+ if (m_transportStatus == STOPPED) {
+ SEQMAN_DEBUG << "SequenceManager::record - armed record\n";
+ m_transportStatus = RECORDING_ARMED;
+
+ // Toggle the buttons
+ m_transport->MetronomeButton()->setOn(comp.useRecordMetronome());
+ m_transport->RecordButton()->setOn(true);
+
+ return ;
+ }
+
+ if (m_transportStatus == RECORDING) {
+ SEQMAN_DEBUG << "SequenceManager::record - stop recording and keep playing\n";
+
+ QByteArray data;
+ QCString replyType;
+ QByteArray replyData;
+
+ // Send Record to the Sequencer to signal it to drop out of record mode
+ if (!rgapp->sequencerCall("punchOut()", replyType, replyData, data)) {
+ SEQMAN_DEBUG << "SequenceManager::record - the \"not very plausible\" code executed\n";
+ // #1797873 - set new transport status first, so that
+ // if we're stopping recording we don't risk the
+ // record segment being restored by a timer while the
+ // document is busy trying to do away with it
+ m_transportStatus = STOPPED;
+
+ m_doc->stopRecordingMidi();
+ m_doc->stopRecordingAudio();
+ return ;
+ }
+
+ // #1797873 - as above
+ m_transportStatus = PLAYING;
+
+ m_doc->stopRecordingMidi();
+ m_doc->stopRecordingAudio();
+
+ return ;
+ }
+
+ if (m_transportStatus == PLAYING) {
+ SEQMAN_DEBUG << "SequenceManager::record - punch in recording\n";
+ punchIn = true;
+ goto punchin;
+ }
+
+ } else {
+
+ m_lastTransportStartPosition = comp.getPosition();
+
+punchin:
+
+ // Get the record tracks and check we have a record instrument
+
+ bool haveInstrument = false;
+ bool haveAudioInstrument = false;
+ bool haveMIDIInstrument = false;
+ //TrackId recordMIDITrack = 0;
+
+ for (Composition::recordtrackcontainer::const_iterator i =
+ comp.getRecordTracks().begin();
+ i != comp.getRecordTracks().end(); ++i) {
+
+ InstrumentId iid =
+ comp.getTrackById(*i)->getInstrument();
+
+ Instrument *inst = studio.getInstrumentById(iid);
+ if (inst) {
+ haveInstrument = true;
+ if (inst->getType() == Instrument::Audio) {
+ haveAudioInstrument = true;
+ break;
+ } else { // soft synths count as MIDI for our purposes here
+ haveMIDIInstrument = true;
+ //recordMIDITrack = *i;
+ }
+ }
+ }
+
+ if (!haveInstrument) {
+ m_transport->RecordButton()->setDown(false);
+ throw(Exception("No Record instrument selected"));
+ }
+
+ // may throw an exception
+ checkSoundDriverStatus(false);
+
+ // toggle the Metronome button if it's in use
+ m_transport->MetronomeButton()->setOn(comp.useRecordMetronome());
+
+ // Update record metronome status
+ //
+ m_controlBlockMmapper->updateMetronomeData
+ (m_metronomeMmapper->getMetronomeInstrument());
+ m_controlBlockMmapper->updateMetronomeForRecord();
+
+ // If we are looping then jump to start of loop and start recording,
+ // if we're not take off the number of count-in bars and start
+ // recording.
+ //
+ if (comp.isLooping())
+ m_doc->slotSetPointerPosition(comp.getLoopStart());
+ else {
+ if (m_transportStatus != RECORDING_ARMED && punchIn == false) {
+ int startBar = comp.getBarNumber(comp.getPosition());
+ startBar -= config->readUnsignedNumEntry("countinbars", 0);
+ m_doc->slotSetPointerPosition(comp.getBarRange(startBar).first);
+ }
+ }
+
+ m_doc->setRecordStartTime(m_doc->getComposition().getPosition());
+
+ if (haveAudioInstrument) {
+ // Ask the document to update its record latencies so as to
+ // do latency compensation when we stop
+ m_doc->updateAudioRecordLatency();
+ }
+
+ if (haveMIDIInstrument) {
+ // Create the record MIDI segment now, so that the
+ // composition view has a real segment to display. It
+ // won't actually be added to the composition until the
+ // first recorded event arrives. We don't have to do this
+ // from here for audio, because for audio the sequencer
+ // calls back on createRecordAudioFiles so as to find out
+ // what files it needs to write to.
+ /*m_doc->addRecordMIDISegment(recordMIDITrack);*/
+ for (Composition::recordtrackcontainer::const_iterator i =
+ comp.getRecordTracks().begin(); i != comp.getRecordTracks().end(); ++i) {
+ InstrumentId iid = comp.getTrackById(*i)->getInstrument();
+ Instrument *inst = studio.getInstrumentById(iid);
+ if (inst && (inst->getType() != Instrument::Audio)) {
+ SEQMAN_DEBUG << "SequenceManager: mdoc->addRecordMIDISegment(" << *i << ")" << endl;
+ m_doc->addRecordMIDISegment(*i);
+ }
+ }
+ }
+
+ // set the buttons
+ m_transport->RecordButton()->setOn(true);
+ m_transport->PlayButton()->setOn(true);
+
+ if (comp.getCurrentTempo() == 0) {
+ SEQMAN_DEBUG << "SequenceManager::play() - setting Tempo to Default value of 120.000\n";
+ comp.setCompositionDefaultTempo(comp.getTempoForQpm(120.0));
+ } else {
+ SEQMAN_DEBUG << "SequenceManager::record() - starting to record\n";
+ }
+
+ // set the tempo in the transport
+ //
+ m_transport->setTempo(comp.getCurrentTempo());
+
+ // The arguments for the Sequencer - record is similar to playback,
+ // we must being playing to record.
+ //
+ RealTime startPos =
+ comp.getElapsedRealTime(comp.getPosition());
+
+ bool lowLat = config->readBoolEntry("audiolowlatencymonitoring", true);
+
+ if (lowLat != m_lastLowLatencySwitchSent) {
+
+ QByteArray data;
+ QDataStream streamOut(data, IO_WriteOnly);
+ streamOut << lowLat;
+
+ rgapp->sequencerSend("setLowLatencyMode(bool)", data);
+ m_lastLowLatencySwitchSent = lowLat;
+ }
+
+ QByteArray data;
+ QCString replyType;
+ QByteArray replyData;
+ QDataStream streamOut(data, IO_WriteOnly);
+
+ // playback start position
+ streamOut << (long)startPos.sec;
+ streamOut << (long)startPos.nsec;
+
+ // Apart from perhaps the small file size, I think with hindsight
+ // that these options are more easily set to reasonable defaults
+ // here than left to the user. Mostly.
+
+ //!!! Duplicates code in play()
+
+ //!!! need some cleverness somewhere to ensure the read-ahead
+ //is larger than the JACK period size
+
+ if (lowLat) {
+ streamOut << 0L; // read-ahead sec
+ streamOut << 160000000L; // read-ahead nsec
+ streamOut << 0L; // audio mix sec
+ streamOut << 60000000L; // audio mix nsec: ignored in lowlat mode
+ streamOut << 2L; // audio read sec
+ streamOut << 500000000L; // audio read nsec
+ streamOut << 4L; // audio write sec
+ streamOut << 0L; // audio write nsec
+ streamOut << 256L; // cacheable small file size in K
+ } else {
+ streamOut << 0L; // read-ahead sec
+ streamOut << 500000000L; // read-ahead nsec
+ streamOut << 0L; // audio mix sec
+ streamOut << 400000000L; // audio mix nsec
+ streamOut << 2L; // audio read sec
+ streamOut << 500000000L; // audio read nsec
+ streamOut << 4L; // audio write sec
+ streamOut << 0L; // audio write nsec
+ streamOut << 256L; // cacheable small file size in K
+ }
+
+ // record type
+ streamOut << (long)STARTING_TO_RECORD;
+
+ // Send Play to the Sequencer
+ if (!rgapp->sequencerCall("record(long int, long int, long int, long int, long int, long int, long int, long int, long int, long int, long int, long int)",
+ replyType, replyData, data)) {
+ // failed
+ m_transportStatus = STOPPED;
+ return ;
+ }
+
+ // ensure the return type is ok
+ QDataStream streamIn(replyData, IO_ReadOnly);
+ int result;
+ streamIn >> result;
+
+ if (result) {
+
+ // completed successfully
+ m_transportStatus = STARTING_TO_RECORD;
+
+ // Create the countdown timer dialog to show recording time
+ // remaining. (Note (dmm) this has changed, and it now reports
+ // the time remaining during both MIDI and audio recording.)
+ //
+ timeT p = comp.getPosition();
+ timeT d = comp.getEndMarker();
+ // end marker less current position == available duration
+ d -= p;
+
+ // set seconds to total possible time, initially
+ RealTime rtd = comp.getElapsedRealTime(d);
+ int seconds = rtd.sec;
+
+ // re-initialise
+ m_countdownDialog->setTotalTime(seconds);
+
+ // Create the timer
+ //
+ m_recordTime->start();
+
+ // Start an elapse timer for updating the dialog -
+ // it will fire every second.
+ //
+ m_countdownTimer->start(1000);
+
+ // Pop-up the dialog (don't use exec())
+ //
+ // bug #1505805, abolish recording countdown dialog
+ //m_countdownDialog->show();
+
+ } else {
+ // Stop immediately - turn off buttons in parent
+ //
+ m_transportStatus = STOPPED;
+
+ if (haveAudioInstrument) {
+ throw(Exception("Couldn't start recording audio.\nPlease set a valid file path in the Document Properties\n(Composition menu -> Edit Document Properties -> Audio)."));
+ }
+ }
+ }
+}
+
+void
+SequenceManager::processAsynchronousMidi(const MappedComposition &mC,
+ AudioManagerDialog *audioManagerDialog)
+{
+ static bool boolShowingWarning = false;
+ static bool boolShowingALSAWarning = false;
+ static long warningShownAt = 0;
+
+ if (m_doc == 0 || mC.size() == 0)
+ return ;
+
+ MappedComposition::const_iterator i;
+
+ // Thru filtering is done at the sequencer for the actual sound
+ // output, but here we need both filtered (for OUT display) and
+ // unfiltered (for insertable note callbacks) compositions, so
+ // we've received the unfiltered copy and will filter here
+ MappedComposition tempMC =
+ applyFiltering(mC,
+ MappedEvent::MappedEventType(
+ m_doc->getStudio().getMIDIThruFilter()));
+
+ // send to the MIDI labels (which can only hold one event at a time)
+ i = mC.begin();
+ if (i != mC.end()) {
+ m_transport->setMidiInLabel(*i);
+ }
+
+ i = tempMC.begin();
+ while (i != tempMC.end()) {
+ if ((*i)->getRecordedDevice() != Device::CONTROL_DEVICE) {
+ m_transport->setMidiOutLabel(*i);
+ break;
+ }
+ ++i;
+ }
+
+ for (i = mC.begin(); i != mC.end(); ++i ) {
+ if ((*i)->getType() >= MappedEvent::Audio) {
+ if ((*i)->getType() == MappedEvent::AudioStopped) {
+ /*
+ SEQMAN_DEBUG << "AUDIO FILE ID = "
+ << int((*i)->getData1())
+ << " - FILE STOPPED - "
+ << "INSTRUMENT = "
+ << (*i)->getInstrument()
+ << endl;
+ */
+
+ if (audioManagerDialog && (*i)->getInstrument() ==
+ m_doc->getStudio().getAudioPreviewInstrument()) {
+ audioManagerDialog->
+ closePlayingDialog(
+ AudioFileId((*i)->getData1()));
+ }
+ }
+
+ if ((*i)->getType() == MappedEvent::AudioLevel)
+ sendAudioLevel(*i);
+
+ if ((*i)->getType() ==
+ MappedEvent::AudioGeneratePreview) {
+ SEQMAN_DEBUG << "Received AudioGeneratePreview: data1 is " << int((*i)->getData1()) << ", data2 " << int((*i)->getData2()) << ", instrument is " << (*i)->getInstrument() << endl;
+
+ m_doc->finalizeAudioFile((int)(*i)->getData1() +
+ (int)(*i)->getData2() * 256);
+ }
+
+ if ((*i)->getType() ==
+ MappedEvent::SystemUpdateInstruments) {
+ // resync Devices and Instruments
+ //
+ m_doc->syncDevices();
+
+ /*KConfig* config = kapp->config();
+ config->setGroup(SequencerOptionsConfigGroup);
+ QString recordDeviceStr = config->readEntry("midirecorddevice");
+ sendMIDIRecordingDevice(recordDeviceStr);*/
+ restoreRecordSubscriptions();
+ }
+
+ if (m_transportStatus == PLAYING ||
+ m_transportStatus == RECORDING) {
+ if ((*i)->getType() == MappedEvent::SystemFailure) {
+
+ SEQMAN_DEBUG << "Failure of some sort..." << endl;
+
+ bool handling = true;
+
+ /* These are the ones that we always report or handle. */
+
+ if ((*i)->getData1() == MappedEvent::FailureJackDied) {
+
+ // Something horrible has happened to JACK or we got
+ // bumped out of the graph. Either way stop playback.
+ //
+ stopping();
+
+ } else if ((*i)->getData1() == MappedEvent::FailureJackRestartFailed) {
+
+ KMessageBox::error(
+ dynamic_cast<QWidget*>(m_doc->parent())->parentWidget(),
+ i18n("The JACK Audio subsystem has failed or it has stopped Rosegarden from processing audio.\nPlease restart Rosegarden to continue working with audio.\nQuitting other running applications may improve Rosegarden's performance."));
+
+ } else if ((*i)->getData1() == MappedEvent::FailureJackRestart) {
+
+ KMessageBox::error(
+ dynamic_cast<QWidget*>(m_doc->parent())->parentWidget(),
+ i18n("The JACK Audio subsystem has stopped Rosegarden from processing audio, probably because of a processing overload.\nAn attempt to restart the audio service has been made, but some problems may remain.\nQuitting other running applications may improve Rosegarden's performance."));
+
+ } else if ((*i)->getData1() == MappedEvent::FailureCPUOverload) {
+
+#define REPORT_CPU_OVERLOAD 1
+#ifdef REPORT_CPU_OVERLOAD
+
+ stopping();
+
+ KMessageBox::error(
+ dynamic_cast<QWidget*>(m_doc->parent())->parentWidget(),
+ i18n("Run out of processor power for real-time audio processing. Cannot continue."));
+
+#endif
+
+ } else {
+
+ handling = false;
+ }
+
+ if (handling)
+ continue;
+
+ if (!m_canReport) {
+ SEQMAN_DEBUG << "Not reporting it to user just yet"
+ << endl;
+ continue;
+ }
+
+ if ((*i)->getData1() == MappedEvent::FailureALSACallFailed) {
+
+ struct timeval tv;
+ (void)gettimeofday(&tv, 0);
+
+ if (tv.tv_sec - warningShownAt >= 5 &&
+ !boolShowingALSAWarning) {
+
+ QString message = i18n("A serious error has occurred in the ALSA MIDI subsystem. It may not be possible to continue sequencing. Please check console output for more information.");
+ boolShowingALSAWarning = true;
+
+ KMessageBox::information(0, message);
+ boolShowingALSAWarning = false;
+
+ (void)gettimeofday(&tv, 0);
+ warningShownAt = tv.tv_sec;
+ }
+
+ } else if ((*i)->getData1() == MappedEvent::FailureXRuns) {
+
+ //#define REPORT_XRUNS 1
+#ifdef REPORT_XRUNS
+
+ struct timeval tv;
+ (void)gettimeofday(&tv, 0);
+
+ if (tv.tv_sec - warningShownAt >= 5 &&
+ !boolShowingWarning) {
+
+ QString message = i18n("JACK Audio subsystem is losing sample frames.");
+ boolShowingWarning = true;
+
+ KMessageBox::information(0, message);
+ boolShowingWarning = false;
+
+ (void)gettimeofday(&tv, 0);
+ warningShownAt = tv.tv_sec;
+ }
+#endif
+
+ } else if (!m_shownOverrunWarning) {
+
+ QString message;
+
+ switch ((*i)->getData1()) {
+
+ case MappedEvent::FailureDiscUnderrun:
+ message = i18n("Failed to read audio data from disc in time to service the audio subsystem.");
+ break;
+
+ case MappedEvent::FailureDiscOverrun:
+ message = i18n("Failed to write audio data to disc fast enough to service the audio subsystem.");
+ break;
+
+ case MappedEvent::FailureBussMixUnderrun:
+ message = i18n("The audio mixing subsystem is failing to keep up.");
+ break;
+
+ case MappedEvent::FailureMixUnderrun:
+ message = i18n("The audio subsystem is failing to keep up.");
+ break;
+
+ default:
+ message = i18n("Unknown sequencer failure mode!");
+ break;
+ }
+
+ m_shownOverrunWarning = true;
+
+#ifdef REPORT_XRUNS
+
+ KMessageBox::information(0, message);
+#else
+
+ if ((*i)->getData1() == MappedEvent::FailureDiscOverrun) {
+ // the error you can't hear
+ KMessageBox::information(0, message);
+ } else {
+ std::cerr << message << std::endl;
+ }
+#endif
+
+ }
+
+ // Turn off the report flag and set off a one-shot
+ // timer for 5 seconds.
+ //
+ if (!m_reportTimer->isActive()) {
+ m_canReport = false;
+ m_reportTimer->start(5000, true);
+ }
+ }
+ } else {
+ KStartupLogo::hideIfStillThere();
+
+ if ((*i)->getType() == MappedEvent::SystemFailure) {
+
+ if ((*i)->getData1() == MappedEvent::FailureJackRestartFailed) {
+
+ KMessageBox::error(
+ dynamic_cast<QWidget*>(m_doc->parent()),
+ i18n("The JACK Audio subsystem has failed or it has stopped Rosegarden from processing audio.\nPlease restart Rosegarden to continue working with audio.\nQuitting other running applications may improve Rosegarden's performance."));
+
+ } else if ((*i)->getData1() == MappedEvent::FailureJackRestart) {
+
+ KMessageBox::error(
+ dynamic_cast<QWidget*>(m_doc->parent()),
+ i18n("The JACK Audio subsystem has stopped Rosegarden from processing audio, probably because of a processing overload.\nAn attempt to restart the audio service has been made, but some problems may remain.\nQuitting other running applications may improve Rosegarden's performance."));
+
+ } else if ((*i)->getData1() == MappedEvent::WarningImpreciseTimer &&
+ shouldWarnForImpreciseTimer()) {
+
+ std::cerr << "Rosegarden: WARNING: No accurate sequencer timer available" << std::endl;
+
+ KStartupLogo::hideIfStillThere();
+ CurrentProgressDialog::freeze();
+
+ RosegardenGUIApp::self()->awaitDialogClearance();
+
+ KMessageBox::information(
+ dynamic_cast<QWidget*>(m_doc->parent()),
+ i18n("<h3>System timer resolution is too low</h3><p>Rosegarden was unable to find a high-resolution timing source for MIDI performance.</p><p>This may mean you are using a Linux system with the kernel timer resolution set too low. Please contact your Linux distributor for more information.</p><p>Some Linux distributors already provide low latency kernels, see <a href=\"http://rosegarden.wiki.sourceforge.net/Low+latency+kernels\">http://rosegarden.wiki.sourceforge.net/Low+latency+kernels</a> for instructions.</p>"),
+ NULL, NULL,
+ KMessageBox::Notify + KMessageBox::AllowLink);
+
+ CurrentProgressDialog::thaw();
+
+ } else if ((*i)->getData1() == MappedEvent::WarningImpreciseTimerTryRTC &&
+ shouldWarnForImpreciseTimer()) {
+
+ std::cerr << "Rosegarden: WARNING: No accurate sequencer timer available" << std::endl;
+
+ KStartupLogo::hideIfStillThere();
+ CurrentProgressDialog::freeze();
+
+ RosegardenGUIApp::self()->awaitDialogClearance();
+
+ KMessageBox::information(
+ dynamic_cast<QWidget*>(m_doc->parent()),
+ i18n("<h3>System timer resolution is too low</h3><p>Rosegarden was unable to find a high-resolution timing source for MIDI performance.</p><p>You may be able to solve this problem by loading the RTC timer kernel module. To do this, try running <b>sudo modprobe snd-rtctimer</b> in a terminal window and then restarting Rosegarden.</p><p>Alternatively, check whether your Linux distributor provides a multimedia-optimized kernel. See <a href=\"http://rosegarden.wiki.sourceforge.net/Low+latency+kernels\">http://rosegarden.wiki.sourceforge.net/Low+latency+kernels</a> for notes about this.</p>"),
+ NULL, NULL,
+ KMessageBox::Notify + KMessageBox::AllowLink);
+
+ CurrentProgressDialog::thaw();
+ }
+ }
+ }
+ }
+ }
+
+ // if we aren't playing or recording, consider invoking any
+ // step-by-step clients (using unfiltered composition). send
+ // out any incoming external controller events
+
+ for (i = mC.begin(); i != mC.end(); ++i ) {
+ if (m_transportStatus == STOPPED ||
+ m_transportStatus == RECORDING_ARMED) {
+ if ((*i)->getType() == MappedEvent::MidiNote) {
+ if ((*i)->getVelocity() == 0) {
+ emit insertableNoteOffReceived((*i)->getPitch(), (*i)->getVelocity());
+ } else {
+ emit insertableNoteOnReceived((*i)->getPitch(), (*i)->getVelocity());
+ }
+ }
+ }
+ if ((*i)->getRecordedDevice() == Device::CONTROL_DEVICE) {
+ SEQMAN_DEBUG << "controllerDeviceEventReceived" << endl;
+ emit controllerDeviceEventReceived(*i);
+ }
+ }
+}
+
+void
+SequenceManager::rewindToBeginning()
+{
+ SEQMAN_DEBUG << "SequenceManager::rewindToBeginning()\n";
+ m_doc->slotSetPointerPosition(m_doc->getComposition().getStartMarker());
+}
+
+void
+SequenceManager::fastForwardToEnd()
+{
+ SEQMAN_DEBUG << "SequenceManager::fastForwardToEnd()\n";
+
+ Composition &comp = m_doc->getComposition();
+ m_doc->slotSetPointerPosition(comp.getDuration());
+}
+
+void
+SequenceManager::setLoop(const timeT &lhs, const timeT &rhs)
+{
+ // do not set a loop if JACK transport sync is enabled, because this is
+ // completely broken, and apparently broken due to a limitation of JACK
+ // transport itself. #1240039 - DMM
+ // KConfig* config = kapp->config();
+ // config->setGroup(SequencerOptionsConfigGroup);
+ // if (config->readBoolEntry("jacktransport", false))
+ // {
+ // //!!! message box should go here to inform user of why the loop was
+ // // not set, but I can't add it at the moment due to to the pre-release
+ // // freeze - DMM
+ // return;
+ // }
+
+ // Let the sequencer know about the loop markers
+ //
+ QByteArray data;
+ QDataStream streamOut(data, IO_WriteOnly);
+
+ RealTime loopStart =
+ m_doc->getComposition().getElapsedRealTime(lhs);
+ RealTime loopEnd =
+ m_doc->getComposition().getElapsedRealTime(rhs);
+
+ streamOut << (long)loopStart.sec;
+ streamOut << (long)loopStart.nsec;
+ streamOut << (long)loopEnd.sec;
+ streamOut << (long)loopEnd.nsec;
+
+ rgapp->sequencerSend("setLoop(long int, long int, long int, long int)", data);
+}
+
+void
+SequenceManager::checkSoundDriverStatus(bool warnUser)
+{
+ QByteArray data;
+ QCString replyType;
+ QByteArray replyData;
+ QDataStream streamOut(data, IO_WriteOnly);
+
+ streamOut << QString(VERSION);
+
+ if (! rgapp->sequencerCall("getSoundDriverStatus(QString)",
+ replyType, replyData, data)) {
+
+ m_soundDriverStatus = NO_DRIVER;
+
+ } else {
+
+ QDataStream streamIn(replyData, IO_ReadOnly);
+ unsigned int result;
+ streamIn >> result;
+ m_soundDriverStatus = result;
+ }
+
+ SEQMAN_DEBUG << "Sound driver status is: " << m_soundDriverStatus << endl;
+
+ if (!warnUser) return;
+
+#ifdef HAVE_LIBJACK
+ if ((m_soundDriverStatus & (AUDIO_OK | MIDI_OK | VERSION_OK)) ==
+ (AUDIO_OK | MIDI_OK | VERSION_OK)) return;
+#else
+ if ((m_soundDriverStatus & (MIDI_OK | VERSION_OK)) ==
+ (MIDI_OK | VERSION_OK)) return;
+#endif
+
+ KStartupLogo::hideIfStillThere();
+ CurrentProgressDialog::freeze();
+
+ QString text = "";
+
+ if (m_soundDriverStatus == NO_DRIVER) {
+ text = i18n("<p>Both MIDI and Audio subsystems have failed to initialize.</p><p>You may continue without the sequencer, but we suggest closing Rosegarden, running \"alsaconf\" as root, and starting Rosegarden again. If you wish to run with no sequencer by design, then use \"rosegarden --nosequencer\" to avoid seeing this error in the future.</p>");
+ } else if (!(m_soundDriverStatus & MIDI_OK)) {
+ text = i18n("<p>The MIDI subsystem has failed to initialize.</p><p>You may continue without the sequencer, but we suggest closing Rosegarden, running \"modprobe snd-seq-midi\" as root, and starting Rosegarden again. If you wish to run with no sequencer by design, then use \"rosegarden --nosequencer\" to avoid seeing this error in the future.</p>");
+ } else if (!(m_soundDriverStatus & VERSION_OK)) {
+ text = i18n("<p>The Rosegarden sequencer module version does not match the GUI module version.</p><p>You have probably mixed up files from two different versions of Rosegarden. Please check your installation.</p>");
+ }
+
+ if (text != "") {
+ RosegardenGUIApp::self()->awaitDialogClearance();
+ KMessageBox::error(RosegardenGUIApp::self(),
+ i18n("<h3>Sequencer startup failed</h3>%1").arg(text));
+ CurrentProgressDialog::thaw();
+ return;
+ }
+
+#ifdef HAVE_LIBJACK
+ if (!(m_soundDriverStatus & AUDIO_OK)) {
+ RosegardenGUIApp::self()->awaitDialogClearance();
+ KMessageBox::information(RosegardenGUIApp::self(), i18n("<h3>Failed to connect to JACK audio server.</h3><p>Rosegarden could not connect to the JACK audio server. This probably means the JACK server is not running.</p><p>If you want to be able to play or record audio files or use plugins, you should exit Rosegarden and start the JACK server before running Rosegarden again.</p>"),
+ i18n("Failed to connect to JACK"),
+ "startup-jack-failed");
+ }
+#endif
+ CurrentProgressDialog::thaw();
+}
+
+void
+SequenceManager::preparePlayback(bool forceProgramChanges)
+{
+ Studio &studio = m_doc->getStudio();
+ InstrumentList list = studio.getAllInstruments();
+ MappedComposition mC;
+ MappedEvent *mE;
+
+ std::set<InstrumentId> activeInstruments;
+
+ Composition &composition = m_doc->getComposition();
+
+ for (Composition::trackcontainer::const_iterator i =
+ composition.getTracks().begin();
+ i != composition.getTracks().end(); ++i) {
+
+ Track *track = i->second;
+ if (track) activeInstruments.insert(track->getInstrument());
+ }
+
+ // Send the MappedInstruments (minimal Instrument information
+ // required for Performance) to the Sequencer
+ //
+ InstrumentList::iterator it = list.begin();
+ for (; it != list.end(); it++) {
+
+ StudioControl::sendMappedInstrument(MappedInstrument(*it));
+
+ // Send program changes for MIDI Instruments
+ //
+ if ((*it)->getType() == Instrument::Midi) {
+
+ if (activeInstruments.find((*it)->getId()) ==
+ activeInstruments.end()) {
+// std::cerr << "SequenceManager::preparePlayback: instrument "
+// << (*it)->getId() << " is not in use" << std::endl;
+ continue;
+ }
+
+ // send bank select always before program change
+ //
+ if ((*it)->sendsBankSelect()) {
+ mE = new MappedEvent((*it)->getId(),
+ MappedEvent::MidiController,
+ MIDI_CONTROLLER_BANK_MSB,
+ (*it)->getMSB());
+ mC.insert(mE);
+
+ mE = new MappedEvent((*it)->getId(),
+ MappedEvent::MidiController,
+ MIDI_CONTROLLER_BANK_LSB,
+ (*it)->getLSB());
+ mC.insert(mE);
+ }
+
+ // send program change
+ //
+ if ((*it)->sendsProgramChange() || forceProgramChanges) {
+ RG_DEBUG << "SequenceManager::preparePlayback() : sending prg change for "
+ << (*it)->getPresentationName().c_str() << endl;
+
+ mE = new MappedEvent((*it)->getId(),
+ MappedEvent::MidiProgramChange,
+ (*it)->getProgramChange());
+ mC.insert(mE);
+ }
+
+ } else if ((*it)->getType() == Instrument::Audio ||
+ (*it)->getType() == Instrument::SoftSynth) {
+ } else {
+ RG_DEBUG << "SequenceManager::preparePlayback - "
+ << "unrecognised instrument type" << endl;
+ }
+
+
+ }
+
+ // Send the MappedComposition if it's got anything in it
+ showVisuals(mC);
+ StudioControl::sendMappedComposition(mC);
+}
+
+void
+SequenceManager::sendAudioLevel(MappedEvent *mE)
+{
+ RosegardenGUIView *v;
+ QList<RosegardenGUIView>& viewList = m_doc->getViewList();
+
+ for (v = viewList.first(); v != 0; v = viewList.next()) {
+ v->showVisuals(mE);
+ }
+
+}
+
+void
+SequenceManager::resetControllers()
+{
+ SEQMAN_DEBUG << "SequenceManager::resetControllers - resetting\n";
+
+ // Should do all Midi Instrument - not just guess like this is doing
+ // currently.
+
+ InstrumentList list = m_doc->getStudio().getPresentationInstruments();
+ InstrumentList::iterator it;
+
+ MappedComposition mC;
+
+ for (it = list.begin(); it != list.end(); it++) {
+ if ((*it)->getType() == Instrument::Midi) {
+ MappedEvent *mE = new MappedEvent((*it)->getId(),
+ MappedEvent::MidiController,
+ MIDI_CONTROLLER_RESET,
+ 0);
+ mC.insert(mE);
+ }
+ }
+
+ StudioControl::sendMappedComposition(mC);
+ //showVisuals(mC);
+}
+
+void
+SequenceManager::resetMidiNetwork()
+{
+ SEQMAN_DEBUG << "SequenceManager::resetMidiNetwork - resetting\n";
+ MappedComposition mC;
+
+ // Should do all Midi Instrument - not just guess like this is doing
+ // currently.
+
+ for (unsigned int i = 0; i < 16; i++) {
+ MappedEvent *mE =
+ new MappedEvent(MidiInstrumentBase + i,
+ MappedEvent::MidiController,
+ MIDI_SYSTEM_RESET,
+ 0);
+
+ mC.insert(mE);
+ }
+ showVisuals(mC);
+ StudioControl::sendMappedComposition(mC);
+}
+
+void
+SequenceManager::sendMIDIRecordingDevice(const QString recordDeviceStr)
+{
+
+ if (recordDeviceStr) {
+ int recordDevice = recordDeviceStr.toInt();
+
+ if (recordDevice >= 0) {
+ MappedEvent mE(MidiInstrumentBase, // InstrumentId
+ MappedEvent::SystemRecordDevice,
+ MidiByte(recordDevice),
+ MidiByte(true));
+
+ StudioControl::sendMappedEvent(mE);
+ SEQMAN_DEBUG << "set MIDI record device to "
+ << recordDevice << endl;
+ }
+ }
+}
+
+void
+SequenceManager::restoreRecordSubscriptions()
+{
+ KConfig* config = kapp->config();
+ config->setGroup(SequencerOptionsConfigGroup);
+ //QString recordDeviceStr = config->readEntry("midirecorddevice");
+ QStringList devList = config->readListEntry("midirecorddevice");
+
+ for ( QStringList::ConstIterator it = devList.begin();
+ it != devList.end(); ++it) {
+ sendMIDIRecordingDevice(*it);
+ }
+
+}
+
+void
+SequenceManager::reinitialiseSequencerStudio()
+{
+ KConfig* config = kapp->config();
+ config->setGroup(SequencerOptionsConfigGroup);
+ //QString recordDeviceStr = config->readEntry("midirecorddevice");
+
+ //sendMIDIRecordingDevice(recordDeviceStr);
+ restoreRecordSubscriptions();
+
+ // Toggle JACK audio ports appropriately
+ //
+ bool submasterOuts = config->readBoolEntry("audiosubmasterouts", false);
+ bool faderOuts = config->readBoolEntry("audiofaderouts", 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);
+
+
+ // Set the studio from the current document
+ //
+ m_doc->initialiseStudio();
+}
+
+void
+SequenceManager::panic()
+{
+ SEQMAN_DEBUG << "panic button\n";
+
+ stopping();
+
+ MappedEvent mE(MidiInstrumentBase, MappedEvent::Panic, 0, 0);
+ emit setProgress(40);
+ StudioControl::sendMappedEvent(mE);
+ emit setProgress(100);
+
+ // Studio &studio = m_doc->getStudio();
+ //
+ // InstrumentList list = studio.getPresentationInstruments();
+ // InstrumentList::iterator it;
+ //
+ // int maxDevices = 0, device = 0;
+ // for (it = list.begin(); it != list.end(); it++)
+ // if ((*it)->getType() == Instrument::Midi)
+ // maxDevices++;
+ //
+ // emit setProgress(40);
+ //
+ // for (it = list.begin(); it != list.end(); it++)
+ // {
+ // if ((*it)->getType() == Instrument::Midi)
+ // {
+ // for (unsigned int i = 0; i < 128; i++)
+ // {
+ // MappedEvent
+ // mE((*it)->getId(),
+ // MappedEvent::MidiNote,
+ // i,
+ // 0);
+ //
+ // StudioControl::sendMappedEvent(mE);
+ // }
+ //
+ // device++;
+ // }
+ //
+ // emit setProgress(int(90.0 * (double(device) / double(maxDevices))));
+ // }
+ //
+ // resetControllers();
+}
+
+void
+SequenceManager::showVisuals(const MappedComposition &mC)
+{
+ MappedComposition::const_iterator it = mC.begin();
+ if (it != mC.end())
+ m_transport->setMidiOutLabel(*it);
+}
+
+MappedComposition
+SequenceManager::applyFiltering(const MappedComposition &mC,
+ MappedEvent::MappedEventType filter)
+{
+ MappedComposition retMc;
+ MappedComposition::const_iterator it = mC.begin();
+
+ for (; it != mC.end(); it++) {
+ if (!((*it)->getType() & filter))
+ retMc.insert(new MappedEvent(*it));
+ }
+
+ return retMc;
+}
+
+void SequenceManager::resetCompositionMmapper()
+{
+ SEQMAN_DEBUG << "SequenceManager::resetCompositionMmapper()\n";
+ delete m_compositionMmapper;
+ m_compositionMmapper = new CompositionMmapper(m_doc);
+
+ resetMetronomeMmapper();
+ resetTempoSegmentMmapper();
+ resetTimeSigSegmentMmapper();
+ resetControlBlockMmapper();
+}
+
+void SequenceManager::resetMetronomeMmapper()
+{
+ SEQMAN_DEBUG << "SequenceManager::resetMetronomeMmapper()\n";
+
+ delete m_metronomeMmapper;
+ m_metronomeMmapper = SegmentMmapperFactory::makeMetronome(m_doc);
+}
+
+void SequenceManager::resetTempoSegmentMmapper()
+{
+ SEQMAN_DEBUG << "SequenceManager::resetTempoSegmentMmapper()\n";
+
+ delete m_tempoSegmentMmapper;
+ m_tempoSegmentMmapper = SegmentMmapperFactory::makeTempo(m_doc);
+}
+
+void SequenceManager::resetTimeSigSegmentMmapper()
+{
+ SEQMAN_DEBUG << "SequenceManager::resetTimeSigSegmentMmapper()\n";
+
+ delete m_timeSigSegmentMmapper;
+ m_timeSigSegmentMmapper = SegmentMmapperFactory::makeTimeSig(m_doc);
+}
+
+void SequenceManager::resetControlBlockMmapper()
+{
+ SEQMAN_DEBUG << "SequenceManager::resetControlBlockMmapper()\n";
+
+ m_controlBlockMmapper->setDocument(m_doc);
+}
+
+bool SequenceManager::event(QEvent *e)
+{
+ if (e->type() == QEvent::User) {
+ SEQMAN_DEBUG << "SequenceManager::event() with user event\n";
+ if (m_updateRequested) {
+ SEQMAN_DEBUG << "SequenceManager::event(): update requested\n";
+ checkRefreshStatus();
+ m_updateRequested = false;
+ }
+ return true;
+ } else {
+ return QObject::event(e);
+ }
+}
+
+void SequenceManager::update()
+{
+ SEQMAN_DEBUG << "SequenceManager::update()\n";
+ // schedule a refresh-status check for the next event loop
+ QEvent *e = new QEvent(QEvent::User);
+ m_updateRequested = true;
+ QApplication::postEvent(this, e);
+}
+
+void SequenceManager::checkRefreshStatus()
+{
+ SEQMAN_DEBUG << "SequenceManager::checkRefreshStatus()\n";
+
+ // Look at trigger segments first: if one of those has changed, we'll
+ // need to be aware of it when scanning segments subsequently
+
+ TriggerSegmentRec::SegmentRuntimeIdSet ridset;
+ Composition &comp = m_doc->getComposition();
+ SegmentRefreshMap newTriggerMap;
+
+ for (Composition::triggersegmentcontaineriterator i =
+ comp.getTriggerSegments().begin();
+ i != comp.getTriggerSegments().end(); ++i) {
+
+ Segment *s = (*i)->getSegment();
+
+ if (m_triggerSegments.find(s) == m_triggerSegments.end()) {
+ newTriggerMap[s] = s->getNewRefreshStatusId();
+ } else {
+ newTriggerMap[s] = m_triggerSegments[s];
+ }
+
+ if (s->getRefreshStatus(newTriggerMap[s]).needsRefresh()) {
+ TriggerSegmentRec::SegmentRuntimeIdSet &thisSet = (*i)->getReferences();
+ ridset.insert(thisSet.begin(), thisSet.end());
+ s->getRefreshStatus(newTriggerMap[s]).setNeedsRefresh(false);
+ }
+ }
+
+ m_triggerSegments = newTriggerMap;
+
+ SEQMAN_DEBUG << "SequenceManager::checkRefreshStatus: segments modified by changes to trigger segments are:" << endl;
+ int x = 0;
+ for (TriggerSegmentRec::SegmentRuntimeIdSet::iterator i = ridset.begin();
+ i != ridset.end(); ++i) {
+ SEQMAN_DEBUG << x << ": " << *i << endl;
+ ++x;
+ }
+
+ std::vector<Segment*>::iterator i;
+
+ // Check removed segments first
+ for (i = m_removedSegments.begin(); i != m_removedSegments.end(); ++i) {
+ processRemovedSegment(*i);
+ }
+ m_removedSegments.clear();
+
+ SEQMAN_DEBUG << "SequenceManager::checkRefreshStatus: we have "
+ << m_segments.size() << " segments" << endl;
+
+ // then the ones which are still there
+ for (SegmentRefreshMap::iterator i = m_segments.begin();
+ i != m_segments.end(); ++i) {
+ if (i->first->getRefreshStatus(i->second).needsRefresh() ||
+ ridset.find(i->first->getRuntimeId()) != ridset.end()) {
+ segmentModified(i->first);
+ i->first->getRefreshStatus(i->second).setNeedsRefresh(false);
+ }
+ }
+
+ // then added ones
+ for (i = m_addedSegments.begin(); i != m_addedSegments.end(); ++i) {
+ processAddedSegment(*i);
+ }
+ m_addedSegments.clear();
+}
+
+void SequenceManager::segmentModified(Segment* s)
+{
+ SEQMAN_DEBUG << "SequenceManager::segmentModified(" << s << ")\n";
+
+ bool sizeChanged = m_compositionMmapper->segmentModified(s);
+
+ SEQMAN_DEBUG << "SequenceManager::segmentModified() : size changed = "
+ << sizeChanged << endl;
+
+ if ((m_transportStatus == PLAYING) && sizeChanged) {
+ QByteArray data;
+ QDataStream streamOut(data, IO_WriteOnly);
+
+ streamOut << (QString)m_compositionMmapper->getSegmentFileName(s);
+ streamOut << (size_t)m_compositionMmapper->getSegmentFileSize(s);
+
+ SEQMAN_DEBUG << "SequenceManager::segmentModified() : DCOP-call sequencer remapSegment"
+ << m_compositionMmapper->getSegmentFileName(s) << endl;
+
+ rgapp->sequencerSend("remapSegment(QString, size_t)", data);
+ }
+}
+
+void SequenceManager::segmentAdded(const Composition*, Segment* s)
+{
+ SEQMAN_DEBUG << "SequenceManager::segmentAdded(" << s << ")\n";
+ m_addedSegments.push_back(s);
+}
+
+void SequenceManager::segmentRemoved(const Composition*, Segment* s)
+{
+ SEQMAN_DEBUG << "SequenceManager::segmentRemoved(" << s << ")\n";
+ m_removedSegments.push_back(s);
+ std::vector<Segment*>::iterator i = find(m_addedSegments.begin(), m_addedSegments.end(), s);
+ if (i != m_addedSegments.end())
+ m_addedSegments.erase(i);
+}
+
+void SequenceManager::segmentRepeatChanged(const Composition*, Segment* s, bool repeat)
+{
+ SEQMAN_DEBUG << "SequenceManager::segmentRepeatChanged(" << s << ", " << repeat << ")\n";
+ segmentModified(s);
+}
+
+void SequenceManager::segmentRepeatEndChanged(const Composition*, Segment* s, timeT newEndTime)
+{
+ SEQMAN_DEBUG << "SequenceManager::segmentRepeatEndChanged(" << s << ", " << newEndTime << ")\n";
+ segmentModified(s);
+}
+
+void SequenceManager::segmentEventsTimingChanged(const Composition*, Segment * s, timeT t, RealTime)
+{
+ SEQMAN_DEBUG << "SequenceManager::segmentEventsTimingChanged(" << s << ", " << t << ")\n";
+ segmentModified(s);
+ if (s && s->getType() == Segment::Audio && m_transportStatus == PLAYING) {
+ QByteArray data;
+ rgapp->sequencerSend("remapTracks()", data);
+ }
+}
+
+void SequenceManager::segmentTransposeChanged(const Composition*, Segment *s, int transpose)
+{
+ SEQMAN_DEBUG << "SequenceManager::segmentTransposeChanged(" << s << ", " << transpose << ")\n";
+ segmentModified(s);
+}
+
+void SequenceManager::segmentTrackChanged(const Composition*, Segment *s, TrackId id)
+{
+ SEQMAN_DEBUG << "SequenceManager::segmentTrackChanged(" << s << ", " << id << ")\n";
+ segmentModified(s);
+ if (s && s->getType() == Segment::Audio && m_transportStatus == PLAYING) {
+ QByteArray data;
+ rgapp->sequencerSend("remapTracks()", data);
+ }
+}
+
+void SequenceManager::segmentEndMarkerChanged(const Composition*, Segment *s, bool)
+{
+ SEQMAN_DEBUG << "SequenceManager::segmentEndMarkerChanged(" << s << ")\n";
+ segmentModified(s);
+}
+
+void SequenceManager::processAddedSegment(Segment* s)
+{
+ SEQMAN_DEBUG << "SequenceManager::processAddedSegment(" << s << ")\n";
+
+ m_compositionMmapper->segmentAdded(s);
+
+ if (m_transportStatus == PLAYING) {
+
+ QByteArray data;
+ QDataStream streamOut(data, IO_WriteOnly);
+
+ streamOut << m_compositionMmapper->getSegmentFileName(s);
+
+ if (!rgapp->sequencerSend("addSegment(QString)", data)) {
+ m_transportStatus = STOPPED;
+ }
+ }
+
+ // Add to segments map
+ int id = s->getNewRefreshStatusId();
+ m_segments.insert(SegmentRefreshMap::value_type(s, id));
+
+}
+
+void SequenceManager::processRemovedSegment(Segment* s)
+{
+ SEQMAN_DEBUG << "SequenceManager::processRemovedSegment(" << s << ")\n";
+
+ QString filename = m_compositionMmapper->getSegmentFileName(s);
+ m_compositionMmapper->segmentDeleted(s);
+
+ if (m_transportStatus == PLAYING) {
+
+ QByteArray data;
+ QDataStream streamOut(data, IO_WriteOnly);
+
+ streamOut << filename;
+
+ if (!rgapp->sequencerSend("deleteSegment(QString)", data)) {
+ // failed
+ m_transportStatus = STOPPED;
+ }
+ }
+
+ // Remove from segments map
+ m_segments.erase(s);
+}
+
+void SequenceManager::endMarkerTimeChanged(const Composition *, bool /*shorten*/)
+{
+ SEQMAN_DEBUG << "SequenceManager::endMarkerTimeChanged()\n";
+ m_compositionMmapperResetTimer->start(500, true); // schedule a composition mmapper reset in 0.5s
+}
+
+void SequenceManager::timeSignatureChanged(const Composition *)
+{
+ resetMetronomeMmapper();
+}
+
+void SequenceManager::trackChanged(const Composition *, Track* t)
+{
+ SEQMAN_DEBUG << "SequenceManager::trackChanged(" << t << ", " << (t ? t->getPosition() : -1) << ")\n";
+ m_controlBlockMmapper->updateTrackData(t);
+
+ if (m_transportStatus == PLAYING) {
+ QByteArray data;
+ rgapp->sequencerSend("remapTracks()", data);
+ }
+}
+
+void SequenceManager::trackDeleted(const Composition *, TrackId t)
+{
+ m_controlBlockMmapper->setTrackDeleted(t);
+}
+
+void SequenceManager::metronomeChanged(InstrumentId id,
+ bool regenerateTicks)
+{
+ // This method is called when the user has changed the
+ // metronome instrument, pitch etc
+
+ SEQMAN_DEBUG << "SequenceManager::metronomeChanged (simple)"
+ << ", instrument = "
+ << id
+ << endl;
+
+ if (regenerateTicks)
+ resetMetronomeMmapper();
+
+ m_controlBlockMmapper->updateMetronomeData(id);
+ if (m_transportStatus == PLAYING) {
+ m_controlBlockMmapper->updateMetronomeForPlayback();
+ } else {
+ m_controlBlockMmapper->updateMetronomeForRecord();
+ }
+
+ m_metronomeMmapper->refresh();
+ m_timeSigSegmentMmapper->refresh();
+ m_tempoSegmentMmapper->refresh();
+}
+
+void SequenceManager::metronomeChanged(const Composition *)
+{
+ // This method is called when the muting status in the composition
+ // has changed -- the metronome itself has not actually changed
+
+ SEQMAN_DEBUG << "SequenceManager::metronomeChanged "
+ << ", instrument = "
+ << m_metronomeMmapper->getMetronomeInstrument()
+ << endl;
+
+ m_controlBlockMmapper->updateMetronomeData
+ (m_metronomeMmapper->getMetronomeInstrument());
+
+ if (m_transportStatus == PLAYING) {
+ m_controlBlockMmapper->updateMetronomeForPlayback();
+ } else {
+ m_controlBlockMmapper->updateMetronomeForRecord();
+ }
+}
+
+void SequenceManager::filtersChanged(MidiFilter thruFilter,
+ MidiFilter recordFilter)
+{
+ m_controlBlockMmapper->updateMidiFilters(thruFilter, recordFilter);
+}
+
+void SequenceManager::soloChanged(const Composition *, bool solo, TrackId selectedTrack)
+{
+ if (m_controlBlockMmapper->updateSoloData(solo, selectedTrack)) {
+ if (m_transportStatus == PLAYING) {
+ QByteArray data;
+ rgapp->sequencerSend("remapTracks()", data);
+ }
+ }
+}
+
+void SequenceManager::tempoChanged(const Composition *c)
+{
+ SEQMAN_DEBUG << "SequenceManager::tempoChanged()\n";
+
+ // Refresh all segments
+ //
+ for (SegmentRefreshMap::iterator i = m_segments.begin();
+ i != m_segments.end(); ++i) {
+ segmentModified(i->first);
+ }
+
+ // and metronome, time sig and tempo
+ //
+ m_metronomeMmapper->refresh();
+ m_timeSigSegmentMmapper->refresh();
+ m_tempoSegmentMmapper->refresh();
+
+ if (c->isLooping())
+ setLoop(c->getLoopStart(), c->getLoopEnd());
+ else if (m_transportStatus == PLAYING) {
+ // If the tempo changes during playback, reset the pointer
+ // position because the sequencer keeps track of position in
+ // real time and we want to maintain the same position in
+ // musical time. Turn off play tracking while this happens,
+ // so that we don't jump about in the main window while the
+ // user's trying to drag the tempo in it. (That doesn't help
+ // for matrix or notation though, sadly)
+ bool tracking = RosegardenGUIApp::self()->isTrackEditorPlayTracking();
+ if (tracking)
+ RosegardenGUIApp::self()->slotToggleTracking();
+ m_doc->slotSetPointerPosition(c->getPosition());
+ if (tracking)
+ RosegardenGUIApp::self()->slotToggleTracking();
+ }
+}
+
+void
+SequenceManager::sendTransportControlStatuses()
+{
+ KConfig* config = kapp->config();
+ config->setGroup(SequencerOptionsConfigGroup);
+
+ // Get the config values
+ //
+ bool jackTransport = config->readBoolEntry("jacktransport", false);
+ bool jackMaster = config->readBoolEntry("jackmaster", false);
+
+ int mmcMode = config->readNumEntry("mmcmode", 0);
+ int mtcMode = config->readNumEntry("mtcmode", 0);
+
+ int midiClock = config->readNumEntry("midiclock", 0);
+ bool midiSyncAuto = config->readBoolEntry("midisyncautoconnect", false);
+
+ // Send JACK transport
+ //
+ int jackValue = 0;
+ if (jackTransport && jackMaster)
+ jackValue = 2;
+ else {
+ if (jackTransport)
+ jackValue = 1;
+ else
+ jackValue = 0;
+ }
+
+ MappedEvent mEjackValue(MidiInstrumentBase, // InstrumentId
+ MappedEvent::SystemJackTransport,
+ MidiByte(jackValue));
+ StudioControl::sendMappedEvent(mEjackValue);
+
+
+ // Send MMC transport
+ //
+ MappedEvent mEmmcValue(MidiInstrumentBase, // InstrumentId
+ MappedEvent::SystemMMCTransport,
+ MidiByte(mmcMode));
+
+ StudioControl::sendMappedEvent(mEmmcValue);
+
+
+ // Send MTC transport
+ //
+ MappedEvent mEmtcValue(MidiInstrumentBase, // InstrumentId
+ MappedEvent::SystemMTCTransport,
+ MidiByte(mtcMode));
+
+ StudioControl::sendMappedEvent(mEmtcValue);
+
+
+ // Send MIDI Clock
+ //
+ MappedEvent mEmidiClock(MidiInstrumentBase, // InstrumentId
+ MappedEvent::SystemMIDIClock,
+ MidiByte(midiClock));
+
+ StudioControl::sendMappedEvent(mEmidiClock);
+
+
+ // Send MIDI Sync Auto-Connect
+ //
+ MappedEvent mEmidiSyncAuto(MidiInstrumentBase, // InstrumentId
+ MappedEvent::SystemMIDISyncAuto,
+ MidiByte(midiSyncAuto ? 1 : 0));
+
+ StudioControl::sendMappedEvent(mEmidiSyncAuto);
+
+}
+
+void
+SequenceManager::slotCountdownTimerTimeout()
+{
+ // Set the elapsed time in seconds
+ //
+ m_countdownDialog->setElapsedTime(m_recordTime->elapsed() / 1000);
+}
+
+void
+SequenceManager::slotFoundMountPoint(const QString&,
+ unsigned long kBSize,
+ unsigned long /*kBUsed*/,
+ unsigned long kBAvail)
+{
+ m_gotDiskSpaceResult = true;
+ m_diskSpaceKBAvail = kBAvail;
+}
+
+void
+SequenceManager::enableMIDIThruRouting(bool state)
+{
+ m_controlBlockMmapper->enableMIDIThruRouting(state);
+}
+
+int
+SequenceManager::getSampleRate()
+{
+ if (m_sampleRate != 0) return m_sampleRate;
+
+ QCString replyType;
+ QByteArray replyData;
+ if (rgapp->sequencerCall("getSampleRate()", replyType, replyData)) {
+ QDataStream streamIn(replyData, IO_ReadOnly);
+ unsigned int result;
+ streamIn >> m_sampleRate;
+ }
+
+ return m_sampleRate;
+}
+
+bool
+SequenceManager::shouldWarnForImpreciseTimer()
+{
+ kapp->config()->setGroup(SequencerOptionsConfigGroup);
+ QString timer = kapp->config()->readEntry("timer");
+ if (timer == "(auto)" || timer == "") return true;
+ else return false; // if the user has chosen the timer, leave them alone
+}
+
+}
+#include "SequenceManager.moc"
diff --git a/src/gui/seqmanager/SequenceManager.h b/src/gui/seqmanager/SequenceManager.h
new file mode 100644
index 0000000..792ec01
--- /dev/null
+++ b/src/gui/seqmanager/SequenceManager.h
@@ -0,0 +1,322 @@
+
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
+
+/*
+ Rosegarden
+ A MIDI and audio sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_SEQUENCEMANAGER_H_
+#define _RG_SEQUENCEMANAGER_H_
+
+#include "base/Composition.h"
+#include "base/Event.h"
+#include "base/MidiProgram.h"
+#include "base/RealTime.h"
+#include "base/Track.h"
+#include "gui/application/RosegardenDCOP.h"
+#include "sound/MappedComposition.h"
+#include "sound/MappedEvent.h"
+#include <qobject.h>
+#include <qstring.h>
+#include <vector>
+#include <map>
+
+
+class QTimer;
+class QTime;
+class QEvent;
+
+
+namespace Rosegarden
+{
+
+class TransportDialog;
+class Track;
+class TimeSigSegmentMmapper;
+class TempoSegmentMmapper;
+class SequencerMapper;
+class Segment;
+class RosegardenGUIDoc;
+class MetronomeMmapper;
+class CountdownDialog;
+class ControlBlockMmapper;
+class CompositionMmapper;
+class Composition;
+class AudioManagerDialog;
+
+
+class SequenceManager : public QObject, public CompositionObserver
+{
+ Q_OBJECT
+public:
+ SequenceManager(RosegardenGUIDoc *doc,
+ TransportDialog *transport);
+ ~SequenceManager();
+
+ /**
+ * Replaces the internal document
+ */
+ void setDocument(RosegardenGUIDoc*);
+
+ /**
+ * Return the current internal document
+ */
+ RosegardenGUIDoc* getDocument();
+
+ //
+ // Transport controls
+ //
+
+ /// returns true if the call actually paused playback
+ bool play();
+
+ // We don't call stop() directly - using stopping() and then
+ // call stop().
+ //
+ void stop();
+
+ void stopping();
+ void rewind();
+ void fastforward();
+ void record(bool countIn);
+ void rewindToBeginning();
+ void fastForwardToEnd();
+
+ void setLoop(const timeT &lhs, const timeT &rhs);
+ void notifySequencerStatus(TransportStatus status);
+ void sendSequencerJump(const RealTime &time);
+
+ // Events coming in
+ void processAsynchronousMidi(const MappedComposition &mC,
+ AudioManagerDialog *aMD);
+
+ // Before playing and recording. If warnUser is true, show the
+ // user a warning dialog if there is a problem with the setup.
+ //
+ void checkSoundDriverStatus(bool warnUser);
+
+ /**
+ * Send program changes and align Instrument lists before playback
+ * starts.
+ * Also called at document loading (with arg set to true) to reset all instruments
+ * (fix for bug 820174)
+ *
+ * @arg forceProgramChanges if true, always send program changes even if the instrument is
+ * set not to send any.
+ */
+ void preparePlayback(bool forceProgramChanges = false);
+
+ /// Check and set sequencer status
+ void setTransportStatus(const TransportStatus &status);
+ TransportStatus getTransportStatus() const { return m_transportStatus; }
+
+ /**
+ * Suspend the sequencer to allow for a safe DCOP call() i.e. one
+ * when we don't hang both clients 'cos they're blocking on each
+ * other.
+ */
+ void suspendSequencer(bool value);
+
+ /// Send the audio level to VU meters
+ void sendAudioLevel(MappedEvent *mE);
+
+ /// Find what has been initialised and what hasn't
+ unsigned int getSoundDriverStatus() { return m_soundDriverStatus; }
+
+ /// Reset MIDI controllers
+ void resetControllers();
+
+ /// Reset MIDI network
+ void resetMidiNetwork();
+
+ /// Reinitialise the studio
+ void reinitialiseSequencerStudio();
+
+ /// Send JACK and MMC transport control statuses
+ void sendTransportControlStatuses();
+
+ /// Send all note offs and resets to MIDI devices
+ void panic();
+
+ /// Send an MC to the view
+ void showVisuals(const MappedComposition &mC);
+
+ /// Apply in-situ filtering to a MappedComposition
+ MappedComposition
+ applyFiltering(const MappedComposition &mC,
+ MappedEvent::MappedEventType filter);
+
+ CountdownDialog* getCountdownDialog() { return m_countdownDialog; }
+
+
+ //
+ // CompositionObserver interface
+ //
+ virtual void segmentAdded (const Composition*, Segment*);
+ virtual void segmentRemoved (const Composition*, Segment*);
+ virtual void segmentRepeatChanged (const Composition*, Segment*, bool);
+ virtual void segmentRepeatEndChanged (const Composition*, Segment*, timeT);
+ virtual void segmentEventsTimingChanged(const Composition*, Segment *, timeT delay, RealTime rtDelay);
+ virtual void segmentTransposeChanged (const Composition*, Segment *, int transpose);
+ virtual void segmentTrackChanged (const Composition*, Segment *, TrackId id);
+ virtual void segmentEndMarkerChanged (const Composition*, Segment *, bool);
+ virtual void endMarkerTimeChanged (const Composition*, bool shorten);
+ virtual void trackChanged (const Composition*, Track*);
+ virtual void trackDeleted (const Composition*, TrackId);
+ virtual void timeSignatureChanged (const Composition*);
+ virtual void metronomeChanged (const Composition*);
+ virtual void soloChanged (const Composition*, bool solo, TrackId selectedTrack);
+ virtual void tempoChanged (const Composition*);
+
+ void processAddedSegment(Segment*);
+ void processRemovedSegment(Segment*);
+ void segmentModified(Segment*);
+
+ virtual bool event(QEvent *e);
+
+ /// for the gui to call to indicate that the metronome needs to be remapped
+ void metronomeChanged(InstrumentId id, bool regenerateTicks);
+
+ /// for the gui to call to indicate that a MIDI filter needs to be remapped
+ void filtersChanged(MidiFilter thruFilter,
+ MidiFilter recordFilter);
+
+ /// Return the current sequencer memory mapped file
+ SequencerMapper* getSequencerMapper() { return m_sequencerMapper; }
+
+ /// Ensure that the sequencer file is mapped
+ void mapSequencer();
+
+ void setTransport(TransportDialog* t) { m_transport = t; }
+
+ void enableMIDIThruRouting(bool state);
+
+ int getSampleRate(); // may return 0 if sequencer uncontactable
+
+public slots:
+
+ void update();
+
+signals:
+ void setProgress(int);
+ void incrementProgress(int);
+
+ void insertableNoteOnReceived(int pitch, int velocity);
+ void insertableNoteOffReceived(int pitch, int velocity);
+ void controllerDeviceEventReceived(MappedEvent *ev);
+
+protected slots:
+ void slotCountdownTimerTimeout();
+
+ // Activated by timer to allow a message to be reported to
+ // the user - we use this mechanism so that the user isn't
+ // bombarded with dialogs in the event of lots of failures.
+ //
+ void slotAllowReport() { m_canReport = true; }
+
+ void slotFoundMountPoint(const QString&,
+ unsigned long kBSize,
+ unsigned long kBUsed,
+ unsigned long kBAvail);
+
+ void slotScheduledCompositionMmapperReset() { resetCompositionMmapper(); }
+
+protected:
+
+ void resetCompositionMmapper();
+ void resetControlBlockMmapper();
+ void resetMetronomeMmapper();
+ void resetTempoSegmentMmapper();
+ void resetTimeSigSegmentMmapper();
+ void checkRefreshStatus();
+ void sendMIDIRecordingDevice(const QString recordDeviceStr);
+ void restoreRecordSubscriptions();
+ bool shouldWarnForImpreciseTimer();
+
+ //--------------- Data members ---------------------------------
+
+ MappedComposition m_mC;
+ RosegardenGUIDoc *m_doc;
+ CompositionMmapper *m_compositionMmapper;
+ ControlBlockMmapper *m_controlBlockMmapper;
+ MetronomeMmapper *m_metronomeMmapper;
+ TempoSegmentMmapper *m_tempoSegmentMmapper;
+ TimeSigSegmentMmapper *m_timeSigSegmentMmapper;
+
+ std::vector<Segment*> m_addedSegments;
+ std::vector<Segment*> m_removedSegments;
+ bool m_metronomeNeedsRefresh;
+
+ // statuses
+ TransportStatus m_transportStatus;
+ unsigned int m_soundDriverStatus;
+
+ // pointer to the transport dialog
+ TransportDialog *m_transport;
+
+ clock_t m_lastRewoundAt;
+
+ CountdownDialog *m_countdownDialog;
+ QTimer *m_countdownTimer;
+
+ bool m_shownOverrunWarning;
+
+ // Keep a track of elapsed record time with this object
+ //
+ QTime *m_recordTime;
+
+ typedef std::map<Segment *, int> SegmentRefreshMap;
+ SegmentRefreshMap m_segments; // map to refresh status id
+ SegmentRefreshMap m_triggerSegments;
+ unsigned int m_compositionRefreshStatusId;
+ bool m_updateRequested;
+
+ // used to schedule a composition mmapper reset when the composition end time marker changes
+ // this can be caused by a window resize, and since the reset is potentially expensive we want to collapse
+ // several following requests into one.
+ QTimer *m_compositionMmapperResetTimer;
+
+ // Information that the sequencer is providing to us - for the moment
+ // it's only the position pointer.
+ //
+ SequencerMapper *m_sequencerMapper;
+
+ // Just to make sure we don't bother the user too often
+ //
+ QTimer *m_reportTimer;
+ bool m_canReport;
+
+ bool m_gotDiskSpaceResult;
+ unsigned long m_diskSpaceKBAvail;
+
+ bool m_lastLowLatencySwitchSent;
+
+ timeT m_lastTransportStartPosition;
+
+ int m_sampleRate;
+};
+
+
+
+
+}
+
+#endif
diff --git a/src/gui/seqmanager/SequencerMapper.cpp b/src/gui/seqmanager/SequencerMapper.cpp
new file mode 100644
index 0000000..5d8acf3
--- /dev/null
+++ b/src/gui/seqmanager/SequencerMapper.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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "SequencerMapper.h"
+
+#include "misc/Debug.h"
+#include "base/Exception.h"
+#include "base/MidiProgram.h"
+#include "base/RealTime.h"
+#include "base/Track.h"
+#include "sound/MappedComposition.h"
+#include "sound/MappedEvent.h"
+#include "sound/SequencerDataBlock.h"
+#include <qfileinfo.h>
+#include <qstring.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <sys/mman.h>
+#include <errno.h>
+
+
+namespace Rosegarden
+{
+
+SequencerMapper::SequencerMapper(const QString filename)
+ : m_fd( -1),
+ m_mmappedSize(0),
+ m_mmappedBuffer((void *)0),
+ m_filename(filename)
+{
+ RG_DEBUG << "SequencerMapper::SequencerMapper - mmapping " << filename << endl;
+ map();
+}
+
+SequencerMapper::~SequencerMapper()
+{
+ unmap();
+}
+
+void
+SequencerMapper::map()
+{
+ QFileInfo fInfo(m_filename);
+ if (!fInfo.exists()) {
+ RG_DEBUG << "SequencerMapper::map() : file " << m_filename << " doesn't exist\n";
+ throw Exception("file not found");
+ }
+
+ m_fd = ::open(m_filename.latin1(), O_RDWR);
+
+ if (m_fd < 0) {
+ RG_DEBUG << "SequencerMapper::map() : Couldn't open " << m_filename
+ << endl;
+ throw Exception("Couldn't open " + std::string(m_filename.data()));
+ }
+
+ m_mmappedSize = sizeof(SequencerDataBlock);
+ m_mmappedBuffer = (long*)::mmap(0, m_mmappedSize, PROT_READ, MAP_SHARED, m_fd, 0);
+
+ if (m_mmappedBuffer == (void*) - 1) {
+
+ RG_DEBUG << QString("mmap failed : (%1) %2\n").
+ arg(errno).arg(strerror(errno));
+
+ throw Exception("mmap failed");
+ }
+
+ RG_DEBUG << "SequencerMapper::map() : "
+ << (void*)m_mmappedBuffer << "," << m_mmappedSize << endl;
+
+ m_sequencerDataBlock = new (m_mmappedBuffer)
+ SequencerDataBlock(false);
+}
+
+void
+SequencerMapper::unmap()
+{
+ ::munmap(m_mmappedBuffer, m_mmappedSize);
+ ::close(m_fd);
+}
+
+}
diff --git a/src/gui/seqmanager/SequencerMapper.h b/src/gui/seqmanager/SequencerMapper.h
new file mode 100644
index 0000000..59d217f
--- /dev/null
+++ b/src/gui/seqmanager/SequencerMapper.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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_SEQUENCERMAPPER_H_
+#define _RG_SEQUENCERMAPPER_H_
+
+#include "base/MidiProgram.h"
+#include "base/RealTime.h"
+#include "base/Track.h"
+#include "sound/SequencerDataBlock.h"
+#include <qstring.h>
+
+
+class LevelInfo;
+
+
+namespace Rosegarden
+{
+
+class MappedEvent;
+class MappedComposition;
+
+
+class SequencerMapper
+{
+public:
+ SequencerMapper(const QString filename);
+ ~SequencerMapper();
+
+ RealTime getPositionPointer() const {
+ return m_sequencerDataBlock->getPositionPointer();
+ }
+
+ bool getVisual(MappedEvent &ev) const {
+ return m_sequencerDataBlock->getVisual(ev);
+ }
+
+ int getRecordedEvents(MappedComposition &mC) const {
+ return m_sequencerDataBlock->getRecordedEvents(mC);
+ }
+
+ bool getTrackLevel(TrackId track,
+ LevelInfo &info) const {
+ return m_sequencerDataBlock->getTrackLevel(track, info);
+ }
+
+ bool getInstrumentLevel(InstrumentId id,
+ LevelInfo &info) const {
+ return m_sequencerDataBlock->getInstrumentLevel(id, info);
+ }
+
+ bool getInstrumentLevelForMixer(InstrumentId id,
+ LevelInfo &info) const {
+ return m_sequencerDataBlock->getInstrumentLevelForMixer(id, info);
+ }
+
+ bool getInstrumentRecordLevel(InstrumentId id,
+ LevelInfo &info) const {
+ return m_sequencerDataBlock->getInstrumentRecordLevel(id, info);
+ }
+
+ bool getInstrumentRecordLevelForMixer(InstrumentId id,
+ LevelInfo &info) const {
+ return m_sequencerDataBlock->getInstrumentRecordLevelForMixer(id, info);
+ }
+
+ bool getSubmasterLevel(int submaster,
+ LevelInfo &info) const {
+ return m_sequencerDataBlock->getSubmasterLevel(submaster, info);
+ }
+
+ bool getMasterLevel(LevelInfo &info) const {
+ return m_sequencerDataBlock->getMasterLevel(info);
+ }
+
+protected:
+ void map();
+ void unmap();
+
+ int m_fd;
+ size_t m_mmappedSize;
+ void* m_mmappedBuffer;
+ QString m_filename;
+ SequencerDataBlock *m_sequencerDataBlock;
+};
+
+
+
+}
+
+#endif
diff --git a/src/gui/seqmanager/SpecialSegmentMmapper.cpp b/src/gui/seqmanager/SpecialSegmentMmapper.cpp
new file mode 100644
index 0000000..5926f11
--- /dev/null
+++ b/src/gui/seqmanager/SpecialSegmentMmapper.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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "SpecialSegmentMmapper.h"
+
+#include <kstddirs.h>
+#include "base/Event.h"
+#include "base/Segment.h"
+#include "base/TriggerSegment.h"
+#include "document/RosegardenGUIDoc.h"
+#include "SegmentMmapper.h"
+#include <kglobal.h>
+#include <qstring.h>
+
+
+namespace Rosegarden
+{
+
+SpecialSegmentMmapper::SpecialSegmentMmapper(RosegardenGUIDoc* doc,
+ QString baseFileName)
+ : SegmentMmapper(doc, 0, createFileName(baseFileName))
+{}
+
+QString SpecialSegmentMmapper::createFileName(QString baseFileName)
+{
+ return KGlobal::dirs()->resourceDirs("tmp").last() + "/" + baseFileName;
+}
+
+unsigned int SpecialSegmentMmapper::getSegmentRepeatCount()
+{
+ return 1;
+}
+
+}
diff --git a/src/gui/seqmanager/SpecialSegmentMmapper.h b/src/gui/seqmanager/SpecialSegmentMmapper.h
new file mode 100644
index 0000000..e16c7b5
--- /dev/null
+++ b/src/gui/seqmanager/SpecialSegmentMmapper.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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_SPECIALSEGMENTMMAPPER_H_
+#define _RG_SPECIALSEGMENTMMAPPER_H_
+
+#include "SegmentMmapper.h"
+#include <qstring.h>
+
+
+
+
+namespace Rosegarden
+{
+
+class RosegardenGUIDoc;
+
+
+class SpecialSegmentMmapper : public SegmentMmapper
+{
+public:
+ // overrides from SegmentMmapper
+ virtual unsigned int getSegmentRepeatCount();
+
+protected:
+ SpecialSegmentMmapper(RosegardenGUIDoc* doc,
+ QString baseFileName);
+
+ QString createFileName(QString baseFileName);
+};
+
+//----------------------------------------
+
+
+}
+
+#endif
diff --git a/src/gui/seqmanager/TempoSegmentMmapper.cpp b/src/gui/seqmanager/TempoSegmentMmapper.cpp
new file mode 100644
index 0000000..1c53922
--- /dev/null
+++ b/src/gui/seqmanager/TempoSegmentMmapper.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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "TempoSegmentMmapper.h"
+
+#include "base/Event.h"
+#include "base/RealTime.h"
+#include "base/Segment.h"
+#include "base/TriggerSegment.h"
+#include "document/RosegardenGUIDoc.h"
+#include "SegmentMmapper.h"
+#include "sound/MappedEvent.h"
+#include "SpecialSegmentMmapper.h"
+#include <qstring.h>
+
+
+namespace Rosegarden
+{
+
+void TempoSegmentMmapper::dump()
+{
+ RealTime eventTime;
+
+ Composition& comp = m_doc->getComposition();
+ MappedEvent* bufPos = m_mmappedEventBuffer;
+
+ for (int i = 0; i < comp.getTempoChangeCount(); ++i) {
+
+ std::pair<timeT, tempoT> tempoChange = comp.getTempoChange(i);
+
+ eventTime = comp.getElapsedRealTime(tempoChange.first);
+ MappedEvent* mappedEvent = new (bufPos) MappedEvent();
+ mappedEvent->setType(MappedEvent::Tempo);
+ mappedEvent->setEventTime(eventTime);
+
+ // Nasty hack -- we use the instrument ID to pass through the
+ // raw tempo value, as it has the appropriate range (unlike
+ // e.g. tempo1 + tempo2). These events are not actually used
+ // on the sequencer side yet, so this may change to something
+ // nicer at some point.
+ mappedEvent->setInstrument(tempoChange.second);
+
+ ++bufPos;
+ }
+
+ // Store the number of events at the start of the shared memory region
+ *(size_t *)m_mmappedRegion = (bufPos - m_mmappedEventBuffer);
+}
+
+size_t TempoSegmentMmapper::computeMmappedSize()
+{
+ return m_doc->getComposition().getTempoChangeCount() * sizeof(MappedEvent);
+}
+
+}
diff --git a/src/gui/seqmanager/TempoSegmentMmapper.h b/src/gui/seqmanager/TempoSegmentMmapper.h
new file mode 100644
index 0000000..08c8ebf
--- /dev/null
+++ b/src/gui/seqmanager/TempoSegmentMmapper.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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_TEMPOSEGMENTMMAPPER_H_
+#define _RG_TEMPOSEGMENTMMAPPER_H_
+
+#include "SpecialSegmentMmapper.h"
+#include <qstring.h>
+
+
+namespace Rosegarden
+{
+
+class RosegardenGUIDoc;
+
+
+class TempoSegmentMmapper : public SpecialSegmentMmapper
+{
+ friend class SegmentMmapperFactory;
+
+protected:
+ TempoSegmentMmapper(RosegardenGUIDoc* doc,
+ QString baseFileName)
+ : SpecialSegmentMmapper(doc, baseFileName) {}
+
+ // overrides from SegmentMmapper
+ virtual size_t computeMmappedSize();
+
+ // override from SegmentMmapper
+ virtual void dump();
+};
+
+//----------------------------------------
+
+
+}
+
+#endif
diff --git a/src/gui/seqmanager/TimeSigSegmentMmapper.cpp b/src/gui/seqmanager/TimeSigSegmentMmapper.cpp
new file mode 100644
index 0000000..98beab3
--- /dev/null
+++ b/src/gui/seqmanager/TimeSigSegmentMmapper.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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "TimeSigSegmentMmapper.h"
+
+#include "base/Event.h"
+#include "base/RealTime.h"
+#include "base/Segment.h"
+#include "base/TriggerSegment.h"
+#include "document/RosegardenGUIDoc.h"
+#include "SegmentMmapper.h"
+#include "sound/MappedEvent.h"
+#include "SpecialSegmentMmapper.h"
+#include <qstring.h>
+
+
+namespace Rosegarden
+{
+
+void TimeSigSegmentMmapper::dump()
+{
+ RealTime eventTime;
+
+ Composition& comp = m_doc->getComposition();
+ MappedEvent* bufPos = m_mmappedEventBuffer;
+
+ for (int i = 0; i < comp.getTimeSignatureCount(); ++i) {
+
+ std::pair<timeT, TimeSignature> timeSigChange = comp.getTimeSignatureChange(i);
+
+ eventTime = comp.getElapsedRealTime(timeSigChange.first);
+ MappedEvent* mappedEvent = new (bufPos) MappedEvent();
+ mappedEvent->setType(MappedEvent::TimeSignature);
+ mappedEvent->setEventTime(eventTime);
+ mappedEvent->setData1(timeSigChange.second.getNumerator());
+ mappedEvent->setData2(timeSigChange.second.getDenominator());
+
+ ++bufPos;
+ }
+
+ // Store the number of events at the start of the shared memory region
+ *(size_t *)m_mmappedRegion = (bufPos - m_mmappedEventBuffer);
+}
+
+size_t TimeSigSegmentMmapper::computeMmappedSize()
+{
+ return m_doc->getComposition().getTimeSignatureCount() * sizeof(MappedEvent);
+}
+
+}
diff --git a/src/gui/seqmanager/TimeSigSegmentMmapper.h b/src/gui/seqmanager/TimeSigSegmentMmapper.h
new file mode 100644
index 0000000..20e0474
--- /dev/null
+++ b/src/gui/seqmanager/TimeSigSegmentMmapper.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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_TIMESIGSEGMENTMMAPPER_H_
+#define _RG_TIMESIGSEGMENTMMAPPER_H_
+
+#include "SpecialSegmentMmapper.h"
+#include <qstring.h>
+
+
+namespace Rosegarden
+{
+
+class RosegardenGUIDoc;
+
+
+class TimeSigSegmentMmapper : public SpecialSegmentMmapper
+{
+ friend class SegmentMmapperFactory;
+
+public:
+
+protected:
+ TimeSigSegmentMmapper(RosegardenGUIDoc* doc,
+ QString baseFileName)
+ : SpecialSegmentMmapper(doc, baseFileName) {}
+
+ // overrides from SegmentMmapper
+ virtual size_t computeMmappedSize();
+
+ // override from SegmentMmapper
+ virtual void dump();
+};
+
+//----------------------------------------
+
+
+}
+
+#endif
diff --git a/src/gui/studio/AudioMixerWindow.cpp b/src/gui/studio/AudioMixerWindow.cpp
new file mode 100644
index 0000000..e8d09b3
--- /dev/null
+++ b/src/gui/studio/AudioMixerWindow.cpp
@@ -0,0 +1,1734 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
+
+/*
+ Rosegarden
+ A MIDI and audio sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "AudioMixerWindow.h"
+#include <qlayout.h>
+#include <kapplication.h>
+
+#include "AudioPlugin.h"
+#include "AudioPluginManager.h"
+#include "MixerWindow.h"
+#include "StudioControl.h"
+#include "sound/Midi.h"
+#include "misc/Debug.h"
+#include "gui/application/RosegardenDCOP.h"
+#include "base/AudioLevel.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 "document/RosegardenGUIDoc.h"
+#include "gui/editors/notation/NotePixmapFactory.h"
+#include "gui/general/GUIPalette.h"
+#include "gui/seqmanager/SequencerMapper.h"
+#include "gui/seqmanager/SequenceManager.h"
+#include "gui/widgets/AudioRouteMenu.h"
+#include "gui/widgets/AudioVUMeter.h"
+#include "gui/widgets/Fader.h"
+#include "gui/widgets/Rotary.h"
+#include "gui/widgets/VUMeter.h"
+#include "sound/MappedCommon.h"
+#include "sound/MappedEvent.h"
+#include "sound/MappedStudio.h"
+#include <klocale.h>
+#include <kstddirs.h>
+#include <kaction.h>
+#include <kglobal.h>
+#include <kmainwindow.h>
+#include <kstdaction.h>
+#include <qaccel.h>
+#include <qcolor.h>
+#include <qfont.h>
+#include <qframe.h>
+#include <qhbox.h>
+#include <qiconset.h>
+#include <qlabel.h>
+#include <qobject.h>
+#include <qpalette.h>
+#include <qpixmap.h>
+#include <qpushbutton.h>
+#include <qstring.h>
+#include <qtooltip.h>
+#include <qvbox.h>
+#include <qwidget.h>
+
+
+namespace Rosegarden
+{
+
+// We define these such that the default of no-bits-set for the
+// studio's mixer display options produces the most sensible result
+static const unsigned int MIXER_OMIT_FADERS = 1 << 0;
+static const unsigned int MIXER_OMIT_SUBMASTERS = 1 << 1;
+static const unsigned int MIXER_OMIT_PLUGINS = 1 << 2;
+static const unsigned int MIXER_SHOW_UNASSIGNED_FADERS = 1 << 3;
+static const unsigned int MIXER_OMIT_SYNTH_FADERS = 1 << 4;
+
+
+AudioMixerWindow::AudioMixerWindow(QWidget *parent,
+ RosegardenGUIDoc *document):
+ MixerWindow(parent, document),
+ m_mainBox (0)
+{
+ populate();
+
+ KStdAction::close(this,
+ SLOT(slotClose()),
+ actionCollection());
+
+ QIconSet 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-record")));
+ new KAction(i18n("&Record"), icon, 0, this,
+ SIGNAL(record()), actionCollection(),
+ "record");
+
+ icon = QIconSet(NotePixmapFactory::toQPixmap(NotePixmapFactory::makeToolbarPixmap
+ ("transport-panic")));
+ new KAction(i18n("Panic"), icon, Key_P + CTRL + ALT, this,
+ SIGNAL(panic()), actionCollection(),
+ "panic");
+
+ unsigned int mixerOptions = m_studio->getMixerDisplayOptions();
+
+ (new KToggleAction(i18n("Show Audio &Faders"), 0, this,
+ SLOT(slotToggleFaders()), actionCollection(),
+ "show_audio_faders"))->setChecked
+ (!(mixerOptions & MIXER_OMIT_FADERS));
+
+ (new KToggleAction(i18n("Show Synth &Faders"), 0, this,
+ SLOT(slotToggleSynthFaders()), actionCollection(),
+ "show_synth_faders"))->setChecked
+ (!(mixerOptions & MIXER_OMIT_SYNTH_FADERS));
+
+ (new KToggleAction(i18n("Show &Submasters"), 0, this,
+ SLOT(slotToggleSubmasters()), actionCollection(),
+ "show_audio_submasters"))->setChecked
+ (!(mixerOptions & MIXER_OMIT_SUBMASTERS));
+
+ (new KToggleAction(i18n("Show &Plugin Buttons"), 0, this,
+ SLOT(slotTogglePluginButtons()), actionCollection(),
+ "show_plugin_buttons"))->setChecked
+ (!(mixerOptions & MIXER_OMIT_PLUGINS));
+
+ (new KToggleAction(i18n("Show &Unassigned Faders"), 0, this,
+ SLOT(slotToggleUnassignedFaders()), actionCollection(),
+ "show_unassigned_faders"))->setChecked
+ (mixerOptions & MIXER_SHOW_UNASSIGNED_FADERS);
+
+ KRadioAction *action = 0;
+
+ for (int i = 1; i <= 16; i *= 2) {
+ action =
+ new KRadioAction(i18n("1 Input", "%n Inputs", i),
+ 0, this,
+ SLOT(slotSetInputCountFromAction()), actionCollection(),
+ QString("inputs_%1").arg(i));
+ action->setExclusiveGroup("inputs");
+ if (i == int(m_studio->getRecordIns().size()))
+ action->setChecked(true);
+ }
+
+ action = new KRadioAction
+ (i18n("No Submasters"),
+ 0, this,
+ SLOT(slotSetSubmasterCountFromAction()), actionCollection(),
+ QString("submasters_0"));
+ action->setExclusiveGroup("submasters");
+ action->setChecked(true);
+
+ for (int i = 2; i <= 8; i *= 2) {
+ action = new KRadioAction
+ (i18n("1 Submaster", "%n Submasters", i),
+ 0, this,
+ SLOT(slotSetSubmasterCountFromAction()), actionCollection(),
+ QString("submasters_%1").arg(i));
+ action->setExclusiveGroup("submasters");
+ if (i == int(m_studio->getBusses().size()) - 1)
+ action->setChecked(true);
+ }
+
+ createGUI("mixer.rc");
+}
+
+AudioMixerWindow::~AudioMixerWindow()
+{
+ RG_DEBUG << "AudioMixerWindow::~AudioMixerWindow\n";
+ depopulate();
+}
+
+void
+AudioMixerWindow::depopulate()
+{
+ if (!m_mainBox)
+ return ;
+
+ // All the widgets will be deleted when the main box goes,
+ // except that we need to delete the AudioRouteMenus first
+ // because they aren't actually widgets but do contain them
+
+ for (FaderMap::iterator i = m_faders.begin();
+ i != m_faders.end(); ++i) {
+ delete i->second.m_input;
+ delete i->second.m_output;
+ }
+
+ m_faders.clear();
+ m_submasters.clear();
+
+ delete m_mainBox;
+ m_mainBox = 0;
+}
+
+void
+AudioMixerWindow::populate()
+{
+ if (m_mainBox) {
+
+ depopulate();
+
+ } else {
+
+ m_surroundBox = new QHBox(this);
+ setCentralWidget(m_surroundBox);
+ }
+
+ QFont font;
+ font.setPointSize(font.pointSize() * 8 / 10);
+ font.setBold(false);
+ setFont(font);
+
+ QFont boldFont(font);
+ boldFont.setBold(true);
+
+ m_mainBox = new QFrame(m_surroundBox);
+
+ InstrumentList instruments = m_studio->getPresentationInstruments();
+ BussList busses = m_studio->getBusses();
+
+ QString pixmapDir = KGlobal::dirs()->findResource("appdata", "pixmaps/");
+ m_monoPixmap.load(QString("%1/misc/mono.xpm").arg(pixmapDir));
+ m_stereoPixmap.load(QString("%1/misc/stereo.xpm").arg(pixmapDir));
+
+ // Total cols: is 2 for each fader, submaster or master, plus 1
+ // for each spacer.
+ QGridLayout *mainLayout = new QGridLayout
+ (m_mainBox, (instruments.size() + busses.size()) * 3, 7);
+
+ setCaption(i18n("Audio Mixer"));
+
+ int count = 1;
+ int col = 0;
+
+ unsigned int mixerOptions = m_studio->getMixerDisplayOptions();
+
+ bool showUnassigned = (mixerOptions & MIXER_SHOW_UNASSIGNED_FADERS);
+
+ for (InstrumentList::iterator i = instruments.begin();
+ i != instruments.end(); ++i) {
+
+ if ((*i)->getType() != Instrument::Audio &&
+ (*i)->getType() != Instrument::SoftSynth)
+ continue;
+
+ FaderRec rec;
+
+ if (!showUnassigned) {
+ // Do any tracks use this instrument?
+ if (!isInstrumentAssigned((*i)->getId())) {
+ continue;
+ }
+ }
+ rec.m_populated = true;
+
+ if ((*i)->getType() == Instrument::Audio) {
+ rec.m_input = new AudioRouteMenu(m_mainBox,
+ AudioRouteMenu::In,
+ AudioRouteMenu::Compact,
+ m_studio, *i);
+ QToolTip::add
+ (rec.m_input->getWidget(), i18n("Record input source"));
+ rec.m_input->getWidget()->setMaximumWidth(45);
+ } else {
+ rec.m_input = 0;
+ }
+
+ rec.m_output = new AudioRouteMenu(m_mainBox,
+ AudioRouteMenu::Out,
+ AudioRouteMenu::Compact,
+ m_studio, *i);
+ QToolTip::add
+ (rec.m_output->getWidget(), i18n("Output destination"));
+ rec.m_output->getWidget()->setMaximumWidth(45);
+
+ rec.m_pan = new Rotary
+ (m_mainBox, -100.0, 100.0, 1.0, 5.0, 0.0, 20,
+ Rotary::NoTicks, false, true);
+
+ if ((*i)->getType() == Instrument::Audio) {
+ rec.m_pan->setKnobColour(GUIPalette::getColour(GUIPalette::RotaryPastelGreen));
+ } else {
+ rec.m_pan->setKnobColour(GUIPalette::getColour(GUIPalette::RotaryPastelYellow));
+ }
+
+ QToolTip::add
+ (rec.m_pan, i18n("Pan"));
+
+ rec.m_fader = new Fader
+ (AudioLevel::LongFader, 20, 240, m_mainBox);
+ rec.m_meter = new AudioVUMeter
+ (m_mainBox, VUMeter::AudioPeakHoldIECLong, true, rec.m_input != 0,
+ 20, 240);
+
+ QToolTip::add
+ (rec.m_fader, i18n("Audio level"));
+ QToolTip::add
+ (rec.m_meter, i18n("Audio level"));
+
+ rec.m_stereoButton = new QPushButton(m_mainBox);
+ rec.m_stereoButton->setPixmap(m_monoPixmap);
+ rec.m_stereoButton->setFixedSize(20, 20);
+ rec.m_stereoButton->setFlat(true);
+ rec.m_stereoness = false;
+ QToolTip::add
+ (rec.m_stereoButton, i18n("Mono or stereo"));
+
+ rec.m_muteButton = new QPushButton(m_mainBox);
+ rec.m_muteButton->setText("M");
+ rec.m_muteButton->setToggleButton(true);
+ rec.m_muteButton->setFixedWidth(rec.m_stereoButton->width());
+ rec.m_muteButton->setFixedHeight(rec.m_stereoButton->height());
+ rec.m_muteButton->setFlat(true);
+ QToolTip::add
+ (rec.m_muteButton, i18n("Mute"));
+
+ rec.m_soloButton = new QPushButton(m_mainBox);
+ rec.m_soloButton->setText("S");
+ rec.m_soloButton->setToggleButton(true);
+ rec.m_soloButton->setFixedWidth(rec.m_stereoButton->width());
+ rec.m_soloButton->setFixedHeight(rec.m_stereoButton->height());
+ rec.m_soloButton->setFlat(true);
+ QToolTip::add
+ (rec.m_soloButton, i18n("Solo"));
+
+ rec.m_recordButton = new QPushButton(m_mainBox);
+ rec.m_recordButton->setText("R");
+ rec.m_recordButton->setToggleButton(true);
+ rec.m_recordButton->setFixedWidth(rec.m_stereoButton->width());
+ rec.m_recordButton->setFixedHeight(rec.m_stereoButton->height());
+ rec.m_recordButton->setFlat(true);
+ QToolTip::add
+ (rec.m_recordButton, i18n("Arm recording"));
+
+ rec.m_pluginBox = new QVBox(m_mainBox);
+
+ for (int p = 0; p < 5; ++p) {
+ QPushButton *plugin = new QPushButton(rec.m_pluginBox, "pluginButton");
+ plugin->setText(i18n("<none>"));
+ plugin->setMaximumWidth(45);
+ QToolTip::add
+ (plugin, i18n("Audio plugin button"));
+ rec.m_plugins.push_back(plugin);
+ connect(plugin, SIGNAL(clicked()),
+ this, SLOT(slotSelectPlugin()));
+ }
+
+ QLabel *idLabel;
+ QString idString;
+ if ((*i)->getType() == Instrument::Audio) {
+ idString = i18n("Audio %1").arg((*i)->getId() -
+ AudioInstrumentBase + 1);
+ idLabel = new QLabel(idString, m_mainBox, "audioIdLabel");
+ } else {
+ idString = i18n("Synth %1").arg((*i)->getId() -
+ SoftSynthInstrumentBase + 1);
+ idLabel = new QLabel(idString, m_mainBox, "synthIdLabel");
+ }
+ idLabel->setFont(boldFont);
+
+ if (rec.m_input) {
+ mainLayout->addMultiCellWidget(rec.m_input->getWidget(), 1, 1, col, col + 1);
+ }
+ mainLayout->addMultiCellWidget(rec.m_output->getWidget(), 2, 2, col, col + 1);
+ // mainLayout->addWidget(idLabel, 2, col, Qt::AlignCenter);
+ // mainLayout->addWidget(rec.m_pan, 2, col+1, Qt::AlignLeft);
+
+ mainLayout->addMultiCellWidget(idLabel, 0, 0, col, col + 1, Qt::AlignCenter);
+ mainLayout->addWidget(rec.m_pan, 5, col, Qt::AlignCenter);
+
+ mainLayout->addWidget(rec.m_fader, 3, col, Qt::AlignCenter);
+ mainLayout->addWidget(rec.m_meter, 3, col + 1, Qt::AlignCenter);
+
+ // commented out until implemented
+ // mainLayout->addWidget(rec.m_muteButton, 4, col);
+ // mainLayout->addWidget(rec.m_soloButton, 4, col+1);
+ rec.m_muteButton->hide();
+ rec.m_soloButton->hide();
+
+ // mainLayout->addWidget(rec.m_recordButton, 5, col);
+ // mainLayout->addWidget(rec.m_stereoButton, 5, col+1);
+
+ rec.m_recordButton->hide();
+ mainLayout->addWidget(rec.m_stereoButton, 5, col + 1);
+
+ if (rec.m_pluginBox) {
+ mainLayout->addMultiCellWidget(rec.m_pluginBox, 6, 6, col, col + 1);
+ }
+
+ m_faders[(*i)->getId()] = rec;
+ updateFader((*i)->getId());
+ updateRouteButtons((*i)->getId());
+ updateStereoButton((*i)->getId());
+ updatePluginButtons((*i)->getId());
+
+ if (rec.m_input) {
+ connect(rec.m_input, SIGNAL(changed()),
+ this, SLOT(slotInputChanged()));
+ }
+
+ connect(rec.m_output, SIGNAL(changed()),
+ this, SLOT(slotOutputChanged()));
+
+ connect(rec.m_fader, SIGNAL(faderChanged(float)),
+ this, SLOT(slotFaderLevelChanged(float)));
+
+ connect(rec.m_pan, SIGNAL(valueChanged(float)),
+ this, SLOT(slotPanChanged(float)));
+
+ connect(rec.m_soloButton, SIGNAL(clicked()),
+ this, SLOT(slotSoloChanged()));
+
+ connect(rec.m_muteButton, SIGNAL(clicked()),
+ this, SLOT(slotMuteChanged()));
+
+ connect(rec.m_stereoButton, SIGNAL(clicked()),
+ this, SLOT(slotChannelsChanged()));
+
+ connect(rec.m_recordButton, SIGNAL(clicked()),
+ this, SLOT(slotRecordChanged()));
+
+ ++count;
+
+ mainLayout->addMultiCell(new QSpacerItem(2, 0), 0, 6, col + 2, col + 2);
+
+ col += 3;
+ }
+
+ count = 1;
+
+ for (BussList::iterator i = busses.begin();
+ i != busses.end(); ++i) {
+
+ if (i == busses.begin())
+ continue; // that one's the master
+
+ FaderRec rec;
+ rec.m_populated = true;
+
+ rec.m_pan = new Rotary
+ (m_mainBox, -100.0, 100.0, 1.0, 5.0, 0.0, 20,
+ Rotary::NoTicks, false, true);
+ rec.m_pan->setKnobColour(GUIPalette::getColour(GUIPalette::RotaryPastelBlue));
+
+ QToolTip::add
+ (rec.m_pan, i18n("Pan"));
+
+ rec.m_fader = new Fader
+ (AudioLevel::LongFader, 20, 240, m_mainBox);
+ rec.m_meter = new AudioVUMeter
+ (m_mainBox, VUMeter::AudioPeakHoldIECLong, true, false, 20, 240);
+
+ QToolTip::add
+ (rec.m_fader, i18n("Audio level"));
+ QToolTip::add
+ (rec.m_meter, i18n("Audio level"));
+
+ rec.m_muteButton = new QPushButton(m_mainBox);
+ rec.m_muteButton->setText("M");
+ rec.m_muteButton->setToggleButton(true);
+ rec.m_muteButton->setFlat(true);
+
+ QToolTip::add
+ (rec.m_muteButton, i18n("Mute"));
+
+ rec.m_pluginBox = new QVBox(m_mainBox);
+
+ for (int p = 0; p < 5; ++p) {
+ QPushButton *plugin = new QPushButton(rec.m_pluginBox, "pluginButton");
+ plugin->setText(i18n("<none>"));
+ plugin->setMaximumWidth(45);
+ QToolTip::add
+ (plugin, i18n("Audio plugin button"));
+ rec.m_plugins.push_back(plugin);
+ connect(plugin, SIGNAL(clicked()),
+ this, SLOT(slotSelectPlugin()));
+ }
+
+ QLabel *idLabel = new QLabel(i18n("Sub %1").arg(count), m_mainBox, "subMaster");
+ idLabel->setFont(boldFont);
+
+ // mainLayout->addWidget(idLabel, 2, col, Qt::AlignCenter);
+ mainLayout->addMultiCellWidget(idLabel, 0, 0, col, col + 1, Qt::AlignCenter);
+
+ // mainLayout->addWidget(rec.m_pan, 2, col+1, Qt::AlignLeft);
+ mainLayout->addMultiCellWidget(rec.m_pan, 5, 5, col, col + 1, Qt::AlignCenter);
+
+ mainLayout->addWidget(rec.m_fader, 3, col, Qt::AlignCenter);
+ mainLayout->addWidget(rec.m_meter, 3, col + 1, Qt::AlignCenter);
+
+ // mainLayout->addMultiCellWidget(rec.m_muteButton, 4, 4, col, col+1);
+ rec.m_muteButton->hide();
+
+ if (rec.m_pluginBox) {
+ mainLayout->addMultiCellWidget(rec.m_pluginBox, 6, 6, col, col + 1);
+ }
+
+ m_submasters.push_back(rec);
+ updateFader(count);
+ updatePluginButtons(count);
+
+ connect(rec.m_fader, SIGNAL(faderChanged(float)),
+ this, SLOT(slotFaderLevelChanged(float)));
+
+ connect(rec.m_pan, SIGNAL(valueChanged(float)),
+ this, SLOT(slotPanChanged(float)));
+
+ connect(rec.m_muteButton, SIGNAL(clicked()),
+ this, SLOT(slotMuteChanged()));
+
+ ++count;
+
+ mainLayout->addMultiCell(new QSpacerItem(2, 0), 0, 6, col + 2, col + 2);
+
+ col += 3;
+ }
+
+ if (busses.size() > 0) {
+
+ FaderRec rec;
+ rec.m_populated = true;
+
+ rec.m_fader = new Fader
+ (AudioLevel::LongFader, 20, 240, m_mainBox);
+ rec.m_meter = new AudioVUMeter
+ (m_mainBox, VUMeter::AudioPeakHoldIEC, true, false, 20, 240);
+
+ QToolTip::add
+ (rec.m_fader, i18n("Audio master output level"));
+ QToolTip::add
+ (rec.m_meter, i18n("Audio master output level"));
+
+ rec.m_muteButton = new QPushButton(m_mainBox);
+ rec.m_muteButton->setText("M");
+ rec.m_muteButton->setToggleButton(true);
+ rec.m_muteButton->setFlat(true);
+
+ QLabel *idLabel = new QLabel(i18n("Master"), m_mainBox);
+ idLabel->setFont(boldFont);
+
+ mainLayout->addMultiCellWidget(idLabel, 0, 0, col, col + 1, Qt::AlignCenter);
+ mainLayout->addWidget(rec.m_fader, 3, col, Qt::AlignCenter);
+ mainLayout->addWidget(rec.m_meter, 3, col + 1, Qt::AlignCenter);
+
+ // mainLayout->addMultiCellWidget(rec.m_muteButton, 4, 4, col, col+1);
+ rec.m_muteButton->hide();
+
+ mainLayout->addMultiCell(new QSpacerItem(2, 0), 0, 6, col + 2, col + 2);
+
+ m_master = rec;
+ updateFader(0);
+
+ connect(rec.m_fader, SIGNAL(faderChanged(float)),
+ this, SLOT(slotFaderLevelChanged(float)));
+
+ connect(rec.m_muteButton, SIGNAL(clicked()),
+ this, SLOT(slotMuteChanged()));
+ }
+
+ m_mainBox->show();
+
+ slotUpdateFaderVisibility();
+ slotUpdateSynthFaderVisibility();
+ slotUpdateSubmasterVisibility();
+ slotUpdatePluginButtonVisibility();
+
+ adjustSize();
+}
+
+bool
+AudioMixerWindow::isInstrumentAssigned(InstrumentId id)
+{
+ Composition::trackcontainer &tracks =
+ m_document->getComposition().getTracks();
+
+ for (Composition::trackcontainer::iterator ti =
+ tracks.begin(); ti != tracks.end(); ++ti) {
+ if (ti->second->getInstrument() == id) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+void
+AudioMixerWindow::slotTrackAssignmentsChanged()
+{
+ for (FaderMap::iterator i = m_faders.begin(); i != m_faders.end(); ++i) {
+
+ InstrumentId id = i->first;
+ bool assigned = isInstrumentAssigned(id);
+
+ if (assigned != i->second.m_populated) {
+ // found an inconsistency
+ populate();
+ return ;
+ }
+ }
+}
+
+void
+AudioMixerWindow::slotUpdateInstrument(InstrumentId id)
+{
+ RG_DEBUG << "AudioMixerWindow::slotUpdateInstrument(" << id << ")" << endl;
+
+ blockSignals(true);
+
+ updateFader(id);
+ updateStereoButton(id);
+ updateRouteButtons(id);
+ updatePluginButtons(id);
+ updateMiscButtons(id);
+
+ blockSignals(false);
+}
+
+void
+AudioMixerWindow::slotPluginSelected(InstrumentId id,
+ int index, int plugin)
+{
+ if (id >= (int)AudioInstrumentBase) {
+
+ FaderRec &rec = m_faders[id];
+ if (!rec.m_populated || !rec.m_pluginBox)
+ return ;
+
+ // nowhere to display synth plugin info yet
+ if (index >= rec.m_plugins.size())
+ return ;
+
+ if (plugin == -1) {
+
+ rec.m_plugins[index]->setText(i18n("<none>"));
+ QToolTip::add
+ (rec.m_plugins[index], i18n("<no plugin>"));
+
+ rec.m_plugins[index]->setPaletteBackgroundColor
+ (kapp->palette().
+ color(QPalette::Active, QColorGroup::Button));
+
+ } else {
+
+ AudioPlugin *pluginClass
+ = m_document->getPluginManager()->getPlugin(plugin);
+
+ QColor pluginBgColour =
+ kapp->palette().color(QPalette::Active, QColorGroup::Light);
+
+ if (pluginClass) {
+ rec.m_plugins[index]->
+ setText(pluginClass->getLabel());
+ QToolTip::add
+ (rec.m_plugins[index], pluginClass->getLabel());
+
+ pluginBgColour = pluginClass->getColour();
+ }
+
+
+ rec.m_plugins[index]->setPaletteForegroundColor(Qt::white);
+ rec.m_plugins[index]->setPaletteBackgroundColor(pluginBgColour);
+ }
+ } else if (id > 0 && id <= m_submasters.size()) {
+
+ FaderRec &rec = m_submasters[id - 1];
+ if (!rec.m_populated || !rec.m_pluginBox)
+ return ;
+ if (index >= rec.m_plugins.size())
+ return ;
+
+ if (plugin == -1) {
+
+ rec.m_plugins[index]->setText(i18n("<none>"));
+ QToolTip::add
+ (rec.m_plugins[index], i18n("<no plugin>"));
+
+ rec.m_plugins[index]->setPaletteBackgroundColor
+ (kapp->palette().
+ color(QPalette::Active, QColorGroup::Button));
+
+ } else {
+
+ AudioPlugin *pluginClass
+ = m_document->getPluginManager()->getPlugin(plugin);
+
+ QColor pluginBgColour =
+ kapp->palette().color(QPalette::Active, QColorGroup::Light);
+
+ if (pluginClass) {
+ rec.m_plugins[index]->
+ setText(pluginClass->getLabel());
+ QToolTip::add
+ (rec.m_plugins[index], pluginClass->getLabel());
+
+ pluginBgColour = pluginClass->getColour();
+ }
+
+
+ rec.m_plugins[index]->setPaletteForegroundColor(Qt::white);
+ rec.m_plugins[index]->setPaletteBackgroundColor(pluginBgColour);
+ }
+ }
+}
+
+void
+AudioMixerWindow::slotPluginBypassed(InstrumentId instrumentId,
+ int , bool )
+{
+ RG_DEBUG << "AudioMixerWindow::slotPluginBypassed(" << instrumentId << ")" << endl;
+
+ updatePluginButtons(instrumentId);
+}
+
+void
+AudioMixerWindow::updateFader(int id)
+{
+ if (id == -1) {
+
+ // This used to be the special code for updating the monitor fader.
+ return ;
+
+ } else if (id >= (int)AudioInstrumentBase) {
+
+ FaderRec &rec = m_faders[id];
+ if (!rec.m_populated)
+ return ;
+ Instrument *instrument = m_studio->getInstrumentById(id);
+
+ rec.m_fader->blockSignals(true);
+ rec.m_fader->setFader(instrument->getLevel());
+ rec.m_fader->blockSignals(false);
+
+ rec.m_pan->blockSignals(true);
+ rec.m_pan->setPosition(instrument->getPan() - 100);
+ rec.m_pan->blockSignals(false);
+
+ } else {
+
+ FaderRec &rec = (id == 0 ? m_master : m_submasters[id - 1]);
+ BussList busses = m_studio->getBusses();
+ Buss *buss = busses[id];
+
+ rec.m_fader->blockSignals(true);
+ rec.m_fader->setFader(buss->getLevel());
+ rec.m_fader->blockSignals(false);
+
+ if (rec.m_pan) {
+ rec.m_pan->blockSignals(true);
+ rec.m_pan->setPosition(buss->getPan() - 100);
+ rec.m_pan->blockSignals(false);
+ }
+ }
+}
+
+void
+AudioMixerWindow::updateRouteButtons(int id)
+{
+ if (id >= (int)AudioInstrumentBase) {
+ FaderRec &rec = m_faders[id];
+ if (!rec.m_populated)
+ return ;
+ if (rec.m_input)
+ rec.m_input->slotRepopulate();
+ rec.m_output->slotRepopulate();
+ }
+}
+
+void
+AudioMixerWindow::updateStereoButton(int id)
+{
+ if (id >= (int)AudioInstrumentBase) {
+
+ FaderRec &rec = m_faders[id];
+ if (!rec.m_populated)
+ return ;
+ Instrument *i = m_studio->getInstrumentById(id);
+
+ bool stereo = (i->getAudioChannels() > 1);
+ if (stereo == rec.m_stereoness)
+ return ;
+
+ rec.m_stereoness = stereo;
+
+ if (stereo)
+ rec.m_stereoButton->setPixmap(m_stereoPixmap);
+ else
+ rec.m_stereoButton->setPixmap(m_monoPixmap);
+ }
+}
+
+void
+AudioMixerWindow::updateMiscButtons(int )
+{
+ //... complications here, because the mute/solo status is actually
+ // per-track rather than per-instrument... doh.
+}
+
+void
+AudioMixerWindow::updatePluginButtons(int id)
+{
+ FaderRec *rec = 0;
+ PluginContainer *container = 0;
+
+ if (id >= (int)AudioInstrumentBase) {
+
+ container = m_studio->getInstrumentById(id);
+ rec = &m_faders[id];
+ if (!rec->m_populated || !rec->m_pluginBox)
+ return ;
+
+ } else {
+
+ BussList busses = m_studio->getBusses();
+ if (busses.size() > id) {
+ container = busses[id];
+ }
+ rec = &m_submasters[id - 1];
+ if (!rec->m_populated || !rec->m_pluginBox)
+ return ;
+ }
+
+ if (rec && container) {
+
+ for (unsigned int i = 0; i < rec->m_plugins.size(); i++) {
+
+ bool used = false;
+ bool bypass = false;
+ QColor pluginBgColour =
+ kapp->palette().color(QPalette::Active, QColorGroup::Light);
+
+ rec->m_plugins[i]->show();
+
+ AudioPluginInstance *inst = container->getPlugin(i);
+
+ if (inst && inst->isAssigned()) {
+
+ AudioPlugin *pluginClass
+ = m_document->getPluginManager()->getPlugin(
+ m_document->getPluginManager()->
+ getPositionByIdentifier(inst->getIdentifier().c_str()));
+
+ if (pluginClass) {
+ rec->m_plugins[i]->setText(pluginClass->getLabel());
+ QToolTip::add
+ (rec->m_plugins[i], pluginClass->getLabel());
+
+ pluginBgColour = pluginClass->getColour();
+ }
+
+ used = true;
+ bypass = inst->isBypassed();
+
+ } else {
+
+ rec->m_plugins[i]->setText(i18n("<none>"));
+ QToolTip::add
+ (rec->m_plugins[i], i18n("<no plugin>"));
+
+ if (inst)
+ bypass = inst->isBypassed();
+ }
+
+ if (bypass) {
+
+ rec->m_plugins[i]->setPaletteForegroundColor
+ (kapp->palette().
+ color(QPalette::Active, QColorGroup::Button));
+
+ rec->m_plugins[i]->setPaletteBackgroundColor
+ (kapp->palette().
+ color(QPalette::Active, QColorGroup::ButtonText));
+
+ } else if (used) {
+
+ rec->m_plugins[i]->setPaletteForegroundColor(Qt::white);
+ rec->m_plugins[i]->setPaletteBackgroundColor(pluginBgColour);
+
+
+ } else {
+
+ rec->m_plugins[i]->setPaletteForegroundColor
+ (kapp->palette().
+ color(QPalette::Active, QColorGroup::ButtonText));
+
+ rec->m_plugins[i]->setPaletteBackgroundColor
+ (kapp->palette().
+ color(QPalette::Active, QColorGroup::Button));
+ }
+ }
+ }
+}
+
+void
+AudioMixerWindow::slotSelectPlugin()
+{
+ const QObject *s = sender();
+
+ for (FaderMap::iterator i = m_faders.begin();
+ i != m_faders.end(); ++i) {
+
+ int index = 0;
+ if (!i->second.m_populated || !i->second.m_pluginBox)
+ continue;
+
+ for (std::vector<QPushButton *>::iterator pli = i->second.m_plugins.begin();
+ pli != i->second.m_plugins.end(); ++pli) {
+
+ if (*pli == s) {
+
+ emit selectPlugin(this, i->first, index);
+ return ;
+ }
+
+ ++index;
+ }
+ }
+
+
+ int b = 1;
+
+ for (FaderVector::iterator i = m_submasters.begin();
+ i != m_submasters.end(); ++i) {
+
+ int index = 0;
+ if (!i->m_populated || !i->m_pluginBox)
+ continue;
+
+ for (std::vector<QPushButton *>::iterator pli = i->m_plugins.begin();
+ pli != i->m_plugins.end(); ++pli) {
+
+ if (*pli == s) {
+
+ emit selectPlugin(this, b, index);
+ return ;
+ }
+
+ ++index;
+ }
+
+ ++b;
+ }
+}
+
+void
+AudioMixerWindow::slotInputChanged()
+{
+ const QObject *s = sender();
+
+ for (FaderMap::iterator i = m_faders.begin();
+ i != m_faders.end(); ++i) {
+
+ if (i->second.m_input == s)
+ emit instrumentParametersChanged(i->first);
+ }
+}
+
+void
+AudioMixerWindow::slotOutputChanged()
+{
+ const QObject *s = sender();
+
+ for (FaderMap::iterator i = m_faders.begin();
+ i != m_faders.end(); ++i) {
+
+ if (i->second.m_output == s)
+ emit instrumentParametersChanged(i->first);
+ }
+}
+
+void
+AudioMixerWindow::sendControllerRefresh()
+{
+ //!!! really want some notification of whether we have an external controller!
+ int controllerChannel = 0;
+
+ for (FaderMap::iterator i = m_faders.begin(); i != m_faders.end(); ++i) {
+
+ if (controllerChannel >= 16)
+ break;
+
+ Instrument *instrument =
+ m_studio->getInstrumentById(i->first);
+
+ int value = AudioLevel::dB_to_fader
+ (instrument->getLevel(), 127, AudioLevel::LongFader);
+ MappedEvent mE(instrument->getId(),
+ MappedEvent::MidiController,
+ MIDI_CONTROLLER_VOLUME,
+ MidiByte(value));
+ mE.setRecordedChannel(controllerChannel);
+ mE.setRecordedDevice(Device::CONTROL_DEVICE);
+ StudioControl::sendMappedEvent(mE);
+
+ int ipan = (int(instrument->getPan()) * 64) / 100;
+ if (ipan < 0)
+ ipan = 0;
+ if (ipan > 127)
+ ipan = 127;
+ MappedEvent mEp(instrument->getId(),
+ MappedEvent::MidiController,
+ MIDI_CONTROLLER_PAN,
+ MidiByte(ipan));
+ mEp.setRecordedChannel(controllerChannel);
+ mEp.setRecordedDevice(Device::CONTROL_DEVICE);
+ StudioControl::sendMappedEvent(mEp);
+
+ ++controllerChannel;
+ }
+}
+
+void
+AudioMixerWindow::slotFaderLevelChanged(float dB)
+{
+ const QObject *s = sender();
+
+ BussList busses = m_studio->getBusses();
+
+ if (m_master.m_fader == s) {
+
+ if (busses.size() > 0) {
+ StudioControl::setStudioObjectProperty
+ (MappedObjectId(busses[0]->getMappedId()),
+ MappedAudioBuss::Level,
+ MappedObjectValue(dB));
+ busses[0]->setLevel(dB);
+ }
+
+ return ;
+ }
+
+ int index = 1;
+
+ for (FaderVector::iterator i = m_submasters.begin();
+ i != m_submasters.end(); ++i) {
+
+ if (i->m_fader == s) {
+ if ((int)busses.size() > index) {
+ StudioControl::setStudioObjectProperty
+ (MappedObjectId(busses[index]->getMappedId()),
+ MappedAudioBuss::Level,
+ MappedObjectValue(dB));
+ busses[index]->setLevel(dB);
+ }
+
+ return ;
+ }
+
+ ++index;
+ }
+
+ int controllerChannel = 0;
+
+ for (FaderMap::iterator i = m_faders.begin();
+ i != m_faders.end(); ++i) {
+
+ if (i->second.m_fader == s) {
+
+ Instrument *instrument =
+ m_studio->getInstrumentById(i->first);
+
+ if (instrument) {
+ StudioControl::setStudioObjectProperty
+ (MappedObjectId
+ (instrument->getMappedId()),
+ MappedAudioFader::FaderLevel,
+ MappedObjectValue(dB));
+ instrument->setLevel(dB);
+ }
+
+ // send out to external controllers as well.
+ //!!! really want some notification of whether we have any!
+ if (controllerChannel < 16) {
+ int value = AudioLevel::dB_to_fader
+ (dB, 127, AudioLevel::LongFader);
+ MappedEvent mE(instrument->getId(),
+ MappedEvent::MidiController,
+ MIDI_CONTROLLER_VOLUME,
+ MidiByte(value));
+ mE.setRecordedChannel(controllerChannel);
+ mE.setRecordedDevice(Device::CONTROL_DEVICE);
+ StudioControl::sendMappedEvent(mE);
+ }
+
+ emit instrumentParametersChanged(i->first);
+ }
+
+ ++controllerChannel;
+ }
+}
+
+void
+AudioMixerWindow::slotPanChanged(float pan)
+{
+ const QObject *s = sender();
+
+ BussList busses = m_studio->getBusses();
+
+ int index = 1;
+
+ for (FaderVector::iterator i = m_submasters.begin();
+ i != m_submasters.end(); ++i) {
+
+ if (i->m_pan == s) {
+ if ((int)busses.size() > index) {
+ StudioControl::setStudioObjectProperty
+ (MappedObjectId(busses[index]->getMappedId()),
+ MappedAudioBuss::Pan,
+ MappedObjectValue(pan));
+ busses[index]->setPan(MidiByte(pan + 100.0));
+ }
+ return ;
+ }
+
+ ++index;
+ }
+
+ int controllerChannel = 0;
+
+ for (FaderMap::iterator i = m_faders.begin();
+ i != m_faders.end(); ++i) {
+
+ if (i->second.m_pan == s) {
+
+ Instrument *instrument =
+ m_studio->getInstrumentById(i->first);
+
+ if (instrument) {
+ StudioControl::setStudioObjectProperty
+ (instrument->getMappedId(),
+ MappedAudioFader::Pan,
+ MappedObjectValue(pan));
+ instrument->setPan(MidiByte(pan + 100.0));
+ }
+
+ // send out to external controllers as well.
+ //!!! really want some notification of whether we have any!
+ if (controllerChannel < 16) {
+ int ipan = (int(instrument->getPan()) * 64) / 100;
+ if (ipan < 0)
+ ipan = 0;
+ if (ipan > 127)
+ ipan = 127;
+ MappedEvent mE(instrument->getId(),
+ MappedEvent::MidiController,
+ MIDI_CONTROLLER_PAN,
+ MidiByte(ipan));
+ mE.setRecordedChannel(controllerChannel);
+ mE.setRecordedDevice(Device::CONTROL_DEVICE);
+ StudioControl::sendMappedEvent(mE);
+ }
+
+ emit instrumentParametersChanged(i->first);
+ }
+
+ ++controllerChannel;
+ }
+}
+
+void
+AudioMixerWindow::slotChannelsChanged()
+{
+ const QObject *s = sender();
+
+ // channels are only switchable on instruments
+
+ //!!! need to reconnect input, or change input channel number anyway
+
+
+ for (FaderMap::iterator i = m_faders.begin();
+ i != m_faders.end(); ++i) {
+
+ if (s == i->second.m_stereoButton) {
+
+ Instrument *instrument =
+ m_studio->getInstrumentById(i->first);
+
+ if (instrument) {
+ instrument->setAudioChannels
+ ((instrument->getAudioChannels() > 1) ? 1 : 2);
+ updateStereoButton(instrument->getId());
+ updateRouteButtons(instrument->getId());
+
+ emit instrumentParametersChanged(i->first);
+
+ return ;
+ }
+ }
+ }
+}
+
+void
+AudioMixerWindow::slotSoloChanged()
+{
+ //...
+}
+
+void
+AudioMixerWindow::slotMuteChanged()
+{
+ //...
+}
+
+void
+AudioMixerWindow::slotRecordChanged()
+{
+ //...
+}
+
+void
+AudioMixerWindow::updateMeters(SequencerMapper *mapper)
+{
+ for (FaderMap::iterator i = m_faders.begin(); i != m_faders.end(); ++i) {
+
+ InstrumentId id = i->first;
+ FaderRec &rec = i->second;
+ if (!rec.m_populated)
+ continue;
+
+ LevelInfo info;
+
+ if (mapper->getInstrumentLevelForMixer(id, info)) {
+
+ // The values passed through are long-fader values
+ float dBleft = AudioLevel::fader_to_dB
+ (info.level, 127, AudioLevel::LongFader);
+
+ if (rec.m_stereoness) {
+ float dBright = AudioLevel::fader_to_dB
+ (info.levelRight, 127, AudioLevel::LongFader);
+
+ rec.m_meter->setLevel(dBleft, dBright);
+
+ } else {
+ rec.m_meter->setLevel(dBleft);
+ }
+ }
+ }
+
+ for (unsigned int i = 0; i < m_submasters.size(); ++i) {
+
+ FaderRec &rec = m_submasters[i];
+
+ LevelInfo info;
+ if (!mapper->getSubmasterLevel(i, info))
+ continue;
+
+ // The values passed through are long-fader values
+ float dBleft = AudioLevel::fader_to_dB
+ (info.level, 127, AudioLevel::LongFader);
+ float dBright = AudioLevel::fader_to_dB
+ (info.levelRight, 127, AudioLevel::LongFader);
+
+ rec.m_meter->setLevel(dBleft, dBright);
+ }
+
+ updateMonitorMeters(mapper);
+
+ LevelInfo masterInfo;
+ if (mapper->getMasterLevel(masterInfo)) {
+
+ float dBleft = AudioLevel::fader_to_dB
+ (masterInfo.level, 127, AudioLevel::LongFader);
+ float dBright = AudioLevel::fader_to_dB
+ (masterInfo.levelRight, 127, AudioLevel::LongFader);
+
+ m_master.m_meter->setLevel(dBleft, dBright);
+ }
+}
+
+void
+AudioMixerWindow::updateMonitorMeters(SequencerMapper *mapper)
+{
+ // only show monitor levels when quiescent or when recording (as
+ // record levels)
+ if (m_document->getSequenceManager() &&
+ m_document->getSequenceManager()->getTransportStatus() == PLAYING) {
+ return ;
+ }
+
+ Composition &comp = m_document->getComposition();
+ Composition::trackcontainer &tracks = comp.getTracks();
+
+ for (FaderMap::iterator i = m_faders.begin(); i != m_faders.end(); ++i) {
+
+ InstrumentId id = i->first;
+ FaderRec &rec = i->second;
+ if (!rec.m_populated)
+ continue;
+
+ LevelInfo info;
+
+ if (mapper->getInstrumentRecordLevelForMixer(id, info)) {
+
+ bool armed = false;
+
+ for (Composition::trackcontainer::iterator ti =
+ tracks.begin(); ti != tracks.end(); ++ti) {
+ if (ti->second->getInstrument() == id) {
+ if (comp.isTrackRecording(ti->second->getId())) {
+ armed = true;
+ break;
+ }
+ }
+ }
+
+ if (!armed)
+ continue;
+
+ // The values passed through are long-fader values
+ float dBleft = AudioLevel::fader_to_dB
+ (info.level, 127, AudioLevel::LongFader);
+
+ if (rec.m_stereoness) {
+ float dBright = AudioLevel::fader_to_dB
+ (info.levelRight, 127, AudioLevel::LongFader);
+
+ rec.m_meter->setRecordLevel(dBleft, dBright);
+
+ } else {
+ rec.m_meter->setRecordLevel(dBleft);
+ }
+ }
+ }
+}
+
+void
+AudioMixerWindow::slotControllerDeviceEventReceived(MappedEvent *e,
+ const void *preferredCustomer)
+{
+ if (preferredCustomer != this)
+ return ;
+ RG_DEBUG << "AudioMixerWindow::slotControllerDeviceEventReceived: this one's for me" << endl;
+ raise();
+
+ // get channel number n from event
+ // update instrument for nth fader in m_faders
+
+ if (e->getType() != MappedEvent::MidiController)
+ return ;
+ unsigned int channel = e->getRecordedChannel();
+ MidiByte controller = e->getData1();
+ MidiByte value = e->getData2();
+
+ int count = 0;
+ for (FaderMap::iterator i = m_faders.begin(); i != m_faders.end(); ++i) {
+
+ if (count < channel) {
+ ++count;
+ continue;
+ }
+
+ Instrument *instrument =
+ m_studio->getInstrumentById(i->first);
+ if (!instrument)
+ continue;
+
+ switch (controller) {
+
+ case MIDI_CONTROLLER_VOLUME: {
+ float level = AudioLevel::fader_to_dB
+ (value, 127, AudioLevel::LongFader);
+
+ StudioControl::setStudioObjectProperty
+ (instrument->getMappedId(),
+ MappedAudioFader::FaderLevel,
+ MappedObjectValue(level));
+
+ instrument->setLevel(level);
+ break;
+ }
+
+ case MIDI_CONTROLLER_PAN: {
+ MidiByte ipan = MidiByte((value / 64.0) * 100.0 + 0.01);
+
+ StudioControl::setStudioObjectProperty
+ (instrument->getMappedId(),
+ MappedAudioFader::Pan,
+ MappedObjectValue(float(ipan) - 100.0));
+
+ instrument->setPan(ipan);
+ break;
+ }
+
+ default:
+ break;
+ }
+
+ slotUpdateInstrument(i->first);
+ emit instrumentParametersChanged(i->first);
+
+ break;
+ }
+}
+
+void
+AudioMixerWindow::slotSetInputCountFromAction()
+{
+ const QObject *s = sender();
+ QString name = s->name();
+
+ if (name.left(7) == "inputs_") {
+
+ int count = name.right(name.length() - 7).toInt();
+
+ RecordInList ins = m_studio->getRecordIns();
+ int current = ins.size();
+
+ if (count == current)
+ return ;
+
+ m_studio->clearRecordIns(); // leaves the default 1
+
+ for (int i = 1; i < count; ++i) {
+ m_studio->addRecordIn(new RecordIn());
+ }
+ }
+
+ m_document->initialiseStudio();
+
+ for (FaderMap::iterator i = m_faders.begin();
+ i != m_faders.end(); ++i) {
+ updateRouteButtons(i->first);
+ }
+}
+
+void
+AudioMixerWindow::slotSetSubmasterCountFromAction()
+{
+ const QObject *s = sender();
+ QString name = s->name();
+
+ if (name.left(11) == "submasters_") {
+
+ int count = name.right(name.length() - 11).toInt();
+
+ BussList busses = m_studio->getBusses();
+ int current = busses.size();
+
+ // offset by 1 generally to take into account the fact that
+ // the first buss in the studio is the master, not a submaster
+
+ if (count + 1 == current)
+ return ;
+
+ BussList dups;
+ for (int i = 0; i < count; ++i) {
+ if (i + 1 < int(busses.size())) {
+ dups.push_back(new Buss(*busses[i + 1]));
+ } else {
+ dups.push_back(new Buss(i + 1));
+ }
+ }
+
+ m_studio->clearBusses();
+
+ for (BussList::iterator i = dups.begin();
+ i != dups.end(); ++i) {
+ m_studio->addBuss(*i);
+ }
+ }
+
+ m_document->initialiseStudio();
+
+ populate();
+}
+
+void AudioMixerWindow::FaderRec::setVisible(bool visible)
+{
+ if (visible) {
+ if (m_input)
+ m_input->getWidget()->show();
+ if (m_output)
+ m_output->getWidget()->show();
+ if (m_pan)
+ m_pan->show();
+ if (m_fader)
+ m_fader->show();
+ if (m_meter)
+ m_meter->show();
+ // commented out until implemented
+ // if (m_muteButton) m_muteButton->show();
+ // if (m_soloButton) m_soloButton->show();
+ // if (m_recordButton) m_recordButton->show();
+ if (m_stereoButton)
+ m_stereoButton->show();
+
+ } else {
+
+ if (m_input)
+ m_input->getWidget()->hide();
+ if (m_output)
+ m_output->getWidget()->hide();
+ if (m_pan)
+ m_pan->hide();
+ if (m_fader)
+ m_fader->hide();
+ if (m_meter)
+ m_meter->hide();
+ // commented out until implemented
+ // if (m_muteButton) m_muteButton->hide();
+ // if (m_soloButton) m_soloButton->hide();
+ // if (m_recordButton) m_recordButton->hide();
+ if (m_stereoButton)
+ m_stereoButton->hide();
+ }
+
+ setPluginButtonsVisible(visible);
+
+}
+
+void
+AudioMixerWindow::FaderRec::setPluginButtonsVisible(bool visible)
+{
+ if (!m_pluginBox)
+ return ;
+
+ if (visible) {
+ m_pluginBox->show();
+ } else {
+ m_pluginBox->hide();
+ }
+}
+
+void
+AudioMixerWindow::slotToggleFaders()
+{
+ m_studio->setMixerDisplayOptions(m_studio->getMixerDisplayOptions() ^
+ MIXER_OMIT_FADERS);
+
+ slotUpdateFaderVisibility();
+}
+
+void
+AudioMixerWindow::slotUpdateFaderVisibility()
+{
+ bool d = !(m_studio->getMixerDisplayOptions() & MIXER_OMIT_FADERS);
+
+ KToggleAction *action = dynamic_cast<KToggleAction *>
+ (actionCollection()->action("show_audio_faders"));
+ if (action) {
+ action->setChecked(d);
+ }
+
+ RG_DEBUG << "AudioMixerWindow::slotUpdateFaderVisibility: visiblility is " << d << " (options " << m_studio->getMixerDisplayOptions() << ")" << endl;
+
+ for (FaderMap::iterator i = m_faders.begin(); i != m_faders.end(); ++i) {
+ if (i->first < SoftSynthInstrumentBase) {
+ FaderRec rec = i->second;
+ rec.setVisible(d);
+ }
+ }
+
+ toggleNamedWidgets(d, "audioIdLabel");
+
+ adjustSize();
+}
+
+void
+AudioMixerWindow::slotToggleSynthFaders()
+{
+ m_studio->setMixerDisplayOptions(m_studio->getMixerDisplayOptions() ^
+ MIXER_OMIT_SYNTH_FADERS);
+
+ slotUpdateSynthFaderVisibility();
+}
+
+void
+AudioMixerWindow::slotUpdateSynthFaderVisibility()
+{
+ KToggleAction *action = dynamic_cast<KToggleAction *>
+ (actionCollection()->action("show_synth_faders"));
+ if (!action)
+ return ;
+
+ action->setChecked(!(m_studio->getMixerDisplayOptions() &
+ MIXER_OMIT_SYNTH_FADERS));
+
+ for (FaderMap::iterator i = m_faders.begin(); i != m_faders.end(); ++i) {
+ if (i->first >= SoftSynthInstrumentBase) {
+ FaderRec rec = i->second;
+ rec.setVisible(action->isChecked());
+ }
+ }
+
+ toggleNamedWidgets(action->isChecked(), "synthIdLabel");
+
+ adjustSize();
+}
+
+void
+AudioMixerWindow::slotToggleSubmasters()
+{
+ m_studio->setMixerDisplayOptions(m_studio->getMixerDisplayOptions() ^
+ MIXER_OMIT_SUBMASTERS);
+
+ slotUpdateSubmasterVisibility();
+}
+
+void
+AudioMixerWindow::slotUpdateSubmasterVisibility()
+{
+ KToggleAction *action = dynamic_cast<KToggleAction *>
+ (actionCollection()->action("show_audio_submasters"));
+ if (!action)
+ return ;
+
+ action->setChecked(!(m_studio->getMixerDisplayOptions() &
+ MIXER_OMIT_SUBMASTERS));
+
+ for (FaderVector::iterator i = m_submasters.begin(); i != m_submasters.end(); ++i) {
+ FaderRec rec = *i;
+ rec.setVisible(action->isChecked());
+ }
+
+ toggleNamedWidgets(action->isChecked(), "subMaster");
+
+ adjustSize();
+}
+
+void
+AudioMixerWindow::slotTogglePluginButtons()
+{
+ m_studio->setMixerDisplayOptions(m_studio->getMixerDisplayOptions() ^
+ MIXER_OMIT_PLUGINS);
+
+ slotUpdatePluginButtonVisibility();
+}
+
+void
+AudioMixerWindow::slotUpdatePluginButtonVisibility()
+{
+ KToggleAction *action = dynamic_cast<KToggleAction *>
+ (actionCollection()->action("show_plugin_buttons"));
+ if (!action)
+ return ;
+
+ action->setChecked(!(m_studio->getMixerDisplayOptions() &
+ MIXER_OMIT_PLUGINS));
+
+ for (FaderMap::iterator i = m_faders.begin(); i != m_faders.end(); ++i) {
+ FaderRec rec = i->second;
+ rec.setPluginButtonsVisible(action->isChecked());
+ }
+
+ adjustSize();
+}
+
+void
+AudioMixerWindow::slotToggleUnassignedFaders()
+{
+ KToggleAction *action = dynamic_cast<KToggleAction *>
+ (actionCollection()->action("show_unassigned_faders"));
+ if (!action)
+ return ;
+
+ m_studio->setMixerDisplayOptions(m_studio->getMixerDisplayOptions() ^
+ MIXER_SHOW_UNASSIGNED_FADERS);
+
+ action->setChecked(m_studio->getMixerDisplayOptions() &
+ MIXER_SHOW_UNASSIGNED_FADERS);
+
+ populate();
+}
+
+void
+AudioMixerWindow::toggleNamedWidgets(bool show, const char* const name)
+{
+ QLayoutIterator it = m_mainBox->layout()->iterator();
+ QLayoutItem *child;
+ while ( (child = it.current()) != 0 ) {
+ QWidget * widget = child->widget();
+ if (widget && widget->name() && !strcmp(widget->name(), name)) {
+ if (show)
+ widget->show();
+ else
+ widget->hide();
+ }
+
+ ++it;
+ }
+
+}
+
+}
+#include "AudioMixerWindow.moc"
diff --git a/src/gui/studio/AudioMixerWindow.h b/src/gui/studio/AudioMixerWindow.h
new file mode 100644
index 0000000..99829de
--- /dev/null
+++ b/src/gui/studio/AudioMixerWindow.h
@@ -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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_AUDIOMIXERWINDOW_H_
+#define _RG_AUDIOMIXERWINDOW_H_
+
+#include "base/MidiProgram.h"
+#include <map>
+#include "MixerWindow.h"
+#include <qpixmap.h>
+#include <vector>
+
+
+class QWidget;
+class QVBox;
+class QPushButton;
+class QHBox;
+class QFrame;
+
+
+namespace Rosegarden
+{
+
+class SequencerMapper;
+class Rotary;
+class RosegardenGUIDoc;
+class MappedEvent;
+class Fader;
+class AudioVUMeter;
+class AudioRouteMenu;
+
+
+class AudioMixerWindow : public MixerWindow
+{
+ Q_OBJECT
+
+public:
+ AudioMixerWindow(QWidget *parent, RosegardenGUIDoc *document);
+ ~AudioMixerWindow();
+
+ void updateMeters(SequencerMapper *mapper);
+ void updateMonitorMeters(SequencerMapper *mapper);
+
+public slots:
+ void slotControllerDeviceEventReceived(MappedEvent *,
+ const void *);
+
+signals:
+ void selectPlugin(QWidget *, InstrumentId id, int index);
+
+ void play();
+ void stop();
+ void fastForwardPlayback();
+ void rewindPlayback();
+ void fastForwardPlaybackToEnd();
+ void rewindPlaybackToBeginning();
+ void record();
+ void panic();
+
+ // to be redirected to the instrument parameter box if necessary
+ void instrumentParametersChanged(InstrumentId);
+
+protected slots:
+ void slotFaderLevelChanged(float level);
+ void slotPanChanged(float value);
+ void slotInputChanged();
+ void slotOutputChanged();
+ void slotChannelsChanged();
+ void slotSoloChanged();
+ void slotMuteChanged();
+ void slotRecordChanged();
+ void slotSelectPlugin();
+
+ // to be called if something changes in an instrument parameter box
+ void slotUpdateInstrument(InstrumentId);
+
+ void slotTrackAssignmentsChanged();
+
+ // from Plugin dialog
+ void slotPluginSelected(InstrumentId id, int index, int plugin);
+ void slotPluginBypassed(InstrumentId id, int pluginIndex, bool bp);
+
+ void slotSetInputCountFromAction();
+ void slotSetSubmasterCountFromAction();
+
+ void slotToggleFaders();
+ void slotToggleSynthFaders();
+ void slotToggleSubmasters();
+ void slotTogglePluginButtons();
+ void slotToggleUnassignedFaders();
+
+ void slotUpdateFaderVisibility();
+ void slotUpdateSynthFaderVisibility();
+ void slotUpdateSubmasterVisibility();
+ void slotUpdatePluginButtonVisibility();
+
+protected:
+ virtual void sendControllerRefresh();
+
+private:
+
+ void toggleNamedWidgets(bool show, const char* const);
+
+
+ // manage the various bits of it in horizontal/vertical slices
+ // with other faders:
+
+ struct FaderRec {
+
+ FaderRec() :
+ m_populated(false),
+ m_input(0), m_output(0), m_pan(0), m_fader(0), m_meter(0),
+ m_muteButton(0), m_soloButton(0), m_recordButton(0),
+ m_stereoButton(0), m_stereoness(false), m_pluginBox(0)
+ { }
+
+ void setVisible(bool);
+ void setPluginButtonsVisible(bool);
+
+ bool m_populated;
+
+ AudioRouteMenu *m_input;
+ AudioRouteMenu *m_output;
+
+ Rotary *m_pan;
+ Fader *m_fader;
+ AudioVUMeter *m_meter;
+
+ QPushButton *m_muteButton;
+ QPushButton *m_soloButton;
+ QPushButton *m_recordButton;
+ QPushButton *m_stereoButton;
+ bool m_stereoness;
+
+ QVBox *m_pluginBox;
+ std::vector<QPushButton *> m_plugins;
+ };
+
+ QHBox *m_surroundBox;
+ QFrame *m_mainBox;
+
+ typedef std::map<InstrumentId, FaderRec> FaderMap;
+ FaderMap m_faders;
+
+ typedef std::vector<FaderRec> FaderVector;
+ FaderVector m_submasters;
+ FaderRec m_monitor;
+ FaderRec m_master;
+
+ void depopulate();
+ void populate();
+
+ bool isInstrumentAssigned(InstrumentId id);
+
+ void updateFader(int id); // instrument id if large enough, monitor if -1, master/sub otherwise
+ void updateRouteButtons(int id);
+ void updateStereoButton(int id);
+ void updatePluginButtons(int id);
+ void updateMiscButtons(int id);
+
+ QPixmap m_monoPixmap;
+ QPixmap m_stereoPixmap;
+};
+
+
+
+}
+
+#endif
diff --git a/src/gui/studio/AudioPlugin.cpp b/src/gui/studio/AudioPlugin.cpp
new file mode 100644
index 0000000..2cf3db2
--- /dev/null
+++ b/src/gui/studio/AudioPlugin.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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "AudioPlugin.h"
+
+#include "misc/Strings.h"
+#include "base/AudioPluginInstance.h"
+#include <qcolor.h>
+#include <qstring.h>
+
+
+namespace Rosegarden
+{
+
+AudioPlugin::AudioPlugin(const QString &identifier,
+ const QString &name,
+ unsigned long uniqueId,
+ const QString &label,
+ const QString &author,
+ const QString &copyright,
+ bool isSynth,
+ bool isGrouped,
+ const QString &category):
+ m_identifier(identifier),
+ m_name(name),
+ m_uniqueId(uniqueId),
+ m_label(label),
+ m_author(author),
+ m_copyright(copyright),
+ m_isSynth(isSynth),
+ m_isGrouped(isGrouped),
+ m_category(category),
+ m_colour(Qt::darkRed)
+{}
+
+void
+AudioPlugin::addPort(int number,
+ const QString &name,
+ PluginPort::PortType type,
+ PluginPort::PortDisplayHint hint,
+ PortData lowerBound,
+ PortData upperBound,
+ PortData defaultValue)
+{
+ PluginPort *port = new PluginPort(number,
+ qstrtostr(name),
+ type,
+ hint,
+ lowerBound,
+ upperBound,
+ defaultValue);
+ m_ports.push_back(port);
+
+}
+
+}
diff --git a/src/gui/studio/AudioPlugin.h b/src/gui/studio/AudioPlugin.h
new file mode 100644
index 0000000..591a43b
--- /dev/null
+++ b/src/gui/studio/AudioPlugin.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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_AUDIOPLUGIN_H_
+#define _RG_AUDIOPLUGIN_H_
+
+#include "base/AudioPluginInstance.h"
+#include <qcolor.h>
+#include <qstring.h>
+#include <vector>
+
+
+
+
+namespace Rosegarden
+{
+
+
+
+class AudioPlugin
+{
+public:
+ AudioPlugin(const QString &identifier,
+ const QString &name,
+ unsigned long uniqueId,
+ const QString &label,
+ const QString &author,
+ const QString &copyright,
+ bool isSynth,
+ bool isGrouped,
+ const QString &category);
+
+ QString getIdentifier() const { return m_identifier; }
+
+ QString getName() const { return m_name; }
+ unsigned long getUniqueId() const { return m_uniqueId; }
+ QString getLabel() const { return m_label; }
+ QString getAuthor() const { return m_author; }
+ QString getCopyright() const { return m_copyright; }
+ bool isSynth() const { return m_isSynth; }
+ bool isEffect() const { // true if >0 audio inputs
+ for (unsigned int i = 0; i < m_ports.size(); ++i) {
+ if ((m_ports[i]->getType() & PluginPort::Input) &&
+ (m_ports[i]->getType() & PluginPort::Audio)) {
+ return true;
+ }
+ }
+ return false;
+ }
+ bool isGrouped() const { return m_isGrouped; }
+ QString getCategory() const { return m_category; }
+
+ void addPort(int number,
+ const QString &name,
+ PluginPort::PortType type,
+ PluginPort::PortDisplayHint hint,
+ PortData lowerBound,
+ PortData upperBound,
+ PortData defaultVale);
+
+ typedef std::vector<PluginPort*>::iterator PortIterator;
+
+ PortIterator begin() { return m_ports.begin(); }
+ PortIterator end() { return m_ports.end(); }
+
+ QColor getColour() const { return m_colour; }
+ void setColour(const QColor &colour) { m_colour = colour; }
+
+protected:
+
+ QString m_identifier;
+
+ QString m_name;
+ unsigned long m_uniqueId;
+ QString m_label;
+ QString m_author;
+ QString m_copyright;
+ bool m_isSynth;
+ bool m_isGrouped;
+ QString m_category;
+
+ // our ports and associated hints
+ std::vector<PluginPort*> m_ports;
+
+ // Colour of this activated plugin
+ //
+ QColor m_colour;
+};
+
+typedef std::vector<AudioPlugin*>::iterator PluginIterator;
+
+
+}
+
+#endif
diff --git a/src/gui/studio/AudioPluginClipboard.cpp b/src/gui/studio/AudioPluginClipboard.cpp
new file mode 100644
index 0000000..54f5612
--- /dev/null
+++ b/src/gui/studio/AudioPluginClipboard.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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "AudioPluginClipboard.h"
+
+
+
+namespace Rosegarden
+{
+}
diff --git a/src/gui/studio/AudioPluginClipboard.h b/src/gui/studio/AudioPluginClipboard.h
new file mode 100644
index 0000000..e31ed90
--- /dev/null
+++ b/src/gui/studio/AudioPluginClipboard.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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_AUDIOPLUGINCLIPBOARD_H_
+#define _RG_AUDIOPLUGINCLIPBOARD_H_
+
+#include <map>
+#include <string>
+#include <vector>
+
+
+
+
+namespace Rosegarden
+{
+
+
+
+struct AudioPluginClipboard
+{
+ int m_pluginNumber;
+ std::map<std::string, std::string> m_configuration;
+ std::string m_program;
+ std::vector<float> m_controlValues;
+};
+
+
+}
+
+#endif
diff --git a/src/gui/studio/AudioPluginManager.cpp b/src/gui/studio/AudioPluginManager.cpp
new file mode 100644
index 0000000..6b64085
--- /dev/null
+++ b/src/gui/studio/AudioPluginManager.cpp
@@ -0,0 +1,307 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
+
+/*
+ Rosegarden
+ A MIDI and audio sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "AudioPluginManager.h"
+
+#include "misc/Debug.h"
+#include "AudioPluginClipboard.h"
+#include "AudioPlugin.h"
+#include "base/AudioPluginInstance.h"
+#include "gui/application/RosegardenApplication.h"
+#include "sound/PluginFactory.h"
+#include "sound/PluginIdentifier.h"
+#include <qcstring.h>
+#include <qdatastream.h>
+#include <qmutex.h>
+#include <qstring.h>
+#include <qthread.h>
+
+
+namespace Rosegarden
+{
+
+AudioPluginManager::AudioPluginManager() :
+ m_sampleRate(0),
+ m_enumerator(this)
+{
+// std::cerr << "AudioPluginManager[" << this << "]::AudioPluginManager - "
+// << "trace is ";
+// std::cerr << kdBacktrace() << std::endl;
+
+
+ // fetch from sequencer
+ fetchSampleRate();
+
+ // Clear the plugin clipboard
+ //
+ m_pluginClipboard.m_pluginNumber = -1;
+ m_pluginClipboard.m_program = "";
+ m_pluginClipboard.m_controlValues.clear();
+
+ m_enumerator.start();
+}
+
+AudioPluginManager::Enumerator::Enumerator(AudioPluginManager *manager) :
+ m_manager(manager),
+ m_done(false)
+{}
+
+void
+AudioPluginManager::Enumerator::run()
+{
+ QMutexLocker locker(&(m_manager->m_mutex));
+ MappedObjectPropertyList rawPlugins;
+
+ RG_DEBUG << "\n\nAudioPluginManager::Enumerator::run()\n\n" << endl;
+
+ if (!rgapp->noSequencerMode()) {
+ // We only waste the time looking for plugins here if we
+ // know we're actually going to be able to use them.
+ PluginFactory::enumerateAllPlugins(rawPlugins);
+ }
+
+ unsigned int i = 0;
+
+ while (i < rawPlugins.size()) {
+
+ QString identifier = rawPlugins[i++];
+ QString name = rawPlugins[i++];
+ unsigned long uniqueId = rawPlugins[i++].toLong();
+ QString label = rawPlugins[i++];
+ QString author = rawPlugins[i++];
+ QString copyright = rawPlugins[i++];
+ bool isSynth = ((rawPlugins[i++]).lower() == "true");
+ bool isGrouped = ((rawPlugins[i++]).lower() == "true");
+ QString category = rawPlugins[i++];
+ unsigned int portCount = rawPlugins[i++].toInt();
+
+ // std::cerr << "PLUGIN: " << i << ": " << (identifier ? identifier : "(null)") << " unique id " << uniqueId << " / CATEGORY: \"" << (category ? category : "(null)") << "\"" << std::endl;
+
+ AudioPlugin *aP = m_manager->addPlugin(identifier,
+ name,
+ uniqueId,
+ label,
+ author,
+ copyright,
+ isSynth,
+ isGrouped,
+ category);
+
+ for (unsigned int j = 0; j < portCount; j++) {
+
+ int number = rawPlugins[i++].toInt();
+ name = rawPlugins[i++];
+ PluginPort::PortType type =
+ PluginPort::PortType(rawPlugins[i++].toInt());
+ PluginPort::PortDisplayHint hint =
+ PluginPort::PortDisplayHint(rawPlugins[i++].toInt());
+ PortData lowerBound = rawPlugins[i++].toFloat();
+ PortData upperBound = rawPlugins[i++].toFloat();
+ PortData defaultValue = rawPlugins[i++].toFloat();
+
+ aP->addPort(number,
+ name,
+ type,
+ hint,
+ lowerBound,
+ upperBound,
+ defaultValue);
+ }
+ }
+
+ m_done = true;
+
+ RG_DEBUG << "\n\nAudioPluginManager::Enumerator::run() - done\n\n" << endl;
+}
+
+AudioPlugin*
+AudioPluginManager::addPlugin(const QString &identifier,
+ const QString &name,
+ unsigned long uniqueId,
+ const QString &label,
+ const QString &author,
+ const QString &copyright,
+ bool isSynth,
+ bool isGrouped,
+ const QString &category)
+{
+ AudioPlugin *newPlugin = new AudioPlugin(identifier,
+ name,
+ uniqueId,
+ label,
+ author,
+ copyright,
+ isSynth,
+ isGrouped,
+ category);
+ m_plugins.push_back(newPlugin);
+
+ return newPlugin;
+}
+
+bool
+AudioPluginManager::removePlugin(const QString &identifier)
+{
+ std::vector<AudioPlugin*>::iterator it = m_plugins.begin();
+
+ for (; it != m_plugins.end(); ++it) {
+ if ((*it)->getIdentifier() == identifier) {
+ delete *it;
+ m_plugins.erase(it);
+ return true;
+ }
+ }
+
+ return false;
+}
+
+std::vector<QString>
+AudioPluginManager::getPluginNames()
+{
+ awaitEnumeration();
+
+ std::vector<QString> names;
+
+ PluginIterator it = m_plugins.begin();
+
+ for (; it != m_plugins.end(); ++it)
+ names.push_back((*it)->getName());
+
+ return names;
+}
+
+AudioPlugin*
+AudioPluginManager::getPlugin(int number)
+{
+ awaitEnumeration();
+
+ if (number < 0 || number > (int(m_plugins.size()) - 1))
+ return 0;
+
+ return m_plugins[number];
+}
+
+int
+AudioPluginManager::getPositionByIdentifier(QString identifier)
+{
+ awaitEnumeration();
+
+ int pos = 0;
+ PluginIterator it = m_plugins.begin();
+
+ for (; it != m_plugins.end(); ++it) {
+ if ((*it)->getIdentifier() == identifier)
+ return pos;
+
+ pos++;
+ }
+
+ pos = 0;
+ it = m_plugins.begin();
+ for (; it != m_plugins.end(); ++it) {
+ if (PluginIdentifier::areIdentifiersSimilar((*it)->getIdentifier(), identifier))
+ return pos;
+
+ pos++;
+ }
+
+ return -1;
+}
+
+AudioPlugin*
+AudioPluginManager::getPluginByIdentifier(QString identifier)
+{
+ awaitEnumeration();
+
+ PluginIterator it = m_plugins.begin();
+ for (; it != m_plugins.end(); ++it) {
+ if ((*it)->getIdentifier() == identifier)
+ return (*it);
+ }
+
+ it = m_plugins.begin();
+ for (; it != m_plugins.end(); ++it) {
+ if (PluginIdentifier::areIdentifiersSimilar((*it)->getIdentifier(), identifier))
+ return (*it);
+ }
+
+ return 0;
+}
+
+AudioPlugin*
+AudioPluginManager::getPluginByUniqueId(unsigned long uniqueId)
+{
+ awaitEnumeration();
+
+ PluginIterator it = m_plugins.begin();
+ for (; it != m_plugins.end(); ++it) {
+ if ((*it)->getUniqueId() == uniqueId)
+ return (*it);
+ }
+
+ return 0;
+}
+
+PluginIterator
+AudioPluginManager::begin()
+{
+ awaitEnumeration();
+ return m_plugins.begin();
+}
+
+PluginIterator
+AudioPluginManager::end()
+{
+ awaitEnumeration();
+ return m_plugins.end();
+}
+
+void
+AudioPluginManager::awaitEnumeration()
+{
+ while (!m_enumerator.isDone()) {
+ RG_DEBUG << "\n\nAudioPluginManager::awaitEnumeration() - waiting\n\n" << endl;
+// m_mutex.lock();
+ usleep(100000);
+// m_mutex.unlock();
+ }
+}
+
+void
+AudioPluginManager::fetchSampleRate()
+{
+ QCString replyType;
+ QByteArray replyData;
+
+ if (rgapp->sequencerCall("getSampleRate()", replyType, replyData)) {
+
+ QDataStream streamIn(replyData, IO_ReadOnly);
+ unsigned int result;
+ streamIn >> result;
+ m_sampleRate = result;
+ }
+}
+
+}
diff --git a/src/gui/studio/AudioPluginManager.h b/src/gui/studio/AudioPluginManager.h
new file mode 100644
index 0000000..f8574f8
--- /dev/null
+++ b/src/gui/studio/AudioPluginManager.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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_AUDIOPLUGINMANAGER_H_
+#define _RG_AUDIOPLUGINMANAGER_H_
+
+#include "AudioPluginClipboard.h"
+#include <qmutex.h>
+#include <qstring.h>
+#include <qthread.h>
+#include <vector>
+#include "AudioPlugin.h"
+
+
+
+
+namespace Rosegarden
+{
+
+class AudioPlugin;
+
+
+class AudioPluginManager
+{
+public:
+ AudioPluginManager();
+
+ // Get a straight list of names
+ //
+ std::vector<QString> getPluginNames();
+
+ // Some useful members
+ //
+ AudioPlugin* getPlugin(int number);
+
+ AudioPlugin* getPluginByIdentifier(QString identifier);
+ int getPositionByIdentifier(QString identifier);
+
+ // Deprecated -- the GUI shouldn't be using unique ID because it's
+ // bound to a particular plugin type (and not necessarily unique
+ // anyway). It should use the identifier instead, which is a
+ // structured string managed by the sequencer. Keep this in only
+ // for compatibility with old .rg files.
+ //
+ AudioPlugin* getPluginByUniqueId(unsigned long uniqueId);
+
+ PluginIterator begin();
+ PluginIterator end();
+
+ // Sample rate
+ //
+ void setSampleRate(unsigned int rate) { m_sampleRate = rate; }
+ unsigned int getSampleRate() const { return m_sampleRate; }
+
+ AudioPluginClipboard* getPluginClipboard() { return &m_pluginClipboard; }
+
+protected:
+ AudioPlugin* addPlugin(const QString &identifier,
+ const QString &name,
+ unsigned long uniqueId,
+ const QString &label,
+ const QString &author,
+ const QString &copyright,
+ bool isSynth,
+ bool isGrouped,
+ const QString &category);
+
+ bool removePlugin(const QString &identifier);
+
+ class Enumerator : public QThread
+ {
+ public:
+ Enumerator(AudioPluginManager *);
+ virtual void run();
+ bool isDone() const { return m_done; }
+
+ protected:
+ AudioPluginManager *m_manager;
+ bool m_done;
+ };
+
+ void awaitEnumeration();
+ void fetchSampleRate();
+
+ std::vector<AudioPlugin*> m_plugins;
+ unsigned int m_sampleRate;
+ AudioPluginClipboard m_pluginClipboard;
+ Enumerator m_enumerator;
+ QMutex m_mutex;
+};
+
+
+
+}
+
+#endif
diff --git a/src/gui/studio/AudioPluginOSCGUI.cpp b/src/gui/studio/AudioPluginOSCGUI.cpp
new file mode 100644
index 0000000..106cbbe
--- /dev/null
+++ b/src/gui/studio/AudioPluginOSCGUI.cpp
@@ -0,0 +1,234 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
+
+/*
+ Rosegarden
+ A MIDI and audio sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifdef HAVE_LIBLO
+
+#include "AudioPluginOSCGUI.h"
+
+#include "misc/Debug.h"
+#include "misc/Strings.h"
+#include "base/AudioPluginInstance.h"
+#include "base/Exception.h"
+#include "sound/PluginIdentifier.h"
+#include <kprocess.h>
+#include <qdir.h>
+#include <qfileinfo.h>
+#include <qstring.h>
+#include <algorithm>
+
+
+namespace Rosegarden
+{
+
+AudioPluginOSCGUI::AudioPluginOSCGUI(AudioPluginInstance *instance,
+ QString serverURL, QString friendlyName) :
+ m_gui(0),
+ m_address(0),
+ m_basePath(""),
+ m_serverUrl(serverURL)
+{
+ QString identifier = strtoqstr(instance->getIdentifier());
+
+ QString filePath = getGUIFilePath(identifier);
+ if (!filePath) {
+ throw Exception("No GUI found");
+ }
+
+ QString type, soName, label;
+ PluginIdentifier::parseIdentifier(identifier, type, soName, label);
+ QFileInfo soInfo(soName);
+
+ // arguments: osc url, dll name, label, instance tag
+
+ m_gui = new KProcess();
+
+ *m_gui << filePath
+ << m_serverUrl
+ << soInfo.fileName()
+ << label
+ << friendlyName;
+
+ RG_DEBUG << "AudioPluginOSCGUI::AudioPluginOSCGUI: Starting process "
+ << filePath << " " << m_serverUrl << " "
+ << soInfo.fileName() << " " << label << " " << friendlyName << endl;
+
+ if (!m_gui->start(KProcess::NotifyOnExit, KProcess::NoCommunication)) {
+ RG_DEBUG << "AudioPluginOSCGUI::AudioPluginOSCGUI: Couldn't start process " << filePath << endl;
+ delete m_gui;
+ m_gui = 0;
+ throw Exception("Failed to start GUI");
+ }
+}
+
+AudioPluginOSCGUI::~AudioPluginOSCGUI()
+{
+ quit();
+}
+
+QString
+AudioPluginOSCGUI::getGUIFilePath(QString identifier)
+{
+ QString type, soName, label;
+ PluginIdentifier::parseIdentifier(identifier, type, soName, label);
+
+ RG_DEBUG << "AudioPluginOSCGUI::getGUIFilePath(" << identifier << ")" << endl;
+
+ QFileInfo soInfo(soName);
+ if (soInfo.isRelative()) {
+ //!!!
+ RG_DEBUG << "AudioPluginOSCGUI::AudioPluginOSCGUI: Unable to deal with relative .so path \"" << soName << "\" in identifier \"" << identifier << "\" yet" << endl;
+ throw Exception("Can't deal with relative .soname");
+ }
+
+ QDir dir(soInfo.dir());
+ QString fileBase(soInfo.baseName(TRUE));
+
+ if (!dir.cd(fileBase)) {
+ RG_DEBUG << "AudioPluginOSCGUI::AudioPluginOSCGUI: No GUI subdir for plugin .so " << soName << endl;
+ throw Exception("No GUI subdir available");
+ }
+
+ const QFileInfoList *list = dir.entryInfoList();
+
+ // in order of preference:
+ const char *suffixes[] = { "_rg", "_kde", "_qt", "_gtk2", "_gtk", "_x11", "_gui"
+ };
+ int nsuffixes = sizeof(suffixes) / sizeof(suffixes[0]);
+
+ for (int k = 0; k <= nsuffixes; ++k) {
+
+ for (int fuzzy = 0; fuzzy <= 1; ++fuzzy) {
+
+ QFileInfoListIterator i(*list);
+ QFileInfo *info;
+
+ while ((info = i.current()) != 0) {
+
+ RG_DEBUG << "Looking at " << info->fileName() << " in path "
+ << info->filePath() << " for suffix " << (k == nsuffixes ? "(none)" : suffixes[k]) << ", fuzzy " << fuzzy << endl;
+
+ ++i;
+
+ if (!(info->isFile() || info->isSymLink())
+ || !info->isExecutable()) {
+ RG_DEBUG << "(not executable)" << endl;
+ continue;
+ }
+
+ if (fuzzy) {
+ if (info->fileName().left(fileBase.length()) != fileBase)
+ continue;
+ RG_DEBUG << "(is file base)" << endl;
+ } else {
+ if (info->fileName().left(label.length()) != label)
+ continue;
+ RG_DEBUG << "(is label)" << endl;
+ }
+
+ if (k == nsuffixes || info->fileName().lower().endsWith(suffixes[k])) {
+ RG_DEBUG << "(ends with suffix " << (k == nsuffixes ? "(none)" : suffixes[k]) << " or out of suffixes)" << endl;
+ return info->filePath();
+ }
+ RG_DEBUG << "(doesn't end with suffix " << (k == nsuffixes ? "(none)" : suffixes[k]) << ")" << endl;
+ }
+ }
+ }
+
+ return QString();
+}
+
+void
+AudioPluginOSCGUI::setGUIUrl(QString url)
+{
+ if (m_address)
+ lo_address_free(m_address);
+
+ char *host = lo_url_get_hostname(url);
+ char *port = lo_url_get_port(url);
+ m_address = lo_address_new(host, port);
+ free(host);
+ free(port);
+
+ m_basePath = lo_url_get_path(url);
+}
+
+void
+AudioPluginOSCGUI::show()
+{
+ RG_DEBUG << "AudioPluginOSCGUI::show" << endl;
+
+ if (!m_address)
+ return ;
+ QString path = m_basePath + "/show";
+ lo_send(m_address, path, "");
+}
+
+void
+AudioPluginOSCGUI::hide()
+{
+ if (!m_address)
+ return ;
+ QString path = m_basePath + "/hide";
+ lo_send(m_address, path, "");
+}
+
+void
+AudioPluginOSCGUI::quit()
+{
+ if (!m_address)
+ return ;
+ QString path = m_basePath + "/quit";
+ lo_send(m_address, path, "");
+}
+
+void
+AudioPluginOSCGUI::sendProgram(int bank, int program)
+{
+ if (!m_address)
+ return ;
+ QString path = m_basePath + "/program";
+ lo_send(m_address, path, "ii", bank, program);
+}
+
+void
+AudioPluginOSCGUI::sendPortValue(int port, float value)
+{
+ if (!m_address)
+ return ;
+ QString path = m_basePath + "/control";
+ lo_send(m_address, path, "if", port, value);
+}
+
+void
+AudioPluginOSCGUI::sendConfiguration(QString key, QString value)
+{
+ if (!m_address)
+ return ;
+ QString path = m_basePath + "/configure";
+ lo_send(m_address, path, "ss", key.data(), value.data());
+}
+
+}
+
+#endif
diff --git a/src/gui/studio/AudioPluginOSCGUI.h b/src/gui/studio/AudioPluginOSCGUI.h
new file mode 100644
index 0000000..d1982f4
--- /dev/null
+++ b/src/gui/studio/AudioPluginOSCGUI.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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_AUDIOPLUGINOSCGUI_H_
+#define _RG_AUDIOPLUGINOSCGUI_H_
+
+#ifdef HAVE_LIBLO
+
+#include <lo/lo.h>
+
+#include <qstring.h>
+
+
+class KProcess;
+
+
+namespace Rosegarden
+{
+
+class AudioPluginInstance;
+
+
+class AudioPluginOSCGUI
+{
+public:
+ AudioPluginOSCGUI(AudioPluginInstance *instance,
+ QString serverURL, QString friendlyName);
+ virtual ~AudioPluginOSCGUI();
+
+ void setGUIUrl(QString url);
+
+ void show();
+ void hide();
+ void quit();
+ void sendProgram(int bank, int program);
+ void sendPortValue(int port, float value);
+ void sendConfiguration(QString key, QString value);
+
+ static QString getGUIFilePath(QString identifier);
+
+protected:
+ KProcess *m_gui;
+ lo_address m_address;
+ QString m_basePath;
+ QString m_serverUrl;
+};
+
+
+
+}
+
+
+#endif
+
+#endif
diff --git a/src/gui/studio/AudioPluginOSCGUIManager.cpp b/src/gui/studio/AudioPluginOSCGUIManager.cpp
new file mode 100644
index 0000000..54c23d7
--- /dev/null
+++ b/src/gui/studio/AudioPluginOSCGUIManager.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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifdef HAVE_LIBLO
+
+#include <lo/lo.h>
+
+#include "AudioPluginOSCGUIManager.h"
+
+#include "sound/Midi.h"
+#include <klocale.h>
+#include "misc/Debug.h"
+#include "misc/Strings.h"
+#include "AudioPluginOSCGUI.h"
+#include "base/AudioPluginInstance.h"
+#include "base/Exception.h"
+#include "base/Instrument.h"
+#include "base/MidiProgram.h"
+#include "base/RealTime.h"
+#include "base/Studio.h"
+#include "gui/application/RosegardenGUIApp.h"
+#include "OSCMessage.h"
+#include "sound/MappedEvent.h"
+#include "sound/PluginIdentifier.h"
+#include "StudioControl.h"
+#include "TimerCallbackAssistant.h"
+#include <qstring.h>
+
+
+namespace Rosegarden
+{
+
+static void osc_error(int num, const char *msg, const char *path)
+{
+ std::cerr << "Rosegarden: ERROR: liblo server error " << num
+ << " in path " << path << ": " << msg << std::endl;
+}
+
+static int osc_message_handler(const char *path, const char *types, lo_arg **argv,
+ int argc, lo_message, void *user_data)
+{
+ AudioPluginOSCGUIManager *manager = (AudioPluginOSCGUIManager *)user_data;
+
+ InstrumentId instrument;
+ int position;
+ QString method;
+
+ if (!manager->parseOSCPath(path, instrument, position, method)) {
+ return 1;
+ }
+
+ OSCMessage *message = new OSCMessage();
+ message->setTarget(instrument);
+ message->setTargetData(position);
+ message->setMethod(qstrtostr(method));
+
+ int arg = 0;
+ while (types && arg < argc && types[arg]) {
+ message->addArg(types[arg], argv[arg]);
+ ++arg;
+ }
+
+ manager->postMessage(message);
+ return 0;
+}
+
+AudioPluginOSCGUIManager::AudioPluginOSCGUIManager(RosegardenGUIApp *app) :
+ m_app(app),
+ m_studio(0),
+ m_haveOSCThread(false),
+ m_oscBuffer(1023),
+ m_dispatchTimer(0)
+{}
+
+AudioPluginOSCGUIManager::~AudioPluginOSCGUIManager()
+{
+ delete m_dispatchTimer;
+
+ for (TargetGUIMap::iterator i = m_guis.begin(); i != m_guis.end(); ++i) {
+ for (IntGUIMap::iterator j = i->second.begin(); j != i->second.end();
+ ++j) {
+ delete j->second;
+ }
+ }
+ m_guis.clear();
+
+#ifdef HAVE_LIBLO_THREADSTOP
+
+ if (m_haveOSCThread)
+ lo_server_thread_stop(m_serverThread);
+#endif
+}
+
+void
+AudioPluginOSCGUIManager::checkOSCThread()
+{
+ if (m_haveOSCThread)
+ return ;
+
+ m_serverThread = lo_server_thread_new(NULL, osc_error);
+
+ lo_server_thread_add_method(m_serverThread, NULL, NULL,
+ osc_message_handler, this);
+
+ lo_server_thread_start(m_serverThread);
+
+ RG_DEBUG << "AudioPluginOSCGUIManager: Base OSC URL is "
+ << lo_server_thread_get_url(m_serverThread) << endl;
+
+ m_dispatchTimer = new TimerCallbackAssistant(20, timerCallback, this);
+
+ m_haveOSCThread = true;
+}
+
+bool
+AudioPluginOSCGUIManager::hasGUI(InstrumentId instrument, int position)
+{
+ PluginContainer *container = 0;
+ container = m_studio->getContainerById(instrument);
+ if (!container) return false;
+
+ AudioPluginInstance *pluginInstance = container->getPlugin(position);
+ if (!pluginInstance) return false;
+
+ try {
+ QString filePath = AudioPluginOSCGUI::getGUIFilePath
+ (strtoqstr(pluginInstance->getIdentifier()));
+ return (filePath && filePath != "");
+ } catch (Exception e) { // that's OK
+ return false;
+ }
+}
+
+void
+AudioPluginOSCGUIManager::startGUI(InstrumentId instrument, int position)
+{
+ RG_DEBUG << "AudioPluginOSCGUIManager::startGUI: " << instrument << "," << position
+ << endl;
+
+ checkOSCThread();
+
+ if (m_guis.find(instrument) != m_guis.end() &&
+ m_guis[instrument].find(position) != m_guis[instrument].end()) {
+ RG_DEBUG << "stopping GUI first" << endl;
+ stopGUI(instrument, position);
+ }
+
+ // check the label
+ PluginContainer *container = 0;
+ container = m_studio->getContainerById(instrument);
+ if (!container) {
+ RG_DEBUG << "AudioPluginOSCGUIManager::startGUI: no such instrument or buss as "
+ << instrument << endl;
+ return;
+ }
+
+ AudioPluginInstance *pluginInstance = container->getPlugin(position);
+ if (!pluginInstance) {
+ RG_DEBUG << "AudioPluginOSCGUIManager::startGUI: no plugin at position "
+ << position << " for instrument " << instrument << endl;
+ return ;
+ }
+
+ try {
+ AudioPluginOSCGUI *gui =
+ new AudioPluginOSCGUI(pluginInstance,
+ getOSCUrl(instrument,
+ position,
+ strtoqstr(pluginInstance->getIdentifier())),
+ getFriendlyName(instrument,
+ position,
+ strtoqstr(pluginInstance->getIdentifier())));
+ m_guis[instrument][position] = gui;
+
+ } catch (Exception e) {
+
+ RG_DEBUG << "AudioPluginOSCGUIManager::startGUI: failed to start GUI: "
+ << e.getMessage() << endl;
+ }
+}
+
+void
+AudioPluginOSCGUIManager::showGUI(InstrumentId instrument, int position)
+{
+ RG_DEBUG << "AudioPluginOSCGUIManager::showGUI: " << instrument << "," << position
+ << endl;
+
+ if (m_guis.find(instrument) != m_guis.end() &&
+ m_guis[instrument].find(position) != m_guis[instrument].end()) {
+ m_guis[instrument][position]->show();
+ } else {
+ startGUI(instrument, position);
+ }
+}
+
+void
+AudioPluginOSCGUIManager::stopGUI(InstrumentId instrument, int position)
+{
+ if (m_guis.find(instrument) != m_guis.end() &&
+ m_guis[instrument].find(position) != m_guis[instrument].end()) {
+ delete m_guis[instrument][position];
+ m_guis[instrument].erase(position);
+ if (m_guis[instrument].empty())
+ m_guis.erase(instrument);
+ }
+}
+
+void
+AudioPluginOSCGUIManager::stopAllGUIs()
+{
+ while (!m_guis.empty()) {
+ while (!m_guis.begin()->second.empty()) {
+ delete (m_guis.begin()->second.begin()->second);
+ m_guis.begin()->second.erase(m_guis.begin()->second.begin());
+ }
+ m_guis.erase(m_guis.begin());
+ }
+}
+
+void
+AudioPluginOSCGUIManager::postMessage(OSCMessage *message)
+{
+ RG_DEBUG << "AudioPluginOSCGUIManager::postMessage" << endl;
+ m_oscBuffer.write(&message, 1);
+}
+
+void
+AudioPluginOSCGUIManager::updateProgram(InstrumentId instrument, int position)
+{
+ RG_DEBUG << "AudioPluginOSCGUIManager::updateProgram(" << instrument << ","
+ << position << ")" << endl;
+
+ if (m_guis.find(instrument) == m_guis.end() ||
+ m_guis[instrument].find(position) == m_guis[instrument].end())
+ return ;
+
+ PluginContainer *container = 0;
+ container = m_studio->getContainerById(instrument);
+ if (!container) return;
+
+ AudioPluginInstance *pluginInstance = container->getPlugin(position);
+ if (!pluginInstance) return;
+
+ unsigned long rv = StudioControl::getPluginProgram
+ (pluginInstance->getMappedId(),
+ strtoqstr(pluginInstance->getProgram()));
+
+ int bank = rv >> 16;
+ int program = rv - (bank << 16);
+
+ RG_DEBUG << "AudioPluginOSCGUIManager::updateProgram(" << instrument << ","
+ << position << "): rv " << rv << ", bank " << bank << ", program " << program << endl;
+
+ m_guis[instrument][position]->sendProgram(bank, program);
+}
+
+void
+AudioPluginOSCGUIManager::updatePort(InstrumentId instrument, int position,
+ int port)
+{
+ RG_DEBUG << "AudioPluginOSCGUIManager::updatePort(" << instrument << ","
+ << position << "," << port << ")" << endl;
+
+ if (m_guis.find(instrument) == m_guis.end() ||
+ m_guis[instrument].find(position) == m_guis[instrument].end())
+ return ;
+
+ PluginContainer *container = 0;
+ container = m_studio->getContainerById(instrument);
+ if (!container) return;
+
+ AudioPluginInstance *pluginInstance = container->getPlugin(position);
+ if (!pluginInstance)
+ return ;
+
+ PluginPortInstance *porti = pluginInstance->getPort(port);
+ if (!porti)
+ return ;
+
+ RG_DEBUG << "AudioPluginOSCGUIManager::updatePort(" << instrument << ","
+ << position << "," << port << "): value " << porti->value << endl;
+
+ m_guis[instrument][position]->sendPortValue(port, porti->value);
+}
+
+void
+AudioPluginOSCGUIManager::updateConfiguration(InstrumentId instrument, int position,
+ QString key)
+{
+ RG_DEBUG << "AudioPluginOSCGUIManager::updateConfiguration(" << instrument << ","
+ << position << "," << key << ")" << endl;
+
+ if (m_guis.find(instrument) == m_guis.end() ||
+ m_guis[instrument].find(position) == m_guis[instrument].end())
+ return ;
+
+ PluginContainer *container = m_studio->getContainerById(instrument);
+ if (!container) return;
+
+ AudioPluginInstance *pluginInstance = container->getPlugin(position);
+ if (!pluginInstance) return;
+
+ QString value = strtoqstr(pluginInstance->getConfigurationValue(qstrtostr(key)));
+
+ RG_DEBUG << "AudioPluginOSCGUIManager::updatePort(" << instrument << ","
+ << position << "," << key << "): value " << value << endl;
+
+ m_guis[instrument][position]->sendConfiguration(key, value);
+}
+
+QString
+AudioPluginOSCGUIManager::getOSCUrl(InstrumentId instrument, int position,
+ QString identifier)
+{
+ // OSC URL will be of the form
+ // osc.udp://localhost:54343/plugin/dssi/<instrument>/<position>/<label>
+ // where <position> will be "synth" for synth plugins
+
+ QString type, soName, label;
+ PluginIdentifier::parseIdentifier(identifier, type, soName, label);
+
+ QString baseUrl = lo_server_thread_get_url(m_serverThread);
+ if (!baseUrl.endsWith("/"))
+ baseUrl += '/';
+
+ QString url = QString("%1%2/%3/%4/%5/%6")
+ .arg(baseUrl)
+ .arg("plugin")
+ .arg(type)
+ .arg(instrument);
+
+ if (position == int(Instrument::SYNTH_PLUGIN_POSITION)) {
+ url = url.arg("synth");
+ } else {
+ url = url.arg(position);
+ }
+
+ url = url.arg(label);
+
+ return url;
+}
+
+bool
+AudioPluginOSCGUIManager::parseOSCPath(QString path, InstrumentId &instrument,
+ int &position, QString &method)
+{
+ RG_DEBUG << "AudioPluginOSCGUIManager::parseOSCPath(" << path << ")" << endl;
+ if (!m_studio)
+ return false;
+
+ QString pluginStr("/plugin/");
+
+ if (path.startsWith("//")) {
+ path = path.right(path.length() - 1);
+ }
+
+ if (!path.startsWith(pluginStr)) {
+ RG_DEBUG << "AudioPluginOSCGUIManager::parseOSCPath: malformed path "
+ << path << endl;
+ return false;
+ }
+
+ path = path.right(path.length() - pluginStr.length());
+
+ QString type = path.section('/', 0, 0);
+ QString instrumentStr = path.section('/', 1, 1);
+ QString positionStr = path.section('/', 2, 2);
+ QString label = path.section('/', 3, -2);
+ method = path.section('/', -1, -1);
+
+ if (!instrumentStr || !positionStr) {
+ RG_DEBUG << "AudioPluginOSCGUIManager::parseOSCPath: no instrument or position in " << path << endl;
+ return false;
+ }
+
+ instrument = instrumentStr.toUInt();
+
+ if (positionStr == "synth") {
+ position = Instrument::SYNTH_PLUGIN_POSITION;
+ } else {
+ position = positionStr.toInt();
+ }
+
+ // check the label
+ PluginContainer *container = m_studio->getContainerById(instrument);
+ if (!container) {
+ RG_DEBUG << "AudioPluginOSCGUIManager::parseOSCPath: no such instrument or buss as "
+ << instrument << " in path " << path << endl;
+ return false;
+ }
+
+ AudioPluginInstance *pluginInstance = container->getPlugin(position);
+ if (!pluginInstance) {
+ RG_DEBUG << "AudioPluginOSCGUIManager::parseOSCPath: no plugin at position "
+ << position << " for instrument " << instrument << " in path "
+ << path << endl;
+ return false;
+ }
+
+ QString identifier = strtoqstr(pluginInstance->getIdentifier());
+ QString iType, iSoName, iLabel;
+ PluginIdentifier::parseIdentifier(identifier, iType, iSoName, iLabel);
+ if (iLabel != label) {
+ RG_DEBUG << "AudioPluginOSCGUIManager::parseOSCPath: wrong label for plugin"
+ << " at position " << position << " for instrument " << instrument
+ << " in path " << path << " (actual label is " << iLabel
+ << ")" << endl;
+ return false;
+ }
+
+ RG_DEBUG << "AudioPluginOSCGUIManager::parseOSCPath: good path " << path
+ << ", got mapped id " << pluginInstance->getMappedId() << endl;
+
+ return true;
+}
+
+QString
+AudioPluginOSCGUIManager::getFriendlyName(InstrumentId instrument, int position,
+ QString)
+{
+ PluginContainer *container = m_studio->getContainerById(instrument);
+ if (!container)
+ return i18n("Rosegarden Plugin");
+ else {
+ if (position == int(Instrument::SYNTH_PLUGIN_POSITION)) {
+ return i18n("Rosegarden: %1").arg(strtoqstr(container->getPresentationName()));
+ } else {
+ return i18n("Rosegarden: %1: %2").arg(strtoqstr(container->getPresentationName()))
+ .arg(i18n("Plugin slot %1").arg(position));
+ }
+ }
+}
+
+void
+AudioPluginOSCGUIManager::timerCallback(void *data)
+{
+ AudioPluginOSCGUIManager *manager = (AudioPluginOSCGUIManager *)data;
+ manager->dispatch();
+}
+
+void
+AudioPluginOSCGUIManager::dispatch()
+{
+ if (!m_studio)
+ return ;
+
+ while (m_oscBuffer.getReadSpace() > 0) {
+
+ OSCMessage *message = 0;
+ m_oscBuffer.read(&message, 1);
+
+ int instrument = message->getTarget();
+ int position = message->getTargetData();
+
+ PluginContainer *container = m_studio->getContainerById(instrument);
+ if (!container) continue;
+
+ AudioPluginInstance *pluginInstance = container->getPlugin(position);
+ if (!pluginInstance) continue;
+
+ AudioPluginOSCGUI *gui = 0;
+
+ if (m_guis.find(instrument) == m_guis.end()) {
+ RG_DEBUG << "AudioPluginOSCGUIManager: no GUI for instrument "
+ << instrument << endl;
+ } else if (m_guis[instrument].find(position) == m_guis[instrument].end()) {
+ RG_DEBUG << "AudioPluginOSCGUIManager: no GUI for instrument "
+ << instrument << ", position " << position << endl;
+ } else {
+ gui = m_guis[instrument][position];
+ }
+
+ std::string method = message->getMethod();
+
+ char type;
+ const lo_arg *arg;
+
+ // These generally call back on the RosegardenGUIApp. We'd
+ // like to emit signals, but making AudioPluginOSCGUIManager a
+ // QObject is problematic if it's only conditionally compiled.
+
+ if (method == "control") {
+
+ if (message->getArgCount() != 2) {
+ RG_DEBUG << "AudioPluginOSCGUIManager: wrong number of args ("
+ << message->getArgCount() << ") for control method"
+ << endl;
+ goto done;
+ }
+ if (!(arg = message->getArg(0, type)) || type != 'i') {
+ RG_DEBUG << "AudioPluginOSCGUIManager: failed to get port number"
+ << endl;
+ goto done;
+ }
+ int port = arg->i;
+ if (!(arg = message->getArg(1, type)) || type != 'f') {
+ RG_DEBUG << "AudioPluginOSCGUIManager: failed to get port value"
+ << endl;
+ goto done;
+ }
+ float value = arg->f;
+
+ RG_DEBUG << "AudioPluginOSCGUIManager: setting port " << port
+ << " to value " << value << endl;
+
+ m_app->slotChangePluginPort(instrument, position, port, value);
+
+ } else if (method == "program") {
+
+ if (message->getArgCount() != 2) {
+ RG_DEBUG << "AudioPluginOSCGUIManager: wrong number of args ("
+ << message->getArgCount() << ") for program method"
+ << endl;
+ goto done;
+ }
+ if (!(arg = message->getArg(0, type)) || type != 'i') {
+ RG_DEBUG << "AudioPluginOSCGUIManager: failed to get bank number"
+ << endl;
+ goto done;
+ }
+ int bank = arg->i;
+ if (!(arg = message->getArg(1, type)) || type != 'i') {
+ RG_DEBUG << "AudioPluginOSCGUIManager: failed to get program number"
+ << endl;
+ goto done;
+ }
+ int program = arg->i;
+
+ QString programName = StudioControl::getPluginProgram
+ (pluginInstance->getMappedId(), bank, program);
+
+ m_app->slotChangePluginProgram(instrument, position, programName);
+
+ } else if (method == "update") {
+
+ if (message->getArgCount() != 1) {
+ RG_DEBUG << "AudioPluginOSCGUIManager: wrong number of args ("
+ << message->getArgCount() << ") for update method"
+ << endl;
+ goto done;
+ }
+ if (!(arg = message->getArg(0, type)) || type != 's') {
+ RG_DEBUG << "AudioPluginOSCGUIManager: failed to get GUI URL"
+ << endl;
+ goto done;
+ }
+ QString url = &arg->s;
+
+ if (!gui) {
+ RG_DEBUG << "AudioPluginOSCGUIManager: no GUI for update method"
+ << endl;
+ goto done;
+ }
+
+ gui->setGUIUrl(url);
+
+ for (AudioPluginInstance::ConfigMap::const_iterator i =
+ pluginInstance->getConfiguration().begin();
+ i != pluginInstance->getConfiguration().end(); ++i) {
+
+ QString key = strtoqstr(i->first);
+ QString value = strtoqstr(i->second);
+
+#ifdef DSSI_PROJECT_DIRECTORY_KEY
+
+ if (key == PluginIdentifier::RESERVED_PROJECT_DIRECTORY_KEY) {
+ key = DSSI_PROJECT_DIRECTORY_KEY;
+ }
+#endif
+
+ RG_DEBUG << "update: configuration: " << key << " -> "
+ << value << endl;
+
+ gui->sendConfiguration(key, value);
+ }
+
+ unsigned long rv = StudioControl::getPluginProgram
+ (pluginInstance->getMappedId(), strtoqstr(pluginInstance->getProgram()));
+
+ int bank = rv >> 16;
+ int program = rv - (bank << 16);
+ gui->sendProgram(bank, program);
+
+ int controlCount = 0;
+ for (PortInstanceIterator i = pluginInstance->begin();
+ i != pluginInstance->end(); ++i) {
+ gui->sendPortValue((*i)->number, (*i)->value);
+ /* Avoid overloading the GUI if there are lots and lots of ports */
+ if (++controlCount % 50 == 0)
+ usleep(300000);
+ }
+
+ gui->show();
+
+ } else if (method == "configure") {
+
+ if (message->getArgCount() != 2) {
+ RG_DEBUG << "AudioPluginOSCGUIManager: wrong number of args ("
+ << message->getArgCount() << ") for configure method"
+ << endl;
+ goto done;
+ }
+
+ if (!(arg = message->getArg(0, type)) || type != 's') {
+ RG_DEBUG << "AudioPluginOSCGUIManager: failed to get configure key"
+ << endl;
+ goto done;
+ }
+ QString key = &arg->s;
+
+ if (!(arg = message->getArg(1, type)) || type != 's') {
+ RG_DEBUG << "AudioPluginOSCGUIManager: failed to get configure value"
+ << endl;
+ goto done;
+ }
+ QString value = &arg->s;
+
+#ifdef DSSI_RESERVED_CONFIGURE_PREFIX
+
+ if (key.startsWith(DSSI_RESERVED_CONFIGURE_PREFIX) ||
+ key == PluginIdentifier::RESERVED_PROJECT_DIRECTORY_KEY) {
+ RG_DEBUG << "AudioPluginOSCGUIManager: illegal reserved configure call from gui: " << key << " -> " << value << endl;
+ goto done;
+ }
+#endif
+
+ RG_DEBUG << "AudioPluginOSCGUIManager: configure(" << key << "," << value
+ << ")" << endl;
+
+ m_app->slotChangePluginConfiguration(instrument, position,
+#ifdef DSSI_GLOBAL_CONFIGURE_PREFIX
+ key.startsWith(DSSI_GLOBAL_CONFIGURE_PREFIX),
+#else
+ false,
+#endif
+ key, value);
+
+ } else if (method == "midi") {
+
+ if (message->getArgCount() != 1) {
+ RG_DEBUG << "AudioPluginOSCGUIManager: wrong number of args ("
+ << message->getArgCount() << ") for midi method"
+ << endl;
+ goto done;
+ }
+ if (!(arg = message->getArg(0, type)) || type != 'm') {
+ RG_DEBUG << "AudioPluginOSCGUIManager: failed to get MIDI event"
+ << endl;
+ goto done;
+ }
+
+ RG_DEBUG << "AudioPluginOSCGUIManager: handling MIDI message" << endl;
+
+ // let's only handle note on and note off
+
+ int eventCode = arg->m[1];
+ int eventType = eventCode & MIDI_MESSAGE_TYPE_MASK;
+ if (eventType == MIDI_NOTE_ON ||
+ eventType == MIDI_NOTE_OFF) {
+ MappedEvent ev(instrument,
+ MappedEvent::MidiNote,
+ MidiByte(arg->m[2]),
+ MidiByte(arg->m[3]),
+ RealTime::zeroTime,
+ RealTime::zeroTime,
+ RealTime::zeroTime);
+ if (eventType == MIDI_NOTE_OFF)
+ ev.setVelocity(0);
+ StudioControl::sendMappedEvent(ev);
+ }
+
+ } else if (method == "exiting") {
+
+ RG_DEBUG << "AudioPluginOSCGUIManager: GUI exiting" << endl;
+ stopGUI(instrument, position);
+ m_app->slotPluginGUIExited(instrument, position);
+
+ } else {
+
+ RG_DEBUG << "AudioPluginOSCGUIManager: unknown method " << method << endl;
+ }
+
+done:
+ delete message;
+ }
+}
+
+}
+
+#endif
diff --git a/src/gui/studio/AudioPluginOSCGUIManager.h b/src/gui/studio/AudioPluginOSCGUIManager.h
new file mode 100644
index 0000000..0bef2a2
--- /dev/null
+++ b/src/gui/studio/AudioPluginOSCGUIManager.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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_AUDIOPLUGINOSCGUIMANAGER_H_
+#define _RG_AUDIOPLUGINOSCGUIMANAGER_H_
+
+#ifdef HAVE_LIBLO
+
+#include <lo/lo.h>
+#include "base/MidiProgram.h"
+#include <map>
+#include "sound/RingBuffer.h"
+#include <qstring.h>
+
+
+
+namespace Rosegarden
+{
+
+class TimerCallbackAssistant;
+class Studio;
+class RosegardenGUIApp;
+class OSCMessage;
+class AudioPluginOSCGUI;
+
+
+class AudioPluginOSCGUIManager
+{
+public:
+ AudioPluginOSCGUIManager(RosegardenGUIApp *app);
+ virtual ~AudioPluginOSCGUIManager();
+
+ void setStudio(Studio *studio) { m_studio = studio; }
+
+ bool hasGUI(InstrumentId id, int position);
+ void startGUI(InstrumentId id, int position);
+ void showGUI(InstrumentId id, int position);
+ void stopGUI(InstrumentId id, int position);
+ void stopAllGUIs();
+
+ void postMessage(OSCMessage *message); // I take over ownership of message
+ void dispatch();
+
+ void updateProgram(InstrumentId id, int position);
+ void updatePort(InstrumentId id, int position, int port);
+ void updateConfiguration(InstrumentId id, int position,
+ QString key);
+
+ QString getOSCUrl(InstrumentId instrument, int position,
+ QString identifier);
+ QString getFriendlyName(InstrumentId instrument, int position,
+ QString identifier);
+ bool parseOSCPath(QString path, InstrumentId &instrument, int &position,
+ QString &method);
+
+ static void timerCallback(void *data);
+ static void guiExitedCallback(void *data);
+
+protected:
+ RosegardenGUIApp *m_app;
+ Studio *m_studio;
+
+ bool m_haveOSCThread;
+ void checkOSCThread();
+
+ lo_server_thread m_serverThread;
+ RingBuffer<OSCMessage *> m_oscBuffer;
+
+ typedef std::map<int, AudioPluginOSCGUI *> IntGUIMap;
+ typedef std::map<int, IntGUIMap> TargetGUIMap;
+ TargetGUIMap m_guis;
+
+ TimerCallbackAssistant *m_dispatchTimer;
+};
+
+
+
+}
+
+#endif
+
+#endif
diff --git a/src/gui/studio/BankEditorDialog.cpp b/src/gui/studio/BankEditorDialog.cpp
new file mode 100644
index 0000000..20aaf2d
--- /dev/null
+++ b/src/gui/studio/BankEditorDialog.cpp
@@ -0,0 +1,1713 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
+
+/*
+ Rosegarden
+ A MIDI and audio sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "BankEditorDialog.h"
+#include <qlayout.h>
+#include <kapplication.h>
+
+#include <klocale.h>
+#include <kstddirs.h>
+#include "misc/Debug.h"
+#include "misc/Strings.h"
+#include "base/Device.h"
+#include "base/MidiDevice.h"
+#include "base/MidiProgram.h"
+#include "base/NotationTypes.h"
+#include "base/Studio.h"
+#include "commands/studio/ModifyDeviceCommand.h"
+#include "document/MultiViewCommandHistory.h"
+#include "document/RosegardenGUIDoc.h"
+#include "document/ConfigGroups.h"
+#include "gui/dialogs/ExportDeviceDialog.h"
+#include "gui/dialogs/ImportDeviceDialog.h"
+#include "MidiBankListViewItem.h"
+#include "MidiDeviceListViewItem.h"
+#include "MidiKeyMapListViewItem.h"
+#include "MidiKeyMappingEditor.h"
+#include "MidiProgramsEditor.h"
+#include <kaction.h>
+#include <kcombobox.h>
+#include <kcommand.h>
+#include <kfiledialog.h>
+#include <kglobal.h>
+#include <klistview.h>
+#include <kmainwindow.h>
+#include <kmessagebox.h>
+#include <kstdaccel.h>
+#include <kstdaction.h>
+#include <kxmlguiclient.h>
+#include <qcheckbox.h>
+#include <qdialog.h>
+#include <qdir.h>
+#include <qfileinfo.h>
+#include <qframe.h>
+#include <qgroupbox.h>
+#include <qhbox.h>
+#include <qpushbutton.h>
+#include <qsizepolicy.h>
+#include <qsplitter.h>
+#include <qstring.h>
+#include <qtooltip.h>
+#include <qvbox.h>
+#include <qvgroupbox.h>
+#include <qwidget.h>
+
+
+namespace Rosegarden
+{
+
+BankEditorDialog::BankEditorDialog(QWidget *parent,
+ RosegardenGUIDoc *doc,
+ DeviceId defaultDevice):
+ KMainWindow(parent, "bankeditordialog"),
+ m_studio(&doc->getStudio()),
+ m_doc(doc),
+ m_copyBank(Device::NO_DEVICE, -1),
+ m_modified(false),
+ m_keepBankList(false),
+ m_deleteAllReally(false),
+ m_lastDevice(Device::NO_DEVICE),
+ m_updateDeviceList(false)
+{
+ QVBox* mainFrame = new QVBox(this);
+ setCentralWidget(mainFrame);
+
+ setCaption(i18n("Manage MIDI Banks and Programs"));
+
+ QSplitter* splitter = new QSplitter(mainFrame);
+
+ QFrame* btnBox = new QFrame(mainFrame);
+
+ btnBox->setSizePolicy(QSizePolicy(QSizePolicy::Minimum, QSizePolicy::Fixed));
+
+ QHBoxLayout* layout = new QHBoxLayout(btnBox, 4, 10);
+
+ m_closeButton = new QPushButton(btnBox);
+ m_applyButton = new QPushButton(i18n("Apply"), btnBox);
+ m_resetButton = new QPushButton(i18n("Reset"), btnBox);
+
+ layout->addStretch(10);
+ layout->addWidget(m_applyButton);
+ layout->addWidget(m_resetButton);
+ layout->addSpacing(15);
+ layout->addWidget(m_closeButton);
+ layout->addSpacing(5);
+
+ connect(m_applyButton, SIGNAL(clicked()),
+ this, SLOT(slotApply()));
+ connect(m_resetButton, SIGNAL(clicked()),
+ this, SLOT(slotReset()));
+
+ //
+ // Left-side list view
+ //
+ QVBox* leftPart = new QVBox(splitter);
+ m_listView = new KListView(leftPart);
+ m_listView->addColumn(i18n("MIDI Device"));
+ m_listView->addColumn(i18n("Type"));
+ m_listView->addColumn(i18n("MSB"));
+ m_listView->addColumn(i18n("LSB"));
+ m_listView->setRootIsDecorated(true);
+ m_listView->setShowSortIndicator(true);
+ m_listView->setItemsRenameable(true);
+ m_listView->restoreLayout(kapp->config(), BankEditorConfigGroup);
+
+ QFrame *bankBox = new QFrame(leftPart);
+ QGridLayout *gridLayout = new QGridLayout(bankBox, 4, 2, 6, 6);
+
+ m_addBank = new QPushButton(i18n("Add Bank"), bankBox);
+ m_addKeyMapping = new QPushButton(i18n("Add Key Mapping"), bankBox);
+ m_delete = new QPushButton(i18n("Delete"), bankBox);
+ m_deleteAll = new QPushButton(i18n("Delete All"), bankBox);
+ gridLayout->addWidget(m_addBank, 0, 0);
+ gridLayout->addWidget(m_addKeyMapping, 0, 1);
+ gridLayout->addWidget(m_delete, 1, 0);
+ gridLayout->addWidget(m_deleteAll, 1, 1);
+
+ // Tips
+ //
+ QToolTip::add
+ (m_addBank,
+ i18n("Add a Bank to the current device"));
+
+ QToolTip::add
+ (m_addKeyMapping,
+ i18n("Add a Percussion Key Mapping to the current device"));
+
+ QToolTip::add
+ (m_delete,
+ i18n("Delete the current Bank or Key Mapping"));
+
+ QToolTip::add
+ (m_deleteAll,
+ i18n("Delete all Banks and Key Mappings from the current Device"));
+
+ m_importBanks = new QPushButton(i18n("Import..."), bankBox);
+ m_exportBanks = new QPushButton(i18n("Export..."), bankBox);
+ gridLayout->addWidget(m_importBanks, 2, 0);
+ gridLayout->addWidget(m_exportBanks, 2, 1);
+
+ // Tips
+ //
+ QToolTip::add
+ (m_importBanks,
+ i18n("Import Bank and Program data from a Rosegarden file to the current Device"));
+ QToolTip::add
+ (m_exportBanks,
+ i18n("Export all Device and Bank information to a Rosegarden format interchange file"));
+
+ m_copyPrograms = new QPushButton(i18n("Copy"), bankBox);
+ m_pastePrograms = new QPushButton(i18n("Paste"), bankBox);
+ gridLayout->addWidget(m_copyPrograms, 3, 0);
+ gridLayout->addWidget(m_pastePrograms, 3, 1);
+
+ // Tips
+ //
+ QToolTip::add
+ (m_copyPrograms,
+ i18n("Copy all Program names from current Bank to clipboard"));
+
+ QToolTip::add
+ (m_pastePrograms,
+ i18n("Paste Program names from clipboard to current Bank"));
+
+ connect(m_listView, SIGNAL(currentChanged(QListViewItem*)),
+ this, SLOT(slotPopulateDevice(QListViewItem*)));
+
+ QFrame *vbox = new QFrame(splitter);
+ QVBoxLayout *vboxLayout = new QVBoxLayout(vbox, 8, 6);
+
+ m_programEditor = new MidiProgramsEditor(this, vbox);
+ vboxLayout->addWidget(m_programEditor);
+
+ m_keyMappingEditor = new MidiKeyMappingEditor(this, vbox);
+ vboxLayout->addWidget(m_keyMappingEditor);
+ m_keyMappingEditor->hide();
+
+ m_programEditor->setSizePolicy(QSizePolicy(QSizePolicy::Minimum, QSizePolicy::Preferred));
+ m_keyMappingEditor->setSizePolicy(QSizePolicy(QSizePolicy::Minimum, QSizePolicy::Preferred));
+
+ m_optionBox = new QVGroupBox(i18n("Options"), vbox);
+ vboxLayout->addWidget(m_optionBox);
+
+ QHBox *variationBox = new QHBox(m_optionBox);
+ m_variationToggle = new QCheckBox(i18n("Show Variation list based on "), variationBox);
+ m_variationCombo = new KComboBox(variationBox);
+ m_variationCombo->insertItem(i18n("LSB"));
+ m_variationCombo->insertItem(i18n("MSB"));
+
+ // device/bank modification
+ connect(m_listView, SIGNAL(itemRenamed (QListViewItem*, const QString&, int)),
+ this, SLOT(slotModifyDeviceOrBankName(QListViewItem*, const QString&, int)));
+
+ connect(m_addBank, SIGNAL(clicked()),
+ this, SLOT(slotAddBank()));
+
+ connect(m_addKeyMapping, SIGNAL(clicked()),
+ this, SLOT(slotAddKeyMapping()));
+
+ connect(m_delete, SIGNAL(clicked()),
+ this, SLOT(slotDelete()));
+
+ connect(m_deleteAll, SIGNAL(clicked()),
+ this, SLOT(slotDeleteAll()));
+
+ connect(m_importBanks, SIGNAL(clicked()),
+ this, SLOT(slotImport()));
+
+ connect(m_exportBanks, SIGNAL(clicked()),
+ this, SLOT(slotExport()));
+
+ connect(m_copyPrograms, SIGNAL(clicked()),
+ this, SLOT(slotEditCopy()));
+
+ connect(m_pastePrograms, SIGNAL(clicked()),
+ this, SLOT(slotEditPaste()));
+
+ connect(m_variationToggle, SIGNAL(clicked()),
+ this, SLOT(slotVariationToggled()));
+
+ connect(m_variationCombo, SIGNAL(activated(int)),
+ this, SLOT(slotVariationChanged(int)));
+
+ setupActions();
+
+ m_doc->getCommandHistory()->attachView(actionCollection());
+ connect(m_doc->getCommandHistory(), SIGNAL(commandExecuted()),
+ this, SLOT(slotUpdate()));
+
+ // Initialise the dialog
+ //
+ initDialog();
+ setModified(false);
+
+ // Check for no Midi devices and disable everything
+ //
+ DeviceList *devices = m_studio->getDevices();
+ DeviceListIterator it;
+ bool haveMidiPlayDevice = false;
+ for (it = devices->begin(); it != devices->end(); ++it) {
+ MidiDevice *md =
+ dynamic_cast<MidiDevice *>(*it);
+ if (md && md->getDirection() == MidiDevice::Play) {
+ haveMidiPlayDevice = true;
+ break;
+ }
+ }
+ if (!haveMidiPlayDevice) {
+ leftPart->setDisabled(true);
+ m_programEditor->setDisabled(true);
+ m_keyMappingEditor->setDisabled(true);
+ m_optionBox->setDisabled(true);
+ }
+
+ if (defaultDevice != Device::NO_DEVICE) {
+ setCurrentDevice(defaultDevice);
+ }
+
+ setAutoSaveSettings(BankEditorConfigGroup, true);
+}
+
+BankEditorDialog::~BankEditorDialog()
+{
+ RG_DEBUG << "~BankEditorDialog()\n";
+
+ m_listView->saveLayout(kapp->config(), BankEditorConfigGroup);
+
+ if (m_doc) // see slotFileClose() for an explanation on why we need to test m_doc
+ m_doc->getCommandHistory()->detachView(actionCollection());
+}
+
+void
+BankEditorDialog::setupActions()
+{
+ KAction* close = KStdAction::close (this, SLOT(slotFileClose()), actionCollection());
+
+ m_closeButton->setText(close->text());
+ connect(m_closeButton, SIGNAL(clicked()),
+ this, SLOT(slotFileClose()));
+
+ KStdAction::copy (this, SLOT(slotEditCopy()), actionCollection());
+ KStdAction::paste (this, SLOT(slotEditPaste()), actionCollection());
+
+ // 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("bankeditor.rc");
+}
+
+void
+BankEditorDialog::initDialog()
+{
+ // Clear down
+ //
+ m_deviceNameMap.clear();
+ m_listView->clear();
+
+ // Fill list view
+ //
+ DeviceList *devices = m_studio->getDevices();
+ DeviceListIterator it;
+
+ for (it = devices->begin(); it != devices->end(); ++it) {
+ if ((*it)->getType() == Device::Midi) {
+ MidiDevice* midiDevice =
+ dynamic_cast<MidiDevice*>(*it);
+ if (!midiDevice)
+ continue;
+
+ // skip read-only devices
+ if (midiDevice->getDirection() == MidiDevice::Record)
+ continue;
+
+ m_deviceNameMap[midiDevice->getId()] = midiDevice->getName();
+ QString itemName = strtoqstr(midiDevice->getName());
+
+ RG_DEBUG << "BankEditorDialog::initDialog - adding "
+ << itemName << endl;
+
+ QListViewItem* deviceItem = new MidiDeviceListViewItem
+ (midiDevice->getId(), m_listView, itemName);
+ deviceItem->setOpen(true);
+
+ populateDeviceItem(deviceItem, midiDevice);
+ }
+ }
+
+ // Select the first Device
+ //
+ populateDevice(m_listView->firstChild());
+ m_listView->setSelected(m_listView->firstChild(), true);
+
+}
+
+void
+BankEditorDialog::updateDialog()
+{
+ // Update list view
+ //
+ DeviceList *devices = m_studio->getDevices();
+ DeviceListIterator it;
+ bool deviceLabelUpdate = false;
+
+ for (it = devices->begin(); it != devices->end(); ++it) {
+
+ if ((*it)->getType() != Device::Midi)
+ continue;
+
+ MidiDevice* midiDevice =
+ dynamic_cast<MidiDevice*>(*it);
+ if (!midiDevice)
+ continue;
+
+ // skip read-only devices
+ if (midiDevice->getDirection() == MidiDevice::Record)
+ continue;
+
+ if (m_deviceNameMap.find(midiDevice->getId()) != m_deviceNameMap.end()) {
+ // Device already displayed but make sure the label is up to date
+ //
+ QListViewItem* currentItem = m_listView->currentItem();
+
+ if (currentItem) {
+ MidiDeviceListViewItem* deviceItem =
+ getParentDeviceItem(currentItem);
+
+ if (deviceItem &&
+ deviceItem->getDeviceId() == midiDevice->getId()) {
+ if (deviceItem->text(0) != strtoqstr(midiDevice->getName())) {
+ deviceItem->setText(0,
+ strtoqstr(midiDevice->getName()));
+ m_deviceNameMap[midiDevice->getId()] =
+ midiDevice->getName();
+
+ /*
+ cout << "NEW TEXT FOR DEVICE " << midiDevice->getId()
+ << " IS " << midiDevice->getName() << endl;
+ cout << "LIST ITEM ID = "
+ << deviceItem->getDeviceId() << endl;
+ */
+
+ deviceLabelUpdate = true;
+ }
+
+ QListViewItem *child = deviceItem->firstChild();
+
+ while (child) {
+
+ MidiBankListViewItem *bankItem =
+ dynamic_cast<MidiBankListViewItem *>(child);
+
+ if (bankItem) {
+ bool percussion = bankItem->isPercussion();
+ int msb = bankItem->text(2).toInt();
+ int lsb = bankItem->text(3).toInt();
+ std::string bankName =
+ midiDevice->getBankName
+ (MidiBank(percussion, msb, lsb));
+ if (bankName != "" &&
+ bankItem->text(0) != strtoqstr(bankName)) {
+ bankItem->setText(0, strtoqstr(bankName));
+ }
+ }
+
+ child = child->nextSibling();
+ }
+ }
+ }
+
+ continue;
+ }
+
+ m_deviceNameMap[midiDevice->getId()] = midiDevice->getName();
+ QString itemName = strtoqstr(midiDevice->getName());
+
+ RG_DEBUG << "BankEditorDialog::updateDialog - adding "
+ << itemName << endl;
+
+ QListViewItem* deviceItem = new MidiDeviceListViewItem
+ (midiDevice->getId(), m_listView, itemName);
+ deviceItem->setOpen(true);
+
+ populateDeviceItem(deviceItem, midiDevice);
+ }
+
+ // delete items whose corresponding devices are no longer present,
+ // and update the other ones
+ //
+ std::vector<MidiDeviceListViewItem*> itemsToDelete;
+
+ MidiDeviceListViewItem* sibling = dynamic_cast<MidiDeviceListViewItem*>
+ (m_listView->firstChild());
+
+ while (sibling) {
+
+ if (m_deviceNameMap.find(sibling->getDeviceId()) == m_deviceNameMap.end())
+ itemsToDelete.push_back(sibling);
+ else
+ updateDeviceItem(sibling);
+
+ sibling = dynamic_cast<MidiDeviceListViewItem*>(sibling->nextSibling());
+ }
+
+ for (unsigned int i = 0; i < itemsToDelete.size(); ++i)
+ delete itemsToDelete[i];
+
+ m_listView->sort();
+
+ if (deviceLabelUpdate)
+ emit deviceNamesChanged();
+}
+
+void
+BankEditorDialog::setCurrentDevice(DeviceId device)
+{
+ for (QListViewItem *item = m_listView->firstChild(); item;
+ item = item->nextSibling()) {
+ MidiDeviceListViewItem * deviceItem =
+ dynamic_cast<MidiDeviceListViewItem *>(item);
+ if (deviceItem && deviceItem->getDeviceId() == device) {
+ m_listView->setSelected(item, true);
+ break;
+ }
+ }
+}
+
+void
+BankEditorDialog::populateDeviceItem(QListViewItem* deviceItem, MidiDevice* midiDevice)
+{
+ clearItemChildren(deviceItem);
+
+ QString itemName = strtoqstr(midiDevice->getName());
+
+ BankList banks = midiDevice->getBanks();
+ // add banks for this device
+ for (unsigned int i = 0; i < banks.size(); ++i) {
+ RG_DEBUG << "BankEditorDialog::populateDeviceItem - adding "
+ << itemName << " - " << strtoqstr(banks[i].getName())
+ << endl;
+ new MidiBankListViewItem(midiDevice->getId(), i, deviceItem,
+ strtoqstr(banks[i].getName()),
+ banks[i].isPercussion(),
+ banks[i].getMSB(), banks[i].getLSB());
+ }
+
+ const KeyMappingList &mappings = midiDevice->getKeyMappings();
+ for (unsigned int i = 0; i < mappings.size(); ++i) {
+ RG_DEBUG << "BankEditorDialog::populateDeviceItem - adding key mapping "
+ << itemName << " - " << strtoqstr(mappings[i].getName())
+ << endl;
+ new MidiKeyMapListViewItem(midiDevice->getId(), deviceItem,
+ strtoqstr(mappings[i].getName()));
+ }
+}
+
+void
+BankEditorDialog::updateDeviceItem(MidiDeviceListViewItem* deviceItem)
+{
+ MidiDevice* midiDevice = getMidiDevice(deviceItem->getDeviceId());
+ if (!midiDevice) {
+ RG_DEBUG << "BankEditorDialog::updateDeviceItem : WARNING no midi device for this item\n";
+ return ;
+ }
+
+ QString itemName = strtoqstr(midiDevice->getName());
+
+ BankList banks = midiDevice->getBanks();
+ KeyMappingList keymaps = midiDevice->getKeyMappings();
+
+ // add missing banks for this device
+ //
+ for (unsigned int i = 0; i < banks.size(); ++i) {
+ if (deviceItemHasBank(deviceItem, i))
+ continue;
+
+ RG_DEBUG << "BankEditorDialog::updateDeviceItem - adding "
+ << itemName << " - " << strtoqstr(banks[i].getName())
+ << endl;
+ new MidiBankListViewItem(midiDevice->getId(), i, deviceItem,
+ strtoqstr(banks[i].getName()),
+ banks[i].isPercussion(),
+ banks[i].getMSB(), banks[i].getLSB());
+ }
+
+ for (unsigned int i = 0; i < keymaps.size(); ++i) {
+
+ QListViewItem *child = deviceItem->firstChild();
+ bool have = false;
+
+ while (child) {
+ MidiKeyMapListViewItem *keyItem =
+ dynamic_cast<MidiKeyMapListViewItem*>(child);
+ if (keyItem) {
+ if (keyItem->getName() == strtoqstr(keymaps[i].getName())) {
+ have = true;
+ }
+ }
+ child = child->nextSibling();
+ }
+
+ if (have)
+ continue;
+
+ RG_DEBUG << "BankEditorDialog::updateDeviceItem - adding "
+ << itemName << " - " << strtoqstr(keymaps[i].getName())
+ << endl;
+ new MidiKeyMapListViewItem(midiDevice->getId(), deviceItem,
+ strtoqstr(keymaps[i].getName()));
+ }
+
+ // delete banks which are no longer present
+ //
+ std::vector<QListViewItem*> childrenToDelete;
+
+ QListViewItem* child = deviceItem->firstChild();
+
+ while (child) {
+
+ MidiBankListViewItem *bankItem =
+ dynamic_cast<MidiBankListViewItem *>(child);
+ if (bankItem) {
+ if (bankItem->getBank() >= int(banks.size()))
+ childrenToDelete.push_back(child);
+ else { // update the banks MSB/LSB which might have changed
+ bankItem->setPercussion(banks[bankItem->getBank()].isPercussion());
+ bankItem->setMSB(banks[bankItem->getBank()].getMSB());
+ bankItem->setLSB(banks[bankItem->getBank()].getLSB());
+ }
+ }
+
+ MidiKeyMapListViewItem *keyItem =
+ dynamic_cast<MidiKeyMapListViewItem *>(child);
+ if (keyItem) {
+ if (!midiDevice->getKeyMappingByName(qstrtostr(keyItem->getName()))) {
+ childrenToDelete.push_back(child);
+ }
+ }
+
+ child = child->nextSibling();
+ }
+
+ for (unsigned int i = 0; i < childrenToDelete.size(); ++i)
+ delete childrenToDelete[i];
+}
+
+bool
+BankEditorDialog::deviceItemHasBank(MidiDeviceListViewItem* deviceItem, int bankNb)
+{
+ QListViewItem *child = deviceItem->firstChild();
+
+ while (child) {
+ MidiBankListViewItem *bankItem =
+ dynamic_cast<MidiBankListViewItem*>(child);
+ if (bankItem) {
+ if (bankItem->getBank() == bankNb)
+ return true;
+ }
+ child = child->nextSibling();
+ }
+
+ return false;
+}
+
+void
+BankEditorDialog::clearItemChildren(QListViewItem* item)
+{
+ QListViewItem* child = 0;
+
+ while ((child = item->firstChild()))
+ delete child;
+}
+
+MidiDevice*
+BankEditorDialog::getCurrentMidiDevice()
+{
+ return getMidiDevice(m_listView->currentItem());
+}
+
+void
+BankEditorDialog::checkModified()
+{
+ if (!m_modified)
+ return ;
+
+ setModified(false);
+
+ // // then ask if we want to apply the changes
+
+ // int reply = KMessageBox::questionYesNo(this,
+ // i18n("Apply pending changes?"));
+
+ ModifyDeviceCommand *command = 0;
+ MidiDevice *device = getMidiDevice(m_lastDevice);
+ if (!device) {
+ RG_DEBUG << "%%% WARNING : BankEditorDialog::checkModified() - NO MIDI DEVICE for device "
+ << m_lastDevice << endl;
+ return ;
+ }
+
+ if (m_bankList.size() == 0 && m_programList.size() == 0) {
+
+ command = new ModifyDeviceCommand(m_studio,
+ m_lastDevice,
+ m_deviceNameMap[m_lastDevice],
+ device->getLibrarianName(),
+ device->getLibrarianEmail()); // rename
+
+ command->clearBankAndProgramList();
+
+ } else {
+
+ MidiDevice::VariationType variation =
+ MidiDevice::NoVariations;
+ if (m_variationToggle->isChecked()) {
+ if (m_variationCombo->currentItem() == 0) {
+ variation = MidiDevice::VariationFromLSB;
+ } else {
+ variation = MidiDevice::VariationFromMSB;
+ }
+ }
+
+ command = new ModifyDeviceCommand(m_studio,
+ m_lastDevice,
+ m_deviceNameMap[m_lastDevice],
+ device->getLibrarianName(),
+ device->getLibrarianEmail());
+
+ command->setVariation(variation);
+ command->setBankList(m_bankList);
+ command->setProgramList(m_programList);
+ }
+
+ addCommandToHistory(command);
+
+ setModified(false);
+}
+
+void
+BankEditorDialog::slotPopulateDevice(QListViewItem* item)
+{
+ RG_DEBUG << "BankEditorDialog::slotPopulateDevice" << endl;
+
+ if (!item)
+ return ;
+
+ checkModified();
+
+ populateDevice(item);
+}
+
+void
+BankEditorDialog::populateDevice(QListViewItem* item)
+{
+ RG_DEBUG << "BankEditorDialog::populateDevice\n";
+
+ if (!item)
+ return ;
+
+ MidiKeyMapListViewItem *keyItem = dynamic_cast<MidiKeyMapListViewItem *>(item);
+
+ if (keyItem) {
+
+ stateChanged("on_key_item");
+ stateChanged("on_bank_item", KXMLGUIClient::StateReverse);
+
+ m_delete->setEnabled(true);
+
+ MidiDevice *device = getMidiDevice(keyItem->getDeviceId());
+ if (!device)
+ return ;
+
+ setProgramList(device);
+
+ m_keyMappingEditor->populate(item);
+
+ m_programEditor->hide();
+ m_keyMappingEditor->show();
+
+ m_lastDevice = keyItem->getDeviceId();
+
+ return ;
+ }
+
+ MidiBankListViewItem* bankItem = dynamic_cast<MidiBankListViewItem*>(item);
+
+ if (bankItem) {
+
+ stateChanged("on_bank_item");
+ stateChanged("on_key_item", KXMLGUIClient::StateReverse);
+
+ m_delete->setEnabled(true);
+ m_copyPrograms->setEnabled(true);
+
+ if (m_copyBank.first != Device::NO_DEVICE)
+ m_pastePrograms->setEnabled(true);
+
+ MidiDevice *device = getMidiDevice(bankItem->getDeviceId());
+ if (!device)
+ return ;
+
+ if (!m_keepBankList || m_bankList.size() == 0)
+ m_bankList = device->getBanks();
+ else
+ m_keepBankList = false;
+
+ setProgramList(device);
+
+ m_variationToggle->setChecked(device->getVariationType() !=
+ MidiDevice::NoVariations);
+ m_variationCombo->setEnabled(m_variationToggle->isChecked());
+ m_variationCombo->setCurrentItem
+ (device->getVariationType() ==
+ MidiDevice::VariationFromLSB ? 0 : 1);
+
+ m_lastBank = m_bankList[bankItem->getBank()];
+
+ m_programEditor->populate(item);
+
+ m_keyMappingEditor->hide();
+ m_programEditor->show();
+
+ m_lastDevice = bankItem->getDeviceId();
+
+ return ;
+ }
+
+ // Device, not bank or key mapping
+ // Ensure we fill these lists for the new device
+ //
+ MidiDeviceListViewItem* deviceItem = getParentDeviceItem(item);
+
+ m_lastDevice = deviceItem->getDeviceId();
+
+ MidiDevice *device = getMidiDevice(deviceItem);
+ if (!device) {
+ RG_DEBUG << "BankEditorDialog::populateDevice - no device for this item\n";
+ return ;
+ }
+
+ m_bankList = device->getBanks();
+ setProgramList(device);
+
+ RG_DEBUG << "BankEditorDialog::populateDevice : not a bank item - disabling" << endl;
+ m_delete->setEnabled(false);
+ m_copyPrograms->setEnabled(false);
+ m_pastePrograms->setEnabled(false);
+
+ m_variationToggle->setChecked(device->getVariationType() !=
+ MidiDevice::NoVariations);
+ m_variationCombo->setEnabled(m_variationToggle->isChecked());
+ m_variationCombo->setCurrentItem
+ (device->getVariationType() ==
+ MidiDevice::VariationFromLSB ? 0 : 1);
+
+ stateChanged("on_bank_item", KXMLGUIClient::StateReverse);
+ stateChanged("on_key_item", KXMLGUIClient::StateReverse);
+ m_programEditor->clearAll();
+ m_keyMappingEditor->clearAll();
+}
+
+void
+BankEditorDialog::slotApply()
+{
+ RG_DEBUG << "BankEditorDialog::slotApply()\n";
+
+ ModifyDeviceCommand *command = 0;
+
+ MidiDevice *device = getMidiDevice(m_lastDevice);
+
+ // Make sure that we don't delete all the banks and programs
+ // if we've not populated them here yet.
+ //
+ if (m_bankList.size() == 0 && m_programList.size() == 0 &&
+ m_deleteAllReally == false) {
+ RG_DEBUG << "BankEditorDialog::slotApply() : m_bankList size = 0\n";
+
+ command = new ModifyDeviceCommand(m_studio,
+ m_lastDevice,
+ m_deviceNameMap[m_lastDevice],
+ device->getLibrarianName(),
+ device->getLibrarianEmail());
+
+ command->clearBankAndProgramList();
+ } else {
+ MidiDevice::VariationType variation =
+ MidiDevice::NoVariations;
+ if (m_variationToggle->isChecked()) {
+ if (m_variationCombo->currentItem() == 0) {
+ variation = MidiDevice::VariationFromLSB;
+ } else {
+ variation = MidiDevice::VariationFromMSB;
+ }
+ }
+
+ RG_DEBUG << "BankEditorDialog::slotApply() : m_bankList size = "
+ << m_bankList.size() << endl;
+
+ command = new ModifyDeviceCommand(m_studio,
+ m_lastDevice,
+ m_deviceNameMap[m_lastDevice],
+ device->getLibrarianName(),
+ device->getLibrarianEmail());
+
+ MidiKeyMapListViewItem *keyItem = dynamic_cast<MidiKeyMapListViewItem*>
+ (m_listView->currentItem());
+ if (keyItem) {
+ KeyMappingList kml(device->getKeyMappings());
+ for (int i = 0; i < kml.size(); ++i) {
+ if (kml[i].getName() == qstrtostr(keyItem->getName())) {
+ kml[i] = m_keyMappingEditor->getMapping();
+ break;
+ }
+ }
+ command->setKeyMappingList(kml);
+ }
+
+ command->setVariation(variation);
+ command->setBankList(m_bankList);
+ command->setProgramList(m_programList);
+ }
+
+ addCommandToHistory(command);
+
+ // Our freaky fudge to update instrument/device names externally
+ //
+ if (m_updateDeviceList) {
+ emit deviceNamesChanged();
+ m_updateDeviceList = false;
+ }
+
+ setModified(false);
+}
+
+void
+BankEditorDialog::slotReset()
+{
+ resetProgramList();
+
+ m_programEditor->reset();
+ m_programEditor->populate(m_listView->currentItem());
+ m_keyMappingEditor->reset();
+ m_keyMappingEditor->populate(m_listView->currentItem());
+
+ MidiDeviceListViewItem* deviceItem = getParentDeviceItem
+ (m_listView->currentItem());
+
+ if (deviceItem) {
+ MidiDevice *device = getMidiDevice(deviceItem);
+ m_variationToggle->setChecked(device->getVariationType() !=
+ MidiDevice::NoVariations);
+ m_variationCombo->setEnabled(m_variationToggle->isChecked());
+ m_variationCombo->setCurrentItem
+ (device->getVariationType() ==
+ MidiDevice::VariationFromLSB ? 0 : 1);
+ }
+
+ updateDialog();
+
+ setModified(false);
+}
+
+void
+BankEditorDialog::resetProgramList()
+{
+ m_programList = m_oldProgramList;
+}
+
+void
+BankEditorDialog::setProgramList(MidiDevice *device)
+{
+ m_programList = device->getPrograms();
+ m_oldProgramList = m_programList;
+}
+
+void
+BankEditorDialog::slotUpdate()
+{
+ updateDialog();
+}
+
+MidiDeviceListViewItem*
+BankEditorDialog::getParentDeviceItem(QListViewItem* item)
+{
+ if (!item)
+ return 0;
+
+ if (dynamic_cast<MidiBankListViewItem*>(item))
+ // go up to the parent device item
+ item = item->parent();
+
+ if (dynamic_cast<MidiKeyMapListViewItem*>(item))
+ // go up to the parent device item
+ item = item->parent();
+
+ if (!item) {
+ RG_DEBUG << "BankEditorDialog::getParentDeviceItem : missing parent device item for bank item - this SHOULD NOT HAPPEN" << endl;
+ return 0;
+ }
+
+ return dynamic_cast<MidiDeviceListViewItem*>(item);
+}
+
+void
+BankEditorDialog::slotAddBank()
+{
+ if (!m_listView->currentItem())
+ return ;
+
+ QListViewItem* currentItem = m_listView->currentItem();
+
+ MidiDeviceListViewItem* deviceItem = getParentDeviceItem(currentItem);
+ MidiDevice *device = getMidiDevice(currentItem);
+
+ if (device) {
+ // If the bank and program lists are empty then try to
+ // populate them.
+ //
+ if (m_bankList.size() == 0 && m_programList.size() == 0) {
+ m_bankList = device->getBanks();
+ setProgramList(device);
+ }
+
+ std::pair<int, int> bank = getFirstFreeBank(m_listView->currentItem());
+
+ MidiBank newBank(false,
+ bank.first, bank.second,
+ qstrtostr(i18n("<new bank>")));
+ m_bankList.push_back(newBank);
+
+ QListViewItem* newBankItem =
+ new MidiBankListViewItem(deviceItem->getDeviceId(),
+ m_bankList.size() - 1,
+ deviceItem,
+ strtoqstr(newBank.getName()),
+ newBank.isPercussion(),
+ newBank.getMSB(), newBank.getLSB());
+ keepBankListForNextPopulate();
+ m_listView->setCurrentItem(newBankItem);
+
+ slotApply();
+ selectDeviceItem(device);
+ }
+}
+
+void
+BankEditorDialog::slotAddKeyMapping()
+{
+ if (!m_listView->currentItem())
+ return ;
+
+ QListViewItem* currentItem = m_listView->currentItem();
+
+ MidiDeviceListViewItem* deviceItem = getParentDeviceItem(currentItem);
+ MidiDevice *device = getMidiDevice(currentItem);
+
+ if (device) {
+
+ QString name = "";
+ int n = 0;
+ while (name == "" || device->getKeyMappingByName(qstrtostr(name)) != 0) {
+ ++n;
+ if (n == 1)
+ name = i18n("<new mapping>");
+ else
+ name = i18n("<new mapping %1>").arg(n);
+ }
+
+ MidiKeyMapping newKeyMapping(qstrtostr(name));
+
+ ModifyDeviceCommand *command = new ModifyDeviceCommand
+ (m_studio,
+ device->getId(),
+ device->getName(),
+ device->getLibrarianName(),
+ device->getLibrarianEmail());
+
+ KeyMappingList kml;
+ kml.push_back(newKeyMapping);
+ command->setKeyMappingList(kml);
+ command->setOverwrite(false);
+ command->setRename(false);
+
+ addCommandToHistory(command);
+
+ updateDialog();
+ selectDeviceItem(device);
+ }
+}
+
+void
+BankEditorDialog::slotDelete()
+{
+ if (!m_listView->currentItem())
+ return ;
+
+ QListViewItem* currentItem = m_listView->currentItem();
+
+ MidiBankListViewItem* bankItem = dynamic_cast<MidiBankListViewItem*>(currentItem);
+
+ MidiDevice *device = getMidiDevice(currentItem);
+
+ if (device && bankItem) {
+ int currentBank = bankItem->getBank();
+
+ int reply =
+ KMessageBox::warningYesNo(this, i18n("Really delete this bank?"));
+
+ if (reply == KMessageBox::Yes) {
+ MidiBank bank = m_bankList[currentBank];
+
+ // Copy across all programs that aren't in the doomed bank
+ //
+ ProgramList::iterator it;
+ ProgramList tempList;
+ for (it = m_programList.begin(); it != m_programList.end(); it++)
+ if (!(it->getBank() == bank))
+ tempList.push_back(*it);
+
+ // Erase the bank and repopulate
+ //
+ BankList::iterator er =
+ m_bankList.begin();
+ er += currentBank;
+ m_bankList.erase(er);
+ m_programList = tempList;
+ keepBankListForNextPopulate();
+
+ // the listview automatically selects a new current item
+ m_listView->blockSignals(true);
+ delete currentItem;
+ m_listView->blockSignals(false);
+
+ // Don't allow pasting from this defunct device
+ //
+ if (m_copyBank.first == bankItem->getDeviceId() &&
+ m_copyBank.second == bankItem->getBank()) {
+ m_pastePrograms->setEnabled(false);
+ m_copyBank = std::pair<DeviceId, int>
+ (Device::NO_DEVICE, -1);
+ }
+
+ slotApply();
+ selectDeviceItem(device);
+ }
+
+ return ;
+ }
+
+ MidiKeyMapListViewItem* keyItem = dynamic_cast<MidiKeyMapListViewItem*>(currentItem);
+
+ if (keyItem && device) {
+
+ int reply =
+ KMessageBox::warningYesNo(this, i18n("Really delete this key mapping?"));
+
+ if (reply == KMessageBox::Yes) {
+
+ std::string keyMappingName = qstrtostr(keyItem->getName());
+
+ ModifyDeviceCommand *command = new ModifyDeviceCommand
+ (m_studio,
+ device->getId(),
+ device->getName(),
+ device->getLibrarianName(),
+ device->getLibrarianEmail());
+
+ KeyMappingList kml = device->getKeyMappings();
+
+ for (KeyMappingList::iterator i = kml.begin();
+ i != kml.end(); ++i) {
+ if (i->getName() == keyMappingName) {
+ RG_DEBUG << "erasing " << keyMappingName << endl;
+ kml.erase(i);
+ break;
+ }
+ }
+
+ RG_DEBUG << " setting " << kml.size() << " key mappings to device " << endl;
+
+ command->setKeyMappingList(kml);
+ command->setOverwrite(true);
+
+ addCommandToHistory(command);
+
+ RG_DEBUG << " device has " << device->getKeyMappings().size() << " key mappings now " << endl;
+
+ updateDialog();
+ }
+
+ return ;
+ }
+}
+
+void
+BankEditorDialog::slotDeleteAll()
+{
+ if (!m_listView->currentItem())
+ return ;
+
+ QListViewItem* currentItem = m_listView->currentItem();
+ MidiDeviceListViewItem* deviceItem = getParentDeviceItem(currentItem);
+ MidiDevice *device = getMidiDevice(deviceItem);
+
+ QString question = i18n("Really delete all banks for ") +
+ strtoqstr(device->getName()) + QString(" ?");
+
+ int reply = KMessageBox::warningYesNo(this, question);
+
+ if (reply == KMessageBox::Yes) {
+
+ // erase all bank items
+ QListViewItem* child = 0;
+ while ((child = deviceItem->firstChild()))
+ delete child;
+
+ m_bankList.clear();
+ m_programList.clear();
+
+ // Don't allow pasting from this defunct device
+ //
+ if (m_copyBank.first == deviceItem->getDeviceId()) {
+ m_pastePrograms->setEnabled(false);
+ m_copyBank = std::pair<DeviceId, int>
+ (Device::NO_DEVICE, -1);
+ }
+
+ // Urgh, we have this horrible flag that we're using to frig this.
+ // (we might not need this anymore but I'm too scared to remove it
+ // now).
+ //
+ m_deleteAllReally = true;
+ slotApply();
+ m_deleteAllReally = false;
+
+ selectDeviceItem(device);
+
+ }
+}
+
+MidiDevice*
+BankEditorDialog::getMidiDevice(DeviceId id)
+{
+ Device *device = m_studio->getDevice(id);
+ MidiDevice *midiDevice =
+ dynamic_cast<MidiDevice *>(device);
+
+ /*
+ if (device) {
+ if (!midiDevice) {
+ std::cerr << "ERROR: BankEditorDialog::getMidiDevice: device "
+ << id << " is not a MIDI device" << std::endl;
+ }
+ } else {
+ std::cerr
+ << "ERROR: BankEditorDialog::getMidiDevice: no such device as "
+ << id << std::endl;
+ }
+ */
+
+ return midiDevice;
+}
+
+MidiDevice*
+BankEditorDialog::getMidiDevice(QListViewItem* item)
+{
+ MidiDeviceListViewItem* deviceItem =
+ dynamic_cast<MidiDeviceListViewItem*>(item);
+ if (!deviceItem)
+ return 0;
+
+ return getMidiDevice(deviceItem->getDeviceId());
+}
+
+std::pair<int, int>
+BankEditorDialog::getFirstFreeBank(QListViewItem* item)
+{
+ //!!! percussion? this is actually only called in the expectation
+ // that percussion==false at the moment
+
+ for (int msb = 0; msb < 128; ++msb) {
+ for (int lsb = 0; lsb < 128; ++lsb) {
+ BankList::iterator i = m_bankList.begin();
+ for ( ; i != m_bankList.end(); ++i) {
+ if (i->getLSB() == lsb && i->getMSB() == msb) {
+ break;
+ }
+ }
+ if (i == m_bankList.end())
+ return std::pair<int, int>(msb, lsb);
+ }
+ }
+
+ return std::pair<int, int>(0, 0);
+}
+
+void
+BankEditorDialog::slotModifyDeviceOrBankName(QListViewItem* item, const QString &label, int)
+{
+ RG_DEBUG << "BankEditorDialog::slotModifyDeviceOrBankName" << endl;
+
+ MidiDeviceListViewItem* deviceItem =
+ dynamic_cast<MidiDeviceListViewItem*>(item);
+ MidiBankListViewItem* bankItem =
+ dynamic_cast<MidiBankListViewItem*>(item);
+ MidiKeyMapListViewItem *keyItem =
+ dynamic_cast<MidiKeyMapListViewItem*>(item);
+
+ if (bankItem) {
+
+ // renaming a bank item
+
+ RG_DEBUG << "BankEditorDialog::slotModifyDeviceOrBankName - "
+ << "modify bank name to " << label << endl;
+
+ if (m_bankList[bankItem->getBank()].getName() != qstrtostr(label)) {
+ m_bankList[bankItem->getBank()].setName(qstrtostr(label));
+ setModified(true);
+ }
+
+ } else if (keyItem) {
+
+ RG_DEBUG << "BankEditorDialog::slotModifyDeviceOrBankName - "
+ << "modify key mapping name to " << label << endl;
+
+ QString oldName = keyItem->getName();
+
+ QListViewItem* currentItem = m_listView->currentItem();
+ MidiDevice *device = getMidiDevice(currentItem);
+
+ if (device) {
+
+ ModifyDeviceCommand *command = new ModifyDeviceCommand
+ (m_studio,
+ device->getId(),
+ device->getName(),
+ device->getLibrarianName(),
+ device->getLibrarianEmail());
+
+ KeyMappingList kml = device->getKeyMappings();
+
+ for (KeyMappingList::iterator i = kml.begin();
+ i != kml.end(); ++i) {
+ if (i->getName() == qstrtostr(oldName)) {
+ i->setName(qstrtostr(label));
+ break;
+ }
+ }
+
+ command->setKeyMappingList(kml);
+ command->setOverwrite(true);
+
+ addCommandToHistory(command);
+
+ updateDialog();
+ }
+
+ } else if (deviceItem) { // must be last, as the others are subclasses
+
+ // renaming a device item
+
+ RG_DEBUG << "BankEditorDialog::slotModifyDeviceOrBankName - "
+ << "modify device name to " << label << endl;
+
+ if (m_deviceNameMap[deviceItem->getDeviceId()] != qstrtostr(label)) {
+ m_deviceNameMap[deviceItem->getDeviceId()] = qstrtostr(label);
+ setModified(true);
+
+ m_updateDeviceList = true;
+ }
+
+ }
+
+}
+
+void
+BankEditorDialog::selectDeviceItem(MidiDevice *device)
+{
+ QListViewItem *child = m_listView->firstChild();
+ MidiDeviceListViewItem *midiDeviceItem;
+ MidiDevice *midiDevice;
+
+ do {
+ midiDeviceItem = dynamic_cast<MidiDeviceListViewItem*>(child);
+
+ if (midiDeviceItem) {
+ midiDevice = getMidiDevice(midiDeviceItem);
+
+ if (midiDevice == device) {
+ m_listView->setSelected(child, true);
+ return ;
+ }
+ }
+
+ } while ((child = child->nextSibling()));
+}
+
+void
+BankEditorDialog::selectDeviceBankItem(DeviceId deviceId,
+ int bank)
+{
+ QListViewItem *deviceChild = m_listView->firstChild();
+ QListViewItem *bankChild;
+ int deviceCount = 0, bankCount = 0;
+
+ do {
+ bankChild = deviceChild->firstChild();
+
+ MidiDeviceListViewItem *midiDeviceItem =
+ dynamic_cast<MidiDeviceListViewItem*>(deviceChild);
+
+ if (midiDeviceItem && bankChild) {
+ do {
+ if (deviceId == midiDeviceItem->getDeviceId() &
+ bank == bankCount) {
+ m_listView->setSelected(bankChild, true);
+ return ;
+ }
+ bankCount++;
+
+ } while ((bankChild = bankChild->nextSibling()));
+ }
+
+ deviceCount++;
+ bankCount = 0;
+ } while ((deviceChild = deviceChild->nextSibling()));
+}
+
+void
+BankEditorDialog::slotVariationToggled()
+{
+ setModified(true);
+ m_variationCombo->setEnabled(m_variationToggle->isChecked());
+}
+
+void
+BankEditorDialog::slotVariationChanged(int)
+{
+ setModified(true);
+}
+
+void
+BankEditorDialog::setModified(bool modified)
+{
+ RG_DEBUG << "BankEditorDialog::setModified("
+ << modified << ")" << endl;
+
+ if (modified) {
+
+ m_applyButton->setEnabled(true);
+ m_resetButton->setEnabled(true);
+ m_closeButton->setEnabled(false);
+ m_listView->setEnabled(false);
+
+ } else {
+
+ m_applyButton->setEnabled(false);
+ m_resetButton->setEnabled(false);
+ m_closeButton->setEnabled(true);
+ m_listView->setEnabled(true);
+
+ }
+
+ m_modified = modified;
+}
+
+void
+BankEditorDialog::addCommandToHistory(KCommand *command)
+{
+ getCommandHistory()->addCommand(command);
+ setModified(false);
+}
+
+MultiViewCommandHistory*
+BankEditorDialog::getCommandHistory()
+{
+ return m_doc->getCommandHistory();
+}
+
+void
+BankEditorDialog::slotImport()
+{
+ QString deviceDir = KGlobal::dirs()->findResource("appdata", "library/");
+ QDir dir(deviceDir);
+ if (!dir.exists()) {
+ deviceDir = ":ROSEGARDENDEVICE";
+ } else {
+ deviceDir = "file://" + deviceDir;
+ }
+
+ KURL url = KFileDialog::getOpenURL
+ (deviceDir,
+ "audio/x-rosegarden-device audio/x-rosegarden audio/x-soundfont",
+ this, i18n("Import Banks from Device in File"));
+
+ if (url.isEmpty())
+ return ;
+
+ ImportDeviceDialog *dialog = new ImportDeviceDialog(this, url);
+ if (dialog->doImport() && dialog->exec() == QDialog::Accepted) {
+
+ MidiDeviceListViewItem* deviceItem =
+ dynamic_cast<MidiDeviceListViewItem*>
+ (m_listView->selectedItem());
+
+ if (!deviceItem) {
+ KMessageBox::error(this, "Some internal error: cannot locate selected device");
+ return ;
+ }
+
+ ModifyDeviceCommand *command = 0;
+
+ BankList banks(dialog->getBanks());
+ ProgramList programs(dialog->getPrograms());
+ ControlList controls(dialog->getControllers());
+ KeyMappingList keyMappings(dialog->getKeyMappings());
+ MidiDevice::VariationType variation(dialog->getVariationType());
+ std::string librarianName(dialog->getLibrarianName());
+ std::string librarianEmail(dialog->getLibrarianEmail());
+
+ // don't record the librarian when
+ // merging banks -- it's misleading.
+ // (also don't use variation type)
+ if (!dialog->shouldOverwriteBanks()) {
+ librarianName = "";
+ librarianEmail = "";
+ }
+
+ command = new ModifyDeviceCommand(m_studio,
+ deviceItem->getDeviceId(),
+ dialog->getDeviceName(),
+ librarianName,
+ librarianEmail);
+
+ if (dialog->shouldOverwriteBanks()) {
+ command->setVariation(variation);
+ }
+ if (dialog->shouldImportBanks()) {
+ command->setBankList(banks);
+ command->setProgramList(programs);
+ }
+ if (dialog->shouldImportControllers()) {
+ command->setControlList(controls);
+ }
+ if (dialog->shouldImportKeyMappings()) {
+ command->setKeyMappingList(keyMappings);
+ }
+ command->setOverwrite(dialog->shouldOverwriteBanks());
+ command->setRename(dialog->shouldRename());
+
+ addCommandToHistory(command);
+
+ // No need to redraw the dialog, this is done by
+ // slotUpdate, signalled by the MultiViewCommandHistory
+ MidiDevice *device = getMidiDevice(deviceItem);
+ if (device)
+ selectDeviceItem(device);
+ }
+
+ delete dialog;
+ updateDialog();
+}
+
+void
+BankEditorDialog::slotEditCopy()
+{
+ MidiBankListViewItem* bankItem
+ = dynamic_cast<MidiBankListViewItem*>(m_listView->currentItem());
+
+ if (bankItem) {
+ m_copyBank = std::pair<DeviceId, int>(bankItem->getDeviceId(),
+ bankItem->getBank());
+ m_pastePrograms->setEnabled(true);
+ }
+}
+
+void
+BankEditorDialog::slotEditPaste()
+{
+ MidiBankListViewItem* bankItem
+ = dynamic_cast<MidiBankListViewItem*>(m_listView->currentItem());
+
+ if (bankItem) {
+ // Get the full program and bank list for the source device
+ //
+ MidiDevice *device = getMidiDevice(m_copyBank.first);
+ std::vector<MidiBank> tempBank = device->getBanks();
+
+ ProgramList::iterator it;
+ std::vector<MidiProgram> tempProg;
+
+ // Remove programs that will be overwritten
+ //
+ for (it = m_programList.begin(); it != m_programList.end(); it++) {
+ if (!(it->getBank() == m_lastBank))
+ tempProg.push_back(*it);
+ }
+ m_programList = tempProg;
+
+ // Now get source list and msb/lsb
+ //
+ tempProg = device->getPrograms();
+ MidiBank sourceBank = tempBank[m_copyBank.second];
+
+ // Add the new programs
+ //
+ for (it = tempProg.begin(); it != tempProg.end(); it++) {
+ if (it->getBank() == sourceBank) {
+ // Insert with new MSB and LSB
+ //
+ MidiProgram copyProgram(m_lastBank,
+ it->getProgram(),
+ it->getName());
+
+ m_programList.push_back(copyProgram);
+ }
+ }
+
+ // Save these for post-apply
+ //
+ DeviceId devPos = bankItem->getDeviceId();
+ int bankPos = bankItem->getBank();
+
+ slotApply();
+
+ // Select same bank
+ //
+ selectDeviceBankItem(devPos, bankPos);
+ }
+}
+
+void
+BankEditorDialog::slotExport()
+{
+ QString extension = "rgd";
+
+ QString name =
+ KFileDialog::getSaveFileName(":ROSEGARDEN",
+ (extension.isEmpty() ? QString("*") : ("*." + extension)),
+ this,
+ i18n("Export Device as..."));
+
+ // Check for the existence of the name
+ if (name.isEmpty())
+ return ;
+
+ // Append extension if we don't have one
+ //
+ if (!extension.isEmpty()) {
+ if (!name.endsWith("." + extension)) {
+ name += "." + extension;
+ }
+ }
+
+ 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 ;
+
+ }
+
+ MidiDeviceListViewItem* deviceItem =
+ dynamic_cast<MidiDeviceListViewItem*>
+ (m_listView->selectedItem());
+
+ std::vector<DeviceId> devices;
+ MidiDevice *md = getMidiDevice(deviceItem);
+
+ if (md) {
+ ExportDeviceDialog *ed = new ExportDeviceDialog
+ (this, strtoqstr(md->getName()));
+ if (ed->exec() != QDialog::Accepted)
+ return ;
+ if (ed->getExportType() == ExportDeviceDialog::ExportOne) {
+ devices.push_back(md->getId());
+ }
+ }
+
+ m_doc->exportStudio(name, devices);
+}
+
+void
+BankEditorDialog::slotFileClose()
+{
+ RG_DEBUG << "BankEditorDialog::slotFileClose()\n";
+
+ // We need to do this because we might be here due to a
+ // documentAboutToChange signal, in which case the document won't
+ // be valid by the time we reach the dtor, since it will be
+ // triggered when the closeEvent is actually processed.
+ //
+ m_doc->getCommandHistory()->detachView(actionCollection());
+ m_doc = 0;
+ close();
+}
+
+void
+BankEditorDialog::closeEvent(QCloseEvent *e)
+{
+ if (m_modified) {
+
+ int res = KMessageBox::warningYesNoCancel(this,
+ i18n("There are unsaved changes.\n"
+ "Do you want to apply the changes before exiting "
+ "the Bank Editor or discard the changes ?"),
+ i18n("Unsaved Changes"),
+ i18n("&Apply"),
+ i18n("&Discard"));
+ if (res == KMessageBox::Yes) {
+
+ slotApply();
+
+ } else if (res == KMessageBox::Cancel)
+ return ;
+ }
+
+ emit closing();
+ KMainWindow::closeEvent(e);
+}
+
+}
+#include "BankEditorDialog.moc"
diff --git a/src/gui/studio/BankEditorDialog.h b/src/gui/studio/BankEditorDialog.h
new file mode 100644
index 0000000..0e49430
--- /dev/null
+++ b/src/gui/studio/BankEditorDialog.h
@@ -0,0 +1,211 @@
+
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
+
+/*
+ Rosegarden
+ A MIDI and audio sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_BANKEDITORDIALOG_H_
+#define _RG_BANKEDITORDIALOG_H_
+
+#include "base/Device.h"
+#include "base/MidiProgram.h"
+#include <map>
+#include <string>
+#include <kmainwindow.h>
+#include <utility>
+
+
+class QWidget;
+class QString;
+class QPushButton;
+class QListViewItem;
+class QGroupBox;
+class QCloseEvent;
+class QCheckBox;
+class KListView;
+class KCommand;
+class KComboBox;
+
+
+namespace Rosegarden
+{
+
+class Studio;
+class RosegardenGUIDoc;
+class MultiViewCommandHistory;
+class MidiProgramsEditor;
+class MidiKeyMappingEditor;
+class MidiDeviceListViewItem;
+class MidiDevice;
+
+
+class BankEditorDialog : public KMainWindow
+{
+ Q_OBJECT
+
+public:
+ BankEditorDialog(QWidget *parent,
+ RosegardenGUIDoc *doc,
+ DeviceId defaultDevice =
+ Device::NO_DEVICE);
+
+ ~BankEditorDialog();
+
+ // Initialise the devices/banks and programs - the whole lot
+ //
+ void initDialog();
+
+ std::pair<int, int> getFirstFreeBank(QListViewItem*);
+
+ void addCommandToHistory(KCommand *command);
+ MultiViewCommandHistory* getCommandHistory();
+
+ void setCurrentDevice(DeviceId device);
+
+ // Get a MidiDevice from an index number
+ //
+ MidiDevice* getMidiDevice(DeviceId);
+ MidiDevice* getMidiDevice(QListViewItem*);
+ MidiDevice* getCurrentMidiDevice();
+ BankList& getBankList() { return m_bankList; }
+ ProgramList&getProgramList() { return m_programList; }
+
+ Studio *getStudio() { return m_studio; }
+
+ void setModified(bool value);
+
+ void checkModified();
+
+ // Set the listview to select a certain device - used after adding
+ // or deleting banks.
+ //
+ void selectDeviceItem(MidiDevice *device);
+
+ // Select a device/bank combination
+ //
+ void selectDeviceBankItem(DeviceId device, int bank);
+
+public slots:
+ void slotPopulateDevice(QListViewItem*);
+
+ void slotApply();
+ void slotReset();
+
+ void slotUpdate();
+
+ void slotAddBank();
+ void slotAddKeyMapping();
+ void slotDelete();
+ void slotDeleteAll();
+
+ void slotImport();
+ void slotExport();
+
+ void slotModifyDeviceOrBankName(QListViewItem*, const QString&,int);
+
+ void slotFileClose();
+
+ void slotEditCopy();
+ void slotEditPaste();
+
+ void slotVariationToggled();
+ void slotVariationChanged(int);
+
+signals:
+ void closing();
+ void deviceNamesChanged();
+
+protected:
+ virtual void closeEvent(QCloseEvent*);
+
+ void resetProgramList();
+ void setProgramList(MidiDevice *device);
+
+ void updateDialog();
+
+ void populateDeviceItem(QListViewItem* deviceItem,
+ MidiDevice* midiDevice);
+
+ void updateDeviceItem(MidiDeviceListViewItem* deviceItem);
+
+ bool deviceItemHasBank(MidiDeviceListViewItem* deviceItem, int bankNb);
+
+ void clearItemChildren(QListViewItem* deviceItem);
+
+ MidiDeviceListViewItem* getParentDeviceItem(QListViewItem*);
+ void keepBankListForNextPopulate() { m_keepBankList = true; }
+
+ void populateDevice(QListViewItem*);
+
+ void setupActions();
+
+ //--------------- Data members ---------------------------------
+ Studio *m_studio;
+ RosegardenGUIDoc *m_doc;
+
+ MidiProgramsEditor *m_programEditor;
+ MidiKeyMappingEditor *m_keyMappingEditor;
+ KListView *m_listView;
+
+ QGroupBox *m_optionBox;
+ QCheckBox *m_variationToggle;
+ KComboBox *m_variationCombo;
+
+ QPushButton *m_closeButton;
+ QPushButton *m_resetButton;
+ QPushButton *m_applyButton;
+
+ QPushButton *m_addBank;
+ QPushButton *m_addKeyMapping;
+ QPushButton *m_delete;
+ QPushButton *m_deleteAll;
+
+ QPushButton *m_importBanks;
+ QPushButton *m_exportBanks;
+
+ QPushButton *m_copyPrograms;
+ QPushButton *m_pastePrograms;
+ std::pair<DeviceId, int> m_copyBank;
+
+ std::map<DeviceId, std::string> m_deviceNameMap;
+ BankList m_bankList;
+ ProgramList m_programList;
+ ProgramList m_oldProgramList;
+
+ bool m_modified;
+ bool m_keepBankList;
+ bool m_deleteAllReally;
+
+ DeviceId m_lastDevice;
+ MidiBank m_lastBank;
+
+ bool m_updateDeviceList;
+};
+
+// ----------------------- RemapInstrumentDialog ------------------------
+//
+//
+
+
+}
+
+#endif
diff --git a/src/gui/studio/ChangeRecordDeviceCommand.cpp b/src/gui/studio/ChangeRecordDeviceCommand.cpp
new file mode 100644
index 0000000..a5a3947
--- /dev/null
+++ b/src/gui/studio/ChangeRecordDeviceCommand.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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#include "ChangeRecordDeviceCommand.h"
+#include "StudioControl.h"
+#include "document/ConfigGroups.h"
+#include "sound/MappedEvent.h"
+#include <kapplication.h>
+#include <kconfig.h>
+
+namespace Rosegarden
+{
+
+void
+ChangeRecordDeviceCommand::swap()
+ {
+
+ KConfig *config = kapp->config();
+ config->setGroup(Rosegarden::SequencerOptionsConfigGroup);
+ QStringList devList = config->readListEntry("midirecorddevice");
+ QString sdevice = QString::number(m_deviceId);
+ if (m_action)
+ {
+ if(!devList.contains(sdevice))
+ devList.append(sdevice);
+ }
+ else
+ {
+ if(devList.contains(sdevice))
+ devList.remove(sdevice);
+ }
+ config->writeEntry("midirecorddevice", devList);
+
+ // send the selected device to the sequencer
+ Rosegarden::MappedEvent mEdevice
+ (Rosegarden::MidiInstrumentBase,
+ Rosegarden::MappedEvent::SystemRecordDevice,
+ Rosegarden::MidiByte(m_deviceId),
+ Rosegarden::MidiByte(m_action));
+ Rosegarden::StudioControl::sendMappedEvent(mEdevice);
+
+ m_action = !m_action;
+}
+
+}
diff --git a/src/gui/studio/ChangeRecordDeviceCommand.h b/src/gui/studio/ChangeRecordDeviceCommand.h
new file mode 100644
index 0000000..70819db
--- /dev/null
+++ b/src/gui/studio/ChangeRecordDeviceCommand.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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_CHANGERECORDDEVICECOMMAND_H_
+#define _RG_CHANGERECORDDEVICECOMMAND_H_
+
+#include "base/Studio.h"
+#include <klocale.h>
+#include <kcommand.h>
+
+namespace Rosegarden
+{
+
+class ChangeRecordDeviceCommand : public KNamedCommand
+{
+public:
+ ChangeRecordDeviceCommand(Rosegarden::DeviceId deviceId, bool action) :
+ KNamedCommand(i18n("Change Record Device")),
+ m_deviceId(deviceId), m_action(action) { }
+
+ virtual void execute() { swap(); }
+ virtual void unexecute() { swap(); }
+
+private:
+ Rosegarden::DeviceId m_deviceId;
+ bool m_action;
+ void swap();
+
+};
+
+}
+
+#endif /*CHANGERECORDDEVICECOMMAND_H_*/
diff --git a/src/gui/studio/DeviceEditorDialog.cpp b/src/gui/studio/DeviceEditorDialog.cpp
new file mode 100644
index 0000000..29c9dd4
--- /dev/null
+++ b/src/gui/studio/DeviceEditorDialog.cpp
@@ -0,0 +1,406 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
+
+/*
+ Rosegarden
+ A MIDI and audio sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "DeviceEditorDialog.h"
+
+#include <klocale.h>
+#include "misc/Debug.h"
+#include "misc/Strings.h"
+#include "base/Device.h"
+#include "base/MidiDevice.h"
+#include "base/Studio.h"
+#include "commands/studio/CreateOrDeleteDeviceCommand.h"
+#include "commands/studio/ReconnectDeviceCommand.h"
+#include "commands/studio/RenameDeviceCommand.h"
+#include "document/RosegardenGUIDoc.h"
+#include "document/MultiViewCommandHistory.h"
+#include "gui/application/RosegardenApplication.h"
+#include <kdialogbase.h>
+#include <kmessagebox.h>
+#include <qcstring.h>
+#include <qdatastream.h>
+#include <qhbox.h>
+#include <qpushbutton.h>
+#include <qregexp.h>
+#include <qstring.h>
+#include <qstringlist.h>
+#include <qtable.h>
+#include <qvbox.h>
+#include <qwidget.h>
+#include <algorithm>
+
+
+namespace Rosegarden
+{
+
+DeviceEditorDialog::DeviceEditorDialog(QWidget *parent,
+ RosegardenGUIDoc *document) :
+ KDialogBase(parent, "deviceeditordialog", true,
+ i18n("Manage MIDI Devices"), Ok | Apply | Close, Ok, true),
+ m_document(document),
+ m_studio(&document->getStudio()),
+ m_modified(false)
+{
+ QVBox *mainBox = makeVBoxMainWidget();
+
+ m_table = new QTable(0, 4, mainBox);
+ m_table->setSorting(false);
+ m_table->setRowMovingEnabled(false);
+ m_table->setColumnMovingEnabled(false);
+ m_table->setShowGrid(false);
+ m_table->horizontalHeader()->setLabel(0, i18n("Device"));
+ m_table->horizontalHeader()->setLabel(1, i18n("Name"));
+ m_table->horizontalHeader()->setLabel(2, i18n("Type"));
+ m_table->horizontalHeader()->setLabel(3, i18n("Connection"));
+ m_table->horizontalHeader()->show();
+ m_table->verticalHeader()->hide();
+ m_table->setLeftMargin(0);
+ m_table->setSelectionMode(QTable::SingleRow);
+ m_table->setColumnReadOnly(0, true);
+ m_table->setColumnReadOnly(2, true);
+
+ makeConnectionList((unsigned int)MidiDevice::Play,
+ m_playConnections);
+ makeConnectionList((unsigned int)MidiDevice::Record,
+ m_recordConnections);
+
+ populate();
+
+ QHBox *hbox = new QHBox(mainBox);
+ QPushButton *addButton = new QPushButton(i18n("Add Play Device"), hbox);
+ QPushButton *addRButton = new QPushButton(i18n("Add Record Device"), hbox);
+ QPushButton *deleteButton = new QPushButton(i18n("Delete Device"), hbox);
+ connect(addButton, SIGNAL(clicked()), this, SLOT(slotAddPlayDevice()));
+ connect(addRButton, SIGNAL(clicked()), this, SLOT(slotAddRecordDevice()));
+ connect(deleteButton, SIGNAL(clicked()), this, SLOT(slotDeleteDevice()));
+ connect(m_table, SIGNAL(valueChanged(int, int)),
+ this, SLOT(slotValueChanged (int, int)));
+
+ setMinimumHeight(250);
+
+ enableButtonOK(false);
+ enableButtonApply(false);
+}
+
+DeviceEditorDialog::~DeviceEditorDialog()
+{
+ // nothing -- don't need to clear device list (the devices were
+ // just aliases for those in the studio)
+}
+
+void
+DeviceEditorDialog::populate()
+{
+ DeviceList *devices = m_studio->getDevices();
+ DeviceListIterator it;
+ m_devices.clear();
+
+ for (it = devices->begin(); it != devices->end(); ++it) {
+ if ((*it)->getType() == Device::Midi) {
+ MidiDevice *md =
+ dynamic_cast<MidiDevice *>(*it);
+ if (md)
+ m_devices.push_back(md);
+ }
+ }
+
+ while (m_table->numRows() > 0) {
+ m_table->removeRow(m_table->numRows() - 1);
+ }
+
+ int deviceCount = 0;
+
+#define NAME_COL 0
+#define LABEL_COL 1
+#define DIRECTION_COL 2
+#define CONNECTION_COL 3
+
+ for (it = m_devices.begin(); it != m_devices.end(); ++it) {
+
+ m_table->insertRows(deviceCount, 1);
+
+ // we know we only put MidiDevices in m_devices
+ MidiDevice *md = static_cast<MidiDevice *>(*it);
+
+ // if you change this string ("Device %1"), change test in slotApply
+ QString deviceName = i18n("Device %1").arg(md->getId() + 1);
+ QString deviceLabel = strtoqstr(md->getName());
+ QString connectionName = strtoqstr(md->getConnection());
+
+ m_table->setText(deviceCount, NAME_COL, deviceName);
+ m_table->setText(deviceCount, LABEL_COL, deviceLabel);
+ m_table->setText(deviceCount, DIRECTION_COL,
+ (md->getDirection() == MidiDevice::Play ?
+ i18n("Play") : i18n("Record")));
+
+ QStringList &list(md->getDirection() == MidiDevice::Play ?
+ m_playConnections : m_recordConnections);
+ int currentConnectionIndex = list.size() - 1;
+ for (unsigned int i = 0; i < list.size(); ++i) {
+ if (list[i] == connectionName)
+ currentConnectionIndex = i;
+ }
+
+ QComboTableItem *item = new QComboTableItem(m_table, list, false);
+ item->setCurrentItem(currentConnectionIndex);
+ m_table->setItem(deviceCount, CONNECTION_COL, item);
+
+ m_table->adjustRow(deviceCount);
+ ++deviceCount;
+ }
+
+ int minColumnWidths[] = { 80, 120, 100, 250 };
+ for (int i = 0; i < 4; ++i) {
+ m_table->adjustColumn(i);
+ if (m_table->columnWidth(i) < minColumnWidths[i])
+ m_table->setColumnWidth(i, minColumnWidths[i]);
+ }
+}
+
+void
+DeviceEditorDialog::makeConnectionList(unsigned int direction,
+ QStringList &list)
+{
+ QByteArray data;
+ QByteArray replyData;
+ QCString replyType;
+ QDataStream arg(data, IO_WriteOnly);
+ arg << (int)Device::Midi;
+ arg << direction;
+
+ if (!rgapp->sequencerCall("getConnections(int, unsigned int)", replyType, replyData, data)) {
+ RG_DEBUG << "DeviceEditorDialog: can't call Sequencer" << endl;
+ list.append(i18n("No connection"));
+ return ;
+ }
+
+ QDataStream reply(replyData, IO_ReadOnly);
+ unsigned int connections = 0;
+ if (replyType == "unsigned int")
+ reply >> connections;
+
+ for (unsigned int i = 0; i < connections; ++i) {
+
+ QByteArray data;
+ QByteArray replyData;
+ QCString replyType;
+ QDataStream arg(data, IO_WriteOnly);
+ arg << (int)Device::Midi;
+ arg << direction;
+ arg << i;
+
+
+ if (!rgapp->sequencerCall("getConnection(int, unsigned int, unsigned int)",
+ replyType, replyData, data)) {
+ RG_DEBUG << "DeviceEditorDialog: can't call Sequencer" << endl;
+ list.append(i18n("No connection"));
+ return ;
+ }
+
+ QDataStream reply(replyData, IO_ReadOnly);
+ QString connection;
+ if (replyType == "QString") {
+ reply >> connection;
+ list.append(connection);
+ }
+ }
+
+ list.append(i18n("No connection"));
+}
+
+void
+DeviceEditorDialog::setModified(bool m)
+{
+ if (m_modified == m)
+ return ;
+ enableButtonOK(m);
+ enableButtonApply(m);
+ m_modified = m;
+}
+
+void
+DeviceEditorDialog::slotOk()
+{
+ slotApply();
+ accept();
+}
+
+void
+DeviceEditorDialog::slotClose()
+{
+ if (m_modified) {
+
+ int reply = KMessageBox::questionYesNo(this,
+ i18n("Apply pending changes?"));
+
+ if (reply == KMessageBox::Yes)
+ slotApply();
+ }
+
+ reject();
+}
+
+void
+DeviceEditorDialog::slotApply()
+{
+ KMacroCommand *command = new KMacroCommand("Edit Devices");
+
+ // first delete deleted devices, in reverse order of id (so that
+ // if we undo this command we'll get the original ids back... probably)
+
+ std::vector<DeviceId> ids;
+
+ for (DeviceListIterator i = m_devices.begin();
+ i != m_devices.end(); ++i) {
+ if (m_deletedDevices.find((*i)->getId()) != m_deletedDevices.end()) {
+ ids.push_back((*i)->getId());
+ }
+ }
+
+ std::sort(ids.begin(), ids.end());
+
+ for (int i = ids.size() - 1; i >= 0; --i) {
+ command->addCommand(new CreateOrDeleteDeviceCommand(m_studio, ids[i]));
+ }
+
+ // create the new devices, and rename and/or set connections for
+ // any others that have changed
+
+ for (int i = 0; i < m_table->numRows(); ++i) {
+ int deviceId = getDeviceIdAt(i);
+ if (deviceId < 0) { // new device
+ command->addCommand(new CreateOrDeleteDeviceCommand
+ (m_studio,
+ qstrtostr(m_table->text(i, LABEL_COL)),
+ Device::Midi,
+ m_table->text(i, DIRECTION_COL) == "Play" ?
+ MidiDevice::Play :
+ MidiDevice::Record,
+ qstrtostr(m_table->text(i, CONNECTION_COL))));
+ } else { // existing device
+ Device *device = m_studio->getDevice(deviceId);
+ if (!device) {
+ /*
+ std::cerr <<
+ "WARNING: DeviceEditorDialog::slotApply(): device at row "
+ << i << " (id " << deviceId
+ << ") claims not to be new, but isn't in the studio"
+ << std::endl;
+ */
+ } else {
+ std::string name = qstrtostr(m_table->text(i, LABEL_COL));
+ std::string conn = qstrtostr(m_table->text(i, CONNECTION_COL));
+ if (device->getName() != name) {
+ command->addCommand(new RenameDeviceCommand
+ (m_studio, deviceId, name));
+ }
+ if (device->getConnection() != conn) {
+ command->addCommand(new ReconnectDeviceCommand
+ (m_studio, deviceId, conn));
+ }
+ }
+ }
+ }
+
+ m_document->getCommandHistory()->addCommand(command);
+
+ m_deletedDevices.clear();
+
+ populate();
+ setModified(false);
+}
+
+int
+DeviceEditorDialog::getDeviceIdAt(int row) // -1 for new device w/o an id yet
+{
+ QString t(m_table->text(row, 0));
+
+ QRegExp re("^.*(\\d+).*$");
+ re.search(t);
+
+ QString number = re.cap(1);
+ int id = -1;
+
+ if (number && number != "")
+ {
+ id = number.toInt() - 1; // displayed device numbers are 1-based
+ }
+
+ return id;
+}
+
+void
+DeviceEditorDialog::slotAddPlayDevice()
+{
+ int n = m_table->numRows();
+ m_table->insertRows(n, 1);
+ m_table->setText(n, 0, i18n("<new device>"));
+ m_table->setText(n, 1, i18n("New Device"));
+ m_table->setText(n, 2, i18n("Play"));
+
+ QComboTableItem *item =
+ new QComboTableItem(m_table, m_playConnections, false);
+ item->setCurrentItem(m_playConnections.size() - 1);
+ m_table->setItem(n, 3, item);
+ m_table->adjustRow(n);
+
+ setModified(true);
+}
+
+void
+DeviceEditorDialog::slotAddRecordDevice()
+{
+ int n = m_table->numRows();
+ m_table->insertRows(n, 1);
+ m_table->setText(n, 0, i18n("<new device>"));
+ m_table->setText(n, 1, i18n("New Device"));
+ m_table->setText(n, 2, i18n("Record"));
+
+ QComboTableItem *item =
+ new QComboTableItem(m_table, m_recordConnections, false);
+ item->setCurrentItem(m_recordConnections.size() - 1);
+ m_table->setItem(n, 3, item);
+ m_table->adjustRow(n);
+
+ setModified(true);
+}
+
+void
+DeviceEditorDialog::slotDeleteDevice()
+{
+ int n = m_table->currentRow();
+ m_deletedDevices.insert(getDeviceIdAt(n));
+ m_table->removeRow(n);
+ setModified(true);
+}
+
+void
+DeviceEditorDialog::slotValueChanged(int, int)
+{
+ setModified(true);
+}
+
+}
+#include "DeviceEditorDialog.moc"
diff --git a/src/gui/studio/DeviceEditorDialog.h b/src/gui/studio/DeviceEditorDialog.h
new file mode 100644
index 0000000..2bde025
--- /dev/null
+++ b/src/gui/studio/DeviceEditorDialog.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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_DEVICEEDITORDIALOG_H_
+#define _RG_DEVICEEDITORDIALOG_H_
+
+#include <base/Studio.h>
+#include <kdialogbase.h>
+#include <qstringlist.h>
+#include <set>
+
+
+class QWidget;
+class QTable;
+
+
+namespace Rosegarden
+{
+
+class Studio;
+class RosegardenGUIDoc;
+
+
+class DeviceEditorDialog : public KDialogBase
+{
+ Q_OBJECT
+public:
+ DeviceEditorDialog(QWidget *parent, RosegardenGUIDoc *document);
+ ~DeviceEditorDialog();
+
+ void setModified(bool value);
+
+protected slots:
+ void slotOk();
+ void slotApply();
+ void slotClose();
+
+ void slotAddPlayDevice();
+ void slotAddRecordDevice();
+ void slotDeleteDevice();
+ void slotValueChanged(int row, int col);
+
+private:
+ RosegardenGUIDoc *m_document;
+ Studio *m_studio;
+
+ QStringList m_playConnections;
+ QStringList m_recordConnections;
+ void makeConnectionList(unsigned int direction, QStringList &list);
+
+ QTable *m_table;
+
+ DeviceList m_devices;
+ std::set<DeviceId> m_deletedDevices;
+
+ void populate();
+ int getDeviceIdAt(int row); // -1 for new device without an id yet
+
+ bool m_modified;
+};
+
+
+}
+
+#endif
diff --git a/src/gui/studio/DeviceManagerDialog.cpp b/src/gui/studio/DeviceManagerDialog.cpp
new file mode 100644
index 0000000..8f2fa6b
--- /dev/null
+++ b/src/gui/studio/DeviceManagerDialog.cpp
@@ -0,0 +1,833 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
+
+/*
+ Rosegarden
+ A MIDI and audio sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "DeviceManagerDialog.h"
+
+#include "ChangeRecordDeviceCommand.h"
+#include "misc/Debug.h"
+#include "misc/Strings.h"
+#include "base/Device.h"
+#include "base/Event.h"
+#include "base/Instrument.h"
+#include "base/MidiDevice.h"
+#include "base/MidiTypes.h"
+#include "base/Studio.h"
+#include "commands/studio/CreateOrDeleteDeviceCommand.h"
+#include "commands/studio/ModifyDeviceCommand.h"
+#include "commands/studio/ReconnectDeviceCommand.h"
+#include "commands/studio/RenameDeviceCommand.h"
+#include "document/MultiViewCommandHistory.h"
+#include "document/RosegardenGUIDoc.h"
+#include "document/ConfigGroups.h"
+#include "gui/application/RosegardenApplication.h"
+#include "gui/dialogs/ExportDeviceDialog.h"
+#include "gui/dialogs/ImportDeviceDialog.h"
+#include <kapplication.h>
+#include <klocale.h>
+#include <kstddirs.h>
+#include <kaction.h>
+#include <kfiledialog.h>
+#include <kglobal.h>
+#include <kmainwindow.h>
+#include <kmessagebox.h>
+#include <kstdaccel.h>
+#include <kstdaction.h>
+#include <qcstring.h>
+#include <qdatastream.h>
+#include <qdialog.h>
+#include <qdir.h>
+#include <qfileinfo.h>
+#include <qframe.h>
+#include <qgrid.h>
+#include <qgroupbox.h>
+#include <qlayout.h>
+#include <qpushbutton.h>
+#include <qsizepolicy.h>
+#include <qstring.h>
+#include <qstringlist.h>
+#include <qtable.h>
+#include <qtooltip.h>
+#include <qwidget.h>
+
+
+namespace Rosegarden
+{
+
+static const int PLAY_NAME_COL = 0;
+static const int PLAY_CONNECTION_COL = 1;
+
+static const int RECORD_NAME_COL = 0;
+static const int RECORD_CURRENT_COL = 1;
+static const int RECORD_CONNECTION_COL = 2;
+
+
+DeviceManagerDialog::DeviceManagerDialog(QWidget *parent,
+ RosegardenGUIDoc *document) :
+ KMainWindow(parent, "deviceeditordialog"),
+ m_document(document),
+ m_studio(&document->getStudio())
+ {
+ QFrame * mainBox = new QFrame(this);
+ setCentralWidget(mainBox);
+ QVBoxLayout *mainLayout = new QVBoxLayout(mainBox, 10, 10);
+
+ setCaption(i18n("Manage MIDI Devices"));
+
+ QGroupBox *groupBox = new QGroupBox(2, Horizontal, i18n("Play devices"), mainBox);
+
+ m_playTable = new QTable(0, 2, groupBox);
+ m_playTable->setSorting(false);
+ m_playTable->setRowMovingEnabled(false);
+ m_playTable->setColumnMovingEnabled(false);
+ m_playTable->setShowGrid(false);
+ m_playTable->horizontalHeader()->setLabel(PLAY_NAME_COL, i18n("Device"));
+ m_playTable->horizontalHeader()->setLabel(PLAY_CONNECTION_COL, i18n("Connection"));
+ m_playTable->horizontalHeader()->show();
+ m_playTable->verticalHeader()->hide();
+ m_playTable->setLeftMargin(0);
+ m_playTable->setSelectionMode(QTable::SingleRow);
+
+ QFrame *frame = new QFrame(groupBox);
+ QVBoxLayout *vlayout = new QVBoxLayout(frame);
+ QGrid *buttons = new QGrid(2, Horizontal, frame);
+ QPushButton *addButton = new QPushButton(i18n("New"), buttons);
+ m_deletePlayButton = new QPushButton(i18n("Delete"), buttons);
+ m_importButton = new QPushButton(i18n("Import..."), buttons);
+ m_exportButton = new QPushButton(i18n("Export..."), buttons);
+ m_banksButton = new QPushButton(i18n("Banks..."), buttons);
+ m_controllersButton = new QPushButton(i18n("Control Events..."), buttons);
+ vlayout->addWidget(buttons);
+ vlayout->addStretch(10);
+
+ QToolTip::add
+ (addButton,
+ i18n("Create a new Play device"));
+ QToolTip::add
+ (m_deletePlayButton,
+ i18n("Delete the selected device"));
+ QToolTip::add
+ (m_importButton,
+ i18n("Import Bank, Program and Controller data from a Rosegarden file to the selected device"));
+ QToolTip::add
+ (m_exportButton,
+ i18n("Export Bank and Controller data to a Rosegarden interchange file"));
+ QToolTip::add
+ (m_banksButton,
+ i18n("View and edit Banks and Programs for the selected device"));
+ QToolTip::add
+ (m_controllersButton,
+ i18n("View and edit Control Events for the selected device - these are special Event types that you can define against your device and control through Control Rulers or the Instrument Parameter Box "));
+
+ connect(addButton, SIGNAL(clicked()), this, SLOT(slotAddPlayDevice()));
+ connect(m_deletePlayButton, SIGNAL(clicked()), this, SLOT(slotDeletePlayDevice()));
+ connect(m_importButton, SIGNAL(clicked()), this, SLOT(slotImport()));
+ connect(m_exportButton, SIGNAL(clicked()), this, SLOT(slotExport()));
+ connect(m_banksButton, SIGNAL(clicked()), this, SLOT(slotSetBanks()));
+ connect(m_controllersButton, SIGNAL(clicked()), this, SLOT(slotSetControllers()));
+
+ connect(m_playTable, SIGNAL(valueChanged(int, int)),
+ this, SLOT(slotPlayValueChanged (int, int)));
+ connect(m_playTable, SIGNAL(currentChanged(int, int)),
+ this, SLOT(slotPlayDeviceSelected (int, int)));
+
+ mainLayout->addWidget(groupBox);
+ groupBox = new QGroupBox(2, Horizontal, i18n("Record devices"), mainBox);
+
+ m_recordTable = new QTable(0, 3, groupBox);
+ m_recordTable->setSorting(false);
+ m_recordTable->setRowMovingEnabled(false);
+ m_recordTable->setColumnMovingEnabled(false);
+ m_recordTable->setShowGrid(false);
+ m_recordTable->horizontalHeader()->setLabel(RECORD_NAME_COL, i18n("Device"));
+ m_recordTable->horizontalHeader()->setLabel(RECORD_CURRENT_COL, i18n("Current"));
+ m_recordTable->horizontalHeader()->setLabel(RECORD_CONNECTION_COL, i18n("Connection"));
+ m_recordTable->horizontalHeader()->show();
+ m_recordTable->verticalHeader()->hide();
+ m_recordTable->setLeftMargin(0);
+ m_recordTable->setSelectionMode(QTable::SingleRow);
+
+ frame = new QFrame(groupBox);
+ vlayout = new QVBoxLayout(frame);
+ buttons = new QGrid(2, Horizontal, frame);
+ addButton = new QPushButton(i18n("New"), buttons);
+ m_deleteRecordButton = new QPushButton(i18n("Delete"), buttons);
+ vlayout->addWidget(buttons);
+ vlayout->addStretch(10);
+
+ QToolTip::add
+ (addButton,
+ i18n("Create a new Record device"));
+ QToolTip::add
+ (m_deleteRecordButton,
+ i18n("Delete the selected device"));
+
+ connect(addButton, SIGNAL(clicked()), this, SLOT(slotAddRecordDevice()));
+ connect(m_deleteRecordButton, SIGNAL(clicked()), this, SLOT(slotDeleteRecordDevice()));
+
+ connect(m_recordTable, SIGNAL(currentChanged(int, int)),
+ this, SLOT(slotRecordDeviceSelected (int, int)));
+ connect(m_recordTable, SIGNAL(valueChanged(int, int)),
+ this, SLOT(slotRecordValueChanged (int, int)));
+
+ connect(document, SIGNAL(devicesResyncd()), this, SLOT(slotDevicesResyncd()));
+
+ m_noConnectionString = i18n("No connection");
+
+ slotDevicesResyncd();
+
+ setMinimumHeight(400);
+ setMinimumWidth(600);
+
+ mainLayout->addWidget(groupBox);
+
+ QFrame* btnBox = new QFrame(mainBox);
+
+ btnBox->setSizePolicy(
+ QSizePolicy(QSizePolicy::Minimum, QSizePolicy::Fixed));
+
+ QPushButton *closeButton = new QPushButton(i18n("Close"), btnBox);
+
+ QHBoxLayout* layout = new QHBoxLayout(btnBox, 0, 10);
+ layout->addStretch(10);
+ layout->addWidget(closeButton);
+ layout->addSpacing(5);
+
+ KAction* close = KStdAction::close(this,
+ SLOT(slotClose()),
+ actionCollection());
+
+ closeButton->setText(close->text());
+ connect(closeButton, SIGNAL(clicked()), this, SLOT(slotClose()));
+
+ mainLayout->addWidget(btnBox);
+
+ // some adjustments
+ 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));
+
+ createGUI("devicemanager.rc");
+
+ m_document->getCommandHistory()->attachView(actionCollection());
+ connect(m_document->getCommandHistory(), SIGNAL(commandExecuted()),
+ this, SLOT(populate()));
+
+ m_playTable->setCurrentCell( -1, 0);
+ m_recordTable->setCurrentCell( -1, 0);
+
+ setAutoSaveSettings(DeviceManagerConfigGroup, true);
+
+ // enableButtonOK(false);
+ // enableButtonApply(false);
+
+ }
+
+ DeviceManagerDialog::~DeviceManagerDialog()
+ {
+ if (m_document) {
+ m_document->getCommandHistory()->detachView(actionCollection());
+ m_document = 0;
+ }
+
+ RG_DEBUG << "\n*** DeviceManagerDialog::~DeviceManagerDialog\n" << endl;
+ }
+
+ void
+ DeviceManagerDialog::slotClose()
+ {
+ if (m_document) {
+ m_document->getCommandHistory()->detachView(actionCollection());
+ m_document = 0;
+ }
+
+ close();
+ }
+
+ void
+ DeviceManagerDialog::slotDevicesResyncd()
+ {
+ makeConnectionList((unsigned int)MidiDevice::Play,
+ m_playConnections);
+ makeConnectionList((unsigned int)MidiDevice::Record,
+ m_recordConnections);
+
+ populate();
+ }
+
+ void
+ DeviceManagerDialog::populate()
+ {
+ DeviceList *devices = m_studio->getDevices();
+
+ //KConfig *config = kapp->config();
+ //config->setGroup(SequencerOptionsConfigGroup);
+ //DeviceId recordDevice =
+ //config->readUnsignedNumEntry("midirecorddevice");
+
+ m_playDevices.clear();
+ m_recordDevices.clear();
+
+ for (DeviceList::iterator it = devices->begin();
+ it != devices->end(); ++it) {
+ if ((*it)->getType() == Device::Midi) {
+ MidiDevice *md =
+ dynamic_cast<MidiDevice *>(*it);
+ if (md) {
+ if (md->getDirection() == MidiDevice::Play) {
+ m_playDevices.push_back(md);
+ } else {
+ m_recordDevices.push_back(md);
+ }
+ }
+ }
+ }
+
+ while (m_playTable->numRows() > 0) {
+ m_playTable->removeRow(m_playTable->numRows() - 1);
+ }
+ while (m_recordTable->numRows() > 0) {
+ m_recordTable->removeRow(m_recordTable->numRows() - 1);
+ }
+
+ int deviceCount = 0;
+
+ for (MidiDeviceList::iterator it = m_playDevices.begin();
+ it != m_playDevices.end(); ++it) {
+
+ m_playTable->insertRows(deviceCount, 1);
+
+ QString deviceName = i18n("%1").arg(deviceCount + 1);
+ QString connectionName = strtoqstr((*it)->getConnection());
+
+ m_playTable->setText(deviceCount, PLAY_NAME_COL,
+ strtoqstr((*it)->getName()));
+
+ int currentConnectionIndex = m_playConnections.size() - 1;
+ for (unsigned int i = 0; i < m_playConnections.size(); ++i) {
+ if (m_playConnections[i] == connectionName)
+ currentConnectionIndex = i;
+ }
+
+ QComboTableItem *item = new QComboTableItem(m_playTable, m_playConnections, false);
+ item->setCurrentItem(currentConnectionIndex);
+ m_playTable->setItem(deviceCount, PLAY_CONNECTION_COL, item);
+
+ m_playTable->adjustRow(deviceCount);
+ ++deviceCount;
+ }
+
+ int minPlayColumnWidths[] = { 250, 270 };
+ for (int i = 0; i < 2; ++i) {
+ m_playTable->adjustColumn(i);
+ if (m_playTable->columnWidth(i) < minPlayColumnWidths[i])
+ m_playTable->setColumnWidth(i, minPlayColumnWidths[i]);
+ }
+
+ deviceCount = 0;
+
+ for (MidiDeviceList::iterator it = m_recordDevices.begin();
+ it != m_recordDevices.end(); ++it) {
+
+ m_recordTable->insertRows(deviceCount, 1);
+
+ QString deviceName = i18n("%1").arg(deviceCount + 1);
+ QString connectionName = strtoqstr((*it)->getConnection());
+
+ m_recordTable->setText(deviceCount, RECORD_NAME_COL,
+ strtoqstr((*it)->getName()));
+
+ int currentConnectionIndex = m_recordConnections.size() - 1;
+ for (unsigned int i = 0; i < m_recordConnections.size(); ++i) {
+ if (m_recordConnections[i] == connectionName)
+ currentConnectionIndex = i;
+ }
+
+ QComboTableItem *item = new QComboTableItem(m_recordTable, m_recordConnections, false);
+ item->setCurrentItem(currentConnectionIndex);
+ m_recordTable->setItem(deviceCount, RECORD_CONNECTION_COL, item);
+
+ QCheckTableItem *check = new QCheckTableItem(m_recordTable, QString());
+ //check->setChecked((*it)->getId() == recordDevice);
+ //check->setText(((*it)->getId() == recordDevice) ?
+ // i18n("Yes") : i18n("No"));
+ check->setChecked((*it)->isRecording());
+ check->setText((*it)->isRecording() ? i18n("Yes") : i18n("No"));
+ m_recordTable->setItem(deviceCount, RECORD_CURRENT_COL, check);
+
+ m_recordTable->adjustRow(deviceCount);
+ ++deviceCount;
+ }
+
+ int minRecordColumnWidths[] = { 180, 70, 270 };
+ for (int i = 0; i < 3; ++i) {
+ m_recordTable->adjustColumn(i);
+ if (m_recordTable->columnWidth(i) < minRecordColumnWidths[i])
+ m_recordTable->setColumnWidth(i, minRecordColumnWidths[i]);
+ }
+
+ slotPlayDeviceSelected(m_playTable->currentRow(), m_playTable->currentColumn());
+ slotRecordDeviceSelected(m_recordTable->currentRow(), m_recordTable->currentColumn());
+ }
+
+ void
+ DeviceManagerDialog::makeConnectionList(unsigned int direction,
+ QStringList &list)
+ {
+ list.clear();
+
+ QByteArray data;
+ QByteArray replyData;
+ QCString replyType;
+ QDataStream arg(data, IO_WriteOnly);
+ arg << (int)Device::Midi;
+ arg << direction;
+
+ if (!rgapp->sequencerCall("getConnections(int, unsigned int)", replyType, replyData, data)) {
+ RG_DEBUG << "DeviceManagerDialog: can't call Sequencer" << endl;
+ list.append(m_noConnectionString);
+ return ;
+ }
+
+ QDataStream reply(replyData, IO_ReadOnly);
+ unsigned int connections = 0;
+ if (replyType == "unsigned int")
+ reply >> connections;
+
+ for (unsigned int i = 0; i < connections; ++i) {
+
+ QByteArray data;
+ QByteArray replyData;
+ QCString replyType;
+ QDataStream arg(data, IO_WriteOnly);
+ arg << (int)Device::Midi;
+ arg << direction;
+ arg << i;
+
+
+ if (!rgapp->sequencerCall("getConnection(int, unsigned int, unsigned int)",
+ replyType, replyData, data)) {
+ RG_DEBUG << "DeviceManagerDialog: can't call Sequencer" << endl;
+ list.append(i18n("No connection"));
+ return ;
+ }
+
+ QDataStream reply(replyData, IO_ReadOnly);
+ QString connection;
+ if (replyType == "QString") {
+ reply >> connection;
+ list.append(connection);
+ }
+ }
+
+ list.append(i18n("No connection"));
+ }
+
+ void
+ DeviceManagerDialog::closeEvent(QCloseEvent *e)
+ {
+ emit closing();
+ KMainWindow::closeEvent(e);
+ }
+
+ DeviceId
+ DeviceManagerDialog::getPlayDeviceIdAt(int row)
+ {
+ if (row < 0 || row > (int)m_playDevices.size())
+ return Device::NO_DEVICE;
+ return m_playDevices[row]->getId();
+ }
+
+ DeviceId
+ DeviceManagerDialog::getRecordDeviceIdAt(int row)
+ {
+ if (row < 0 || row > (int)m_recordDevices.size())
+ return Device::NO_DEVICE;
+ return m_recordDevices[row]->getId();
+ }
+
+ void
+ DeviceManagerDialog::slotAddPlayDevice()
+ {
+ QString connection = "";
+ if (m_playConnections.size() > 0)
+ connection = m_playConnections[m_playConnections.size() - 1];
+ CreateOrDeleteDeviceCommand *command = new CreateOrDeleteDeviceCommand
+ (m_studio,
+ qstrtostr(i18n("New Device")),
+ Device::Midi,
+ MidiDevice::Play,
+ qstrtostr(connection));
+ m_document->getCommandHistory()->addCommand(command);
+ }
+
+ void
+ DeviceManagerDialog::slotAddRecordDevice()
+ {
+ QString connection = "";
+ if (m_recordConnections.size() > 0)
+ connection = m_recordConnections[m_recordConnections.size() - 1];
+ CreateOrDeleteDeviceCommand *command = new CreateOrDeleteDeviceCommand
+ (m_studio,
+ qstrtostr(i18n("New Device")),
+ Device::Midi,
+ MidiDevice::Record,
+ qstrtostr(connection));
+ m_document->getCommandHistory()->addCommand(command);
+ }
+
+ void
+ DeviceManagerDialog::slotDeletePlayDevice()
+ {
+ // should really grey out if nothing current
+ DeviceId id = getPlayDeviceIdAt(m_playTable->currentRow());
+ if (id == Device::NO_DEVICE)
+ return ;
+ CreateOrDeleteDeviceCommand *command = new CreateOrDeleteDeviceCommand
+ (m_studio, id);
+ m_document->getCommandHistory()->addCommand(command);
+
+ QByteArray data;
+ QDataStream arg(data, IO_WriteOnly);
+ arg << (unsigned int)id;
+ rgapp->sequencerSend("removeDevice(unsigned int)", data);
+ }
+
+ void
+ DeviceManagerDialog::slotDeleteRecordDevice()
+ {
+ DeviceId id = getRecordDeviceIdAt(m_recordTable->currentRow());
+ if (id == Device::NO_DEVICE)
+ return ;
+ CreateOrDeleteDeviceCommand *command = new CreateOrDeleteDeviceCommand
+ (m_studio, id);
+ m_document->getCommandHistory()->addCommand(command);
+ }
+
+ void
+ DeviceManagerDialog::slotPlayValueChanged(int row, int col)
+ {
+ if (!m_document)
+ return ; // closing
+ DeviceId id = getPlayDeviceIdAt(row);
+ if (id == Device::NO_DEVICE)
+ return ;
+
+ Device *device = m_studio->getDevice(id);
+ if (!device) {
+ std::cerr << "WARNING: DeviceManagerDialog::slotPlayValueChanged(): device at row "
+ << row << " (id " << id << ") not found in studio"
+ << std::endl;
+ return ;
+ }
+
+ switch (col) {
+
+ case PLAY_NAME_COL: {
+ std::string name = qstrtostr(m_playTable->text(row, col));
+ if (device->getName() != name) {
+
+ m_document->getCommandHistory()->addCommand
+ (new RenameDeviceCommand(m_studio, id, name));
+ emit deviceNamesChanged();
+
+ QByteArray data;
+ QDataStream arg(data, IO_WriteOnly);
+
+ arg << (unsigned int)id;
+ arg << m_playTable->text(row, col);
+
+ rgapp->sequencerSend("renameDevice(unsigned int, QString)", data);
+ }
+ }
+ break;
+
+ case PLAY_CONNECTION_COL: {
+ std::string connection = qstrtostr(m_playTable->text(row, col));
+ if (connection == qstrtostr(m_noConnectionString))
+ connection = "";
+ if (device->getConnection() != connection) {
+ m_document->getCommandHistory()->addCommand
+ (new ReconnectDeviceCommand(m_studio, id, connection));
+ }
+ }
+ break;
+ }
+ }
+
+ void
+ DeviceManagerDialog::slotRecordValueChanged(int row, int col)
+ {
+ if (!m_document)
+ return ; // closing
+ DeviceId id = getRecordDeviceIdAt(row);
+ if (id == Device::NO_DEVICE)
+ return ;
+
+ Device *device = m_studio->getDevice(id);
+ if (!device) {
+ std::cerr << "WARNING: DeviceManagerDialog::slotRecordValueChanged(): device at row "
+ << row << " (id " << id << ") not found in studio"
+ << std::endl;
+ return ;
+ }
+
+ switch (col) {
+
+ case RECORD_NAME_COL: {
+ std::string name = qstrtostr(m_recordTable->text(row, col));
+ if (device->getName() != name) {
+
+ m_document->getCommandHistory()->addCommand
+ (new RenameDeviceCommand(m_studio, id, name));
+ emit deviceNamesChanged();
+
+ QByteArray data;
+ QDataStream arg(data, IO_WriteOnly);
+
+ arg << (unsigned int)id;
+ arg << m_recordTable->text(row, col);
+
+ rgapp->sequencerSend("renameDevice(unsigned int, QString)", data);
+ }
+ }
+ break;
+
+ case RECORD_CONNECTION_COL: {
+ std::string connection = qstrtostr(m_recordTable->text(row, col));
+ if (device->getConnection() != connection) {
+ m_document->getCommandHistory()->addCommand
+ (new ReconnectDeviceCommand(m_studio, id, connection));
+ }
+ }
+ break;
+
+ case RECORD_CURRENT_COL: {
+ m_recordTable->blockSignals(true);
+
+ QCheckTableItem *check =
+ dynamic_cast<QCheckTableItem *>(m_recordTable->item(row, col));
+ if (!check)
+ return ;
+
+ bool actionConnect = check->isChecked();
+
+ // The following lines are not strictly needed, but give the checkboxes
+ // a smoother behavior while waiting a confirmation from the sequencer.
+ //
+ check->setText(actionConnect ? i18n("Yes") : i18n("No"));
+ MidiDevice *device =
+ dynamic_cast<MidiDevice*>(m_studio->getDevice(id));
+ device->setRecording(actionConnect);
+
+ m_recordTable->setCurrentCell(row, 0);
+
+ m_document->getCommandHistory()->addCommand
+ (new ChangeRecordDeviceCommand(id, actionConnect));
+
+ m_recordTable->blockSignals(false);
+ }
+ break;
+ }
+ }
+
+ void
+ DeviceManagerDialog::slotPlayDeviceSelected(int row, int col)
+ {
+ RG_DEBUG << "slotPlayDeviceSelected(" << row << "," << col << ")" << endl;
+
+ bool enable = (row >= 0 && row < (int)m_playDevices.size());
+ m_deletePlayButton->setEnabled(enable);
+ m_importButton->setEnabled(enable);
+ m_exportButton->setEnabled(enable);
+ m_banksButton->setEnabled(enable);
+ m_controllersButton->setEnabled(enable);
+ }
+
+ void
+ DeviceManagerDialog::slotRecordDeviceSelected(int row, int col)
+ {
+ RG_DEBUG << "slotRecordDeviceSelected(" << row << "," << col << ")" << endl;
+
+ bool enable = (row >= 0 && row < (int)m_recordDevices.size());
+ m_deleteRecordButton->setEnabled(enable);
+ }
+
+ void
+ DeviceManagerDialog::slotImport()
+ {
+ DeviceId id = getPlayDeviceIdAt(m_playTable->currentRow());
+ if (id == Device::NO_DEVICE)
+ return ;
+
+ QString deviceDir = KGlobal::dirs()->findResource("appdata", "library/");
+ QDir dir(deviceDir);
+ if (!dir.exists()) {
+ deviceDir = ":ROSEGARDENDEVICE";
+ } else {
+ deviceDir = "file://" + deviceDir;
+ }
+
+ KURL url = KFileDialog::getOpenURL
+ (deviceDir,
+ "audio/x-rosegarden-device audio/x-rosegarden audio/x-soundfont",
+ this, i18n("Import from Device in File"));
+
+ if (url.isEmpty())
+ return ;
+
+ ImportDeviceDialog *dialog = new ImportDeviceDialog(this, url);
+ if (dialog->doImport() && dialog->exec() == QDialog::Accepted) {
+
+ ModifyDeviceCommand *command = 0;
+
+ BankList banks(dialog->getBanks());
+ ProgramList programs(dialog->getPrograms());
+ ControlList controls(dialog->getControllers());
+ KeyMappingList keyMappings(dialog->getKeyMappings());
+ MidiDevice::VariationType variation(dialog->getVariationType());
+ std::string librarianName(dialog->getLibrarianName());
+ std::string librarianEmail(dialog->getLibrarianEmail());
+
+ // don't record the librarian when
+ // merging banks -- it's misleading.
+ // (also don't use variation type)
+ if (!dialog->shouldOverwriteBanks()) {
+ librarianName = "";
+ librarianEmail = "";
+ }
+
+ command = new ModifyDeviceCommand(m_studio,
+ id,
+ dialog->getDeviceName(),
+ librarianName,
+ librarianEmail);
+
+ if (dialog->shouldOverwriteBanks()) {
+ command->setVariation(variation);
+ }
+ if (dialog->shouldImportBanks()) {
+ command->setBankList(banks);
+ command->setProgramList(programs);
+ }
+ if (dialog->shouldImportControllers()) {
+ command->setControlList(controls);
+ }
+ if (dialog->shouldImportKeyMappings()) {
+ command->setKeyMappingList(keyMappings);
+ }
+
+ command->setOverwrite(dialog->shouldOverwriteBanks());
+ command->setRename(dialog->shouldRename());
+
+ m_document->getCommandHistory()->addCommand(command);
+
+ if (dialog->shouldRename())
+ emit deviceNamesChanged();
+ }
+
+ delete dialog;
+ }
+
+ void
+ DeviceManagerDialog::slotExport()
+ {
+ QString extension = "rgd";
+
+ QString name =
+ KFileDialog::getSaveFileName(":ROSEGARDEN",
+ (extension.isEmpty() ? QString("*") : ("*." + extension)),
+ this,
+ i18n("Export Device as..."));
+
+ // Check for the existence of the name
+ if (name.isEmpty())
+ return ;
+
+ // Append extension if we don't have one
+ //
+ if (!extension.isEmpty()) {
+ if (!name.endsWith("." + extension)) {
+ name += "." + extension;
+ }
+ }
+
+ 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 ;
+
+ }
+
+ std::vector<DeviceId> devices;
+ DeviceId id = getPlayDeviceIdAt(m_playTable->currentRow());
+ MidiDevice *md = 0;
+ if (id != Device::NO_DEVICE) {
+ md = dynamic_cast<MidiDevice *>(m_studio->getDevice(id));
+ }
+ if (md) {
+ ExportDeviceDialog ed(this, strtoqstr(md->getName()));
+ if (ed.exec() != QDialog::Accepted)
+ return ;
+ if (ed.getExportType() == ExportDeviceDialog::ExportOne) {
+ devices.push_back(id);
+ }
+ }
+
+ m_document->exportStudio(name, devices);
+ }
+
+ void
+ DeviceManagerDialog::slotSetBanks()
+ {
+ DeviceId id = getPlayDeviceIdAt(m_playTable->currentRow());
+ emit editBanks(id);
+ }
+
+ void
+ DeviceManagerDialog::slotSetControllers()
+ {
+ DeviceId id = getPlayDeviceIdAt(m_playTable->currentRow());
+ emit editControllers(id);
+ }
+
+ }
+#include "DeviceManagerDialog.moc"
diff --git a/src/gui/studio/DeviceManagerDialog.h b/src/gui/studio/DeviceManagerDialog.h
new file mode 100644
index 0000000..aebc54e
--- /dev/null
+++ b/src/gui/studio/DeviceManagerDialog.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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_DEVICEMANAGERDIALOG_H_
+#define _RG_DEVICEMANAGERDIALOG_H_
+
+#include "base/Device.h"
+#include <kmainwindow.h>
+#include <qstring.h>
+#include <qstringlist.h>
+#include <vector>
+
+
+class QWidget;
+class QTable;
+class QPushButton;
+class QCloseEvent;
+
+
+namespace Rosegarden
+{
+
+class Studio;
+class RosegardenGUIDoc;
+class MidiDevice;
+
+
+class DeviceManagerDialog : public KMainWindow
+{
+ Q_OBJECT
+public:
+ DeviceManagerDialog(QWidget *parent, RosegardenGUIDoc *document);
+ ~DeviceManagerDialog();
+
+ void setModified(bool value);
+
+signals:
+ void deviceNamesChanged();
+
+ void editBanks(DeviceId);
+ void editControllers(DeviceId);
+
+ void closing();
+
+protected slots:
+ void slotClose();
+ void slotAddPlayDevice();
+ void slotAddRecordDevice();
+ void slotDeletePlayDevice();
+ void slotDeleteRecordDevice();
+ void slotPlayValueChanged(int row, int col);
+ void slotRecordValueChanged(int row, int col);
+ void slotPlayDeviceSelected(int row, int col);
+ void slotRecordDeviceSelected(int row, int col);
+
+ // for play devices only:
+ void slotImport();
+ void slotExport();
+ void slotSetBanks();
+ void slotSetControllers();
+
+ void slotDevicesResyncd();
+ void populate();
+
+protected:
+ virtual void closeEvent(QCloseEvent *);
+
+private:
+ RosegardenGUIDoc *m_document;
+ Studio *m_studio;
+
+ QPushButton *m_deletePlayButton;
+ QPushButton *m_deleteRecordButton;
+ QPushButton *m_importButton;
+ QPushButton *m_exportButton;
+ QPushButton *m_banksButton;
+ QPushButton *m_controllersButton;
+
+ QStringList m_playConnections;
+ QStringList m_recordConnections;
+ void makeConnectionList(unsigned int direction, QStringList &list);
+
+ QTable *m_playTable;
+ QTable *m_recordTable;
+
+ typedef std::vector<MidiDevice *> MidiDeviceList;
+ MidiDeviceList m_playDevices;
+ MidiDeviceList m_recordDevices;
+
+ DeviceId getPlayDeviceIdAt(int row); // NO_DEVICE = not found
+ DeviceId getRecordDeviceIdAt(int row); // NO_DEVICE = not found
+
+ QString m_noConnectionString;
+};
+
+
+}
+
+#endif
diff --git a/src/gui/studio/MidiBankListViewItem.cpp b/src/gui/studio/MidiBankListViewItem.cpp
new file mode 100644
index 0000000..2563ccb
--- /dev/null
+++ b/src/gui/studio/MidiBankListViewItem.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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "MidiBankListViewItem.h"
+
+#include <klocale.h>
+#include "base/Device.h"
+#include "MidiDeviceListViewItem.h"
+#include "MidiKeyMapListViewItem.h"
+#include <qstring.h>
+
+
+namespace Rosegarden
+{
+
+MidiBankListViewItem::MidiBankListViewItem(DeviceId deviceId,
+ int bankNb,
+ QListViewItem* parent,
+ QString name,
+ bool percussion, int msb, int lsb)
+ : MidiDeviceListViewItem(deviceId, parent, name, percussion, msb, lsb),
+ m_percussion(percussion),
+ m_bankNb(bankNb)
+{}
+
+void MidiBankListViewItem::setPercussion(bool percussion)
+{
+ m_percussion = percussion;
+ setText(1, QString(percussion ? i18n("Percussion Bank") : i18n("Bank")));
+}
+
+void MidiBankListViewItem::setMSB(int msb)
+{
+ setText(2, QString().setNum(msb));
+}
+
+void MidiBankListViewItem::setLSB(int lsb)
+{
+ setText(3, QString().setNum(lsb));
+}
+
+int MidiBankListViewItem::compare(QListViewItem *i, int col, bool ascending) const
+{
+ MidiBankListViewItem* bankItem = dynamic_cast<MidiBankListViewItem*>(i);
+
+ if (!bankItem) {
+ MidiKeyMapListViewItem *keyItem = dynamic_cast<MidiKeyMapListViewItem *>(i);
+ if (keyItem)
+ return -1; // banks before key maps
+ }
+
+ if (!bankItem || (col != 2 && col != 3)) {
+ return MidiDeviceListViewItem::compare(i, col, ascending);
+ }
+
+ int thisVal = text(col).toInt(),
+ otherVal = bankItem->text(col).toInt();
+
+ if (thisVal == otherVal) {
+ if (col == 2) { // if sorting on MSB, suborder with LSB
+ return compare(i, 3, ascending);
+ } else {
+ return 0;
+ }
+ }
+
+ // 'ascending' should be ignored according to Qt docs
+ //
+ return
+ thisVal > otherVal ? 1 :
+ thisVal == otherVal ? 0 :
+ -1;
+
+}
+
+}
diff --git a/src/gui/studio/MidiBankListViewItem.h b/src/gui/studio/MidiBankListViewItem.h
new file mode 100644
index 0000000..87f4b02
--- /dev/null
+++ b/src/gui/studio/MidiBankListViewItem.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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_MIDIBANKLISTVIEWITEM_H_
+#define _RG_MIDIBANKLISTVIEWITEM_H_
+
+#include "base/Device.h"
+#include "MidiDeviceListViewItem.h"
+#include <qstring.h>
+
+
+class QListViewItem;
+
+
+namespace Rosegarden
+{
+
+
+
+class MidiBankListViewItem : public MidiDeviceListViewItem
+{
+public:
+ MidiBankListViewItem(DeviceId deviceId,
+ int bankNb,
+ QListViewItem* parent, QString name,
+ bool percussion,
+ int msb, int lsb);
+
+ int getBank() { return m_bankNb; }
+
+ void setPercussion(bool percussion);
+ bool isPercussion() const { return m_percussion; }
+ void setMSB(int msb);
+ void setLSB(int msb);
+
+ virtual int compare(QListViewItem *i, int col, bool ascending) const;
+
+protected:
+
+ //--------------- Data members ---------------------------------
+ bool m_percussion;
+ int m_bankNb;
+};
+
+
+}
+
+#endif
diff --git a/src/gui/studio/MidiDeviceListViewItem.cpp b/src/gui/studio/MidiDeviceListViewItem.cpp
new file mode 100644
index 0000000..30a339e
--- /dev/null
+++ b/src/gui/studio/MidiDeviceListViewItem.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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "MidiDeviceListViewItem.h"
+
+#include <klocale.h>
+#include "base/Device.h"
+#include <qlistview.h>
+#include <qstring.h>
+
+
+namespace Rosegarden
+{
+
+MidiDeviceListViewItem::MidiDeviceListViewItem(DeviceId deviceId,
+ QListView* parent, QString name)
+ : KListViewItem(parent, name),
+ m_deviceId(deviceId)
+{}
+
+MidiDeviceListViewItem::MidiDeviceListViewItem(DeviceId deviceId,
+ QListViewItem* parent, QString name,
+ bool percussion,
+ int msb, int lsb)
+ : KListViewItem(parent, name,
+ QString(percussion ? i18n("Percussion Bank") : i18n("Bank")),
+ QString().setNum(msb), QString().setNum(lsb)),
+ m_deviceId(deviceId)
+{}
+
+MidiDeviceListViewItem::MidiDeviceListViewItem(DeviceId deviceId,
+ QListViewItem* parent, QString name)
+: KListViewItem(parent, name, i18n("Key Mapping"), "", ""),
+m_deviceId(deviceId)
+{}
+
+int MidiDeviceListViewItem::compare(QListViewItem *i, int col, bool ascending) const
+{
+ MidiDeviceListViewItem* item = dynamic_cast<MidiDeviceListViewItem*>(i);
+ if (!item)
+ return QListViewItem::compare(i, col, ascending);
+ if (col == 0)
+ return
+ getDeviceId() > item->getDeviceId() ? 1 :
+ getDeviceId() == item->getDeviceId() ? 0 :
+ -1;
+
+ int thisVal = text(col).toInt(),
+ otherVal = item->text(col).toInt();
+
+ if (thisVal == otherVal) {
+ if (col == 2) { // if sorting on MSB, suborder with LSB
+ return compare(i, 3, ascending);
+ } else {
+ return 0;
+ }
+ }
+
+ // 'ascending' should be ignored according to Qt docs
+ //
+ return (thisVal > otherVal) ? 1 : -1;
+
+ //!!! how to use percussion here?
+}
+
+}
diff --git a/src/gui/studio/MidiDeviceListViewItem.h b/src/gui/studio/MidiDeviceListViewItem.h
new file mode 100644
index 0000000..53652c6
--- /dev/null
+++ b/src/gui/studio/MidiDeviceListViewItem.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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_MIDIDEVICELISTVIEWITEM_H_
+#define _RG_MIDIDEVICELISTVIEWITEM_H_
+
+#include "base/Device.h"
+#include <klistview.h>
+#include <qstring.h>
+
+
+namespace Rosegarden
+{
+
+
+
+class MidiDeviceListViewItem : public KListViewItem
+{
+public:
+ // Device
+ MidiDeviceListViewItem(DeviceId id,
+ QListView* parent, QString name);
+
+ // Bank
+ MidiDeviceListViewItem(DeviceId id,
+ QListViewItem* parent, QString name,
+ bool percussion,
+ int msb, int lsb);
+
+ // Key Mapping
+ MidiDeviceListViewItem(DeviceId id,
+ QListViewItem* parent, QString name);
+
+ DeviceId getDeviceId() const { return m_deviceId; }
+
+ virtual int compare(QListViewItem *i, int col, bool ascending) const;
+
+protected:
+
+ //--------------- Data members ---------------------------------
+ DeviceId m_deviceId;
+};
+
+
+}
+
+#endif
diff --git a/src/gui/studio/MidiKeyMapListViewItem.cpp b/src/gui/studio/MidiKeyMapListViewItem.cpp
new file mode 100644
index 0000000..c070e04
--- /dev/null
+++ b/src/gui/studio/MidiKeyMapListViewItem.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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "MidiKeyMapListViewItem.h"
+
+#include "MidiDeviceListViewItem.h"
+#include "MidiBankListViewItem.h"
+#include "base/Device.h"
+#include <klocale.h>
+#include <qstring.h>
+
+
+namespace Rosegarden
+{
+
+MidiKeyMapListViewItem::MidiKeyMapListViewItem(DeviceId deviceId,
+ QListViewItem* parent,
+ QString name)
+ : MidiDeviceListViewItem(deviceId, parent, name),
+ m_name(name)
+{
+ setText(1, i18n("Key Mapping"));
+}
+
+int MidiKeyMapListViewItem::compare(QListViewItem *i, int col, bool ascending) const
+{
+ if (dynamic_cast<MidiBankListViewItem *>(i)) {
+ return 1; // banks before key maps
+ }
+
+ return MidiDeviceListViewItem::compare(i, col, ascending);
+}
+
+}
diff --git a/src/gui/studio/MidiKeyMapListViewItem.h b/src/gui/studio/MidiKeyMapListViewItem.h
new file mode 100644
index 0000000..df53de3
--- /dev/null
+++ b/src/gui/studio/MidiKeyMapListViewItem.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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_MIDIKEYMAPLISTVIEWITEM_H_
+#define _RG_MIDIKEYMAPLISTVIEWITEM_H_
+
+#include "base/Device.h"
+#include "MidiDeviceListViewItem.h"
+#include <qstring.h>
+
+
+class QListViewItem;
+
+
+namespace Rosegarden
+{
+
+
+
+class MidiKeyMapListViewItem : public MidiDeviceListViewItem
+{
+public:
+ MidiKeyMapListViewItem(DeviceId deviceId,
+ QListViewItem* parent, QString name);
+
+ virtual int compare(QListViewItem *i, int col, bool ascending) const;
+
+ QString getName() const { return m_name; }
+
+protected:
+ QString m_name;
+};
+
+
+}
+
+#endif
diff --git a/src/gui/studio/MidiKeyMappingEditor.cpp b/src/gui/studio/MidiKeyMappingEditor.cpp
new file mode 100644
index 0000000..f31d0dc
--- /dev/null
+++ b/src/gui/studio/MidiKeyMappingEditor.cpp
@@ -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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "MidiKeyMappingEditor.h"
+
+#include <klocale.h>
+#include "misc/Debug.h"
+#include "misc/Strings.h"
+#include "BankEditorDialog.h"
+#include "base/MidiDevice.h"
+#include "base/MidiProgram.h"
+#include "base/NotationTypes.h"
+#include "MidiKeyMapListViewItem.h"
+#include "NameSetEditor.h"
+#include <kcompletion.h>
+#include <klineedit.h>
+#include <qframe.h>
+#include <qlayout.h>
+#include <qlabel.h>
+#include <qobject.h>
+#include <qobjectlist.h>
+#include <qpushbutton.h>
+#include <qstring.h>
+#include <qvgroupbox.h>
+#include <qwidget.h>
+
+
+namespace Rosegarden
+{
+
+MidiKeyMappingEditor::MidiKeyMappingEditor(BankEditorDialog* bankEditor,
+ QWidget* parent,
+ const char* name)
+ : NameSetEditor(bankEditor,
+ i18n("Key Mapping details"),
+ parent, name, i18n("Pitches"), false),
+ m_device(0)
+{
+ QWidget *additionalWidget = makeAdditionalWidget(m_mainFrame);
+ if (additionalWidget) {
+ m_mainLayout->addMultiCellWidget(additionalWidget, 0, 2, 0, 2);
+ }
+}
+
+QWidget *
+MidiKeyMappingEditor::makeAdditionalWidget(QWidget *parent)
+{
+ return 0;
+}
+
+void
+MidiKeyMappingEditor::clearAll()
+{
+ blockAllSignals(true);
+
+ for (unsigned int i = 0; i < m_names.size(); ++i)
+ m_names[i]->clear();
+
+ setTitle(i18n("Key Mapping details"));
+
+ setEnabled(false);
+
+ blockAllSignals(false);
+}
+
+void
+MidiKeyMappingEditor::populate(QListViewItem* item)
+{
+ RG_DEBUG << "MidiKeyMappingEditor::populate\n";
+
+ MidiKeyMapListViewItem *keyItem =
+ dynamic_cast<MidiKeyMapListViewItem *>(item);
+ if (!keyItem) {
+ RG_DEBUG << "MidiKeyMappingEditor::populate : not a key item - returning\n";
+ return ;
+ }
+
+ MidiDevice* device = m_bankEditor->getCurrentMidiDevice();
+ if (!device)
+ return ;
+
+ m_device = device;
+ m_mappingName = qstrtostr(keyItem->getName());
+
+ setEnabled(true);
+
+ reset();
+}
+
+void
+MidiKeyMappingEditor::reset()
+{
+ if (!m_device)
+ return ;
+
+ setTitle(strtoqstr(m_mappingName));
+
+ const MidiKeyMapping *m = m_device->getKeyMappingByName(m_mappingName);
+
+ if (!m) {
+ RG_DEBUG << "WARNING: MidiKeyMappingEditor::reset: No such mapping as " << m_mappingName << endl;
+ }
+
+ m_mapping = *m;
+
+ blockAllSignals(true);
+
+ // Librarian details
+ //
+ m_librarian->setText(strtoqstr(m_device->getLibrarianName()));
+ m_librarianEmail->setText(strtoqstr(m_device->getLibrarianEmail()));
+
+ for (MidiKeyMapping::KeyNameMap::const_iterator it =
+ m_mapping.getMap().begin();
+ it != m_mapping.getMap().end(); ++it) {
+
+ int i = it->first;
+ if (i < 0 || i > 127) {
+ RG_DEBUG << "WARNING: MidiKeyMappingEditor::reset: Key " << i
+ << " out of range in mapping " << m_mapping.getName()
+ << endl;
+ continue;
+ }
+
+ QString name = strtoqstr(it->second);
+ m_completion.addItem(name);
+ m_names[i]->setText(name);
+ m_names[i]->setCursorPosition(0);
+ }
+
+ blockAllSignals(false);
+}
+
+void
+MidiKeyMappingEditor::slotNameChanged(const QString& name)
+{
+ const KLineEdit* lineEdit = dynamic_cast<const KLineEdit*>(sender());
+ if (!lineEdit) {
+ RG_DEBUG << "MidiKeyMappingEditor::slotNameChanged() : %%% ERROR - signal sender is not a KLineEdit\n";
+ return ;
+ }
+
+ QString senderName = sender()->name();
+
+ // Adjust value back to zero rated
+ //
+ unsigned int pitch = senderName.toUInt() - 1;
+
+ RG_DEBUG << "MidiKeyMappingEditor::slotNameChanged("
+ << name << ") : pitch = " << pitch << endl;
+
+ if (qstrtostr(name) != m_mapping.getMap()[pitch]) {
+ m_mapping.getMap()[pitch] = qstrtostr(name);
+ m_bankEditor->setModified(true);
+ }
+}
+
+void
+MidiKeyMappingEditor::slotEntryButtonPressed()
+{}
+
+void MidiKeyMappingEditor::blockAllSignals(bool block)
+{
+ const QObjectList* allChildren = queryList("KLineEdit", "[0-9]+");
+ QObjectListIt it(*allChildren);
+ QObject *obj;
+
+ while ( (obj = it.current()) != 0 ) {
+ obj->blockSignals(block);
+ ++it;
+ }
+}
+
+}
+#include "MidiKeyMappingEditor.moc"
diff --git a/src/gui/studio/MidiKeyMappingEditor.h b/src/gui/studio/MidiKeyMappingEditor.h
new file mode 100644
index 0000000..86959b8
--- /dev/null
+++ b/src/gui/studio/MidiKeyMappingEditor.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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_MIDIKEYMAPPINGEDITOR_H_
+#define _RG_MIDIKEYMAPPINGEDITOR_H_
+
+#include "base/MidiProgram.h"
+#include "NameSetEditor.h"
+#include <string>
+
+
+class QWidget;
+class QString;
+class QListViewItem;
+
+
+namespace Rosegarden
+{
+
+class MidiDevice;
+class BankEditorDialog;
+
+
+class MidiKeyMappingEditor : public NameSetEditor
+{
+ Q_OBJECT
+
+public:
+ MidiKeyMappingEditor(BankEditorDialog *bankEditor,
+ QWidget *parent,
+ const char *name = 0);
+
+ void clearAll();
+ void populate(QListViewItem *);
+ MidiKeyMapping &getMapping() { return m_mapping; }
+ void reset();
+
+public slots:
+ virtual void slotNameChanged(const QString &);
+ virtual void slotEntryButtonPressed();
+
+protected:
+ virtual QWidget *makeAdditionalWidget(QWidget *parent);
+ void blockAllSignals(bool block);
+
+ //--------------- Data members ---------------------------------
+
+ MidiDevice *m_device;
+ std::string m_mappingName;
+ MidiKeyMapping m_mapping;
+};
+
+
+}
+
+#endif
diff --git a/src/gui/studio/MidiMixerVUMeter.cpp b/src/gui/studio/MidiMixerVUMeter.cpp
new file mode 100644
index 0000000..2dd86cc
--- /dev/null
+++ b/src/gui/studio/MidiMixerVUMeter.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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "MidiMixerVUMeter.h"
+
+#include "gui/widgets/VUMeter.h"
+#include <qwidget.h>
+
+
+namespace Rosegarden
+{
+
+MidiMixerVUMeter::MidiMixerVUMeter(QWidget *parent,
+ VUMeterType type,
+ int width,
+ int height,
+ const char *name):
+ VUMeter(parent, type, false, false, width, height, VUMeter::Vertical, name)
+{
+ setAlignment(AlignCenter);
+}
+
+void
+MidiMixerVUMeter::meterStart()
+{}
+
+void
+MidiMixerVUMeter::meterStop()
+{}
+
+}
diff --git a/src/gui/studio/MidiMixerVUMeter.h b/src/gui/studio/MidiMixerVUMeter.h
new file mode 100644
index 0000000..f67d922
--- /dev/null
+++ b/src/gui/studio/MidiMixerVUMeter.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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_MIDIMIXERVUMETER_H_
+#define _RG_MIDIMIXERVUMETER_H_
+
+#include "gui/widgets/VUMeter.h"
+
+
+class QWidget;
+
+
+namespace Rosegarden
+{
+
+
+
+class MidiMixerVUMeter : public VUMeter
+{
+public:
+ MidiMixerVUMeter(QWidget *parent = 0,
+ VUMeterType type = Plain,
+ int width = 0,
+ int height = 0,
+ const char *name = 0);
+
+protected:
+ virtual void meterStart();
+ virtual void meterStop();
+
+private:
+ int m_textHeight;
+
+};
+
+
+}
+
+#endif
diff --git a/src/gui/studio/MidiMixerWindow.cpp b/src/gui/studio/MidiMixerWindow.cpp
new file mode 100644
index 0000000..127db7f
--- /dev/null
+++ b/src/gui/studio/MidiMixerWindow.cpp
@@ -0,0 +1,742 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
+
+/*
+ Rosegarden
+ A MIDI and audio sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "MidiMixerWindow.h"
+#include <qlayout.h>
+
+#include "sound/Midi.h"
+#include <klocale.h>
+#include "misc/Debug.h"
+#include "misc/Strings.h"
+#include "base/Colour.h"
+#include "base/Device.h"
+#include "base/Instrument.h"
+#include "base/MidiDevice.h"
+#include "base/MidiProgram.h"
+#include "base/Studio.h"
+#include "document/RosegardenGUIDoc.h"
+#include "gui/editors/notation/NotePixmapFactory.h"
+#include "gui/seqmanager/SequencerMapper.h"
+#include "gui/widgets/Fader.h"
+#include "gui/widgets/Rotary.h"
+#include "gui/widgets/VUMeter.h"
+#include "MidiMixerVUMeter.h"
+#include "MixerWindow.h"
+#include "sound/MappedEvent.h"
+#include "StudioControl.h"
+#include <kaction.h>
+#include <kmainwindow.h>
+#include <kstdaction.h>
+#include <qaccel.h>
+#include <qcolor.h>
+#include <qframe.h>
+#include <qiconset.h>
+#include <qlabel.h>
+#include <qobject.h>
+#include <qstring.h>
+#include <qtabwidget.h>
+#include <qwidget.h>
+
+
+namespace Rosegarden
+{
+
+MidiMixerWindow::MidiMixerWindow(QWidget *parent,
+ RosegardenGUIDoc *document):
+ MixerWindow(parent, document),
+ m_tabFrame(0)
+{
+ // Initial setup
+ //
+ setupTabs();
+
+ KStdAction::close(this,
+ SLOT(slotClose()),
+ actionCollection());
+
+ QIconSet 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-record")));
+ new KAction(i18n("&Record"), icon, 0, this,
+ SIGNAL(record()), actionCollection(),
+ "record");
+
+ icon = QIconSet(NotePixmapFactory::toQPixmap(NotePixmapFactory::makeToolbarPixmap
+ ("transport-panic")));
+ new KAction(i18n("Panic"), icon, Key_P + CTRL + ALT, this,
+ SIGNAL(panic()), actionCollection(),
+ "panic");
+
+ createGUI("midimixer.rc");
+
+}
+
+void
+MidiMixerWindow::setupTabs()
+{
+ DeviceListConstIterator it;
+ MidiDevice *dev = 0;
+ InstrumentList instruments;
+ InstrumentList::const_iterator iIt;
+ int faderCount = 0, deviceCount = 1;
+
+ if (m_tabFrame)
+ delete m_tabFrame;
+
+ // Setup m_tabFrame
+ //
+ m_tabWidget = new QTabWidget(this);
+ setCentralWidget(m_tabWidget);
+ connect(m_tabWidget, SIGNAL(currentChanged(QWidget *)),
+ this, SLOT(slotCurrentTabChanged(QWidget *)));
+ m_tabWidget->setTabPosition(QTabWidget::Bottom);
+ setCaption(i18n("MIDI Mixer"));
+
+ for (it = m_studio->begin(); it != m_studio->end(); ++it) {
+ dev = dynamic_cast<MidiDevice*>(*it);
+
+ if (dev) {
+ // Get the control parameters that are on the IPB (and hence can
+ // be shown here too).
+ //
+ ControlList controls = dev->getIPBControlParameters();
+
+ instruments = dev->getPresentationInstruments();
+
+ // Don't add a frame for empty devices
+ //
+ if (!instruments.size())
+ continue;
+
+ m_tabFrame = new QFrame(m_tabWidget);
+ m_tabFrame->setFrameStyle(QFrame::TabWidgetPanel);
+ m_tabFrame->setMargin(10);
+
+ QGridLayout *mainLayout = new QGridLayout
+ (m_tabFrame, instruments.size() + 4, controls.size() + 4, 5);
+
+ // MIDI Mixer label
+ //
+ //QLabel *label = new QLabel(QString("%1 %2").
+ //arg(strtoqstr(dev->getName()))
+ //.arg(i18n("MIDI Mixer")), m_tabFrame);
+
+ QLabel *label = new QLabel("", m_tabFrame);
+ mainLayout->addMultiCellWidget(label, 0, 0, 0, 16, Qt::AlignCenter);
+
+ // control labels
+ for (unsigned int i = 0; i < controls.size(); ++i) {
+ label = new QLabel(strtoqstr(controls[i].getName()), m_tabFrame);
+ mainLayout->addWidget(label, i + 1, 0, Qt::AlignCenter);
+ }
+
+ // meter label
+ //
+ //label = new QLabel(i18n("Meter"), m_tabFrame);
+ //mainLayout->addWidget(label,
+ //controls.size() + 1, 0, Qt::AlignCenter);
+
+ // volume label
+ label = new QLabel(i18n("Volume"), m_tabFrame);
+ mainLayout->addWidget(label, controls.size() + 2, 0,
+ Qt::AlignCenter);
+
+ // instrument label
+ label = new QLabel(i18n("Instrument"), m_tabFrame);
+ mainLayout->addWidget(label, controls.size() + 3, 0,
+ Qt::AlignCenter);
+
+ int posCount = 1;
+ int firstInstrument = -1;
+
+ for (iIt = instruments.begin(); iIt != instruments.end(); ++iIt) {
+
+ // Add new fader struct
+ //
+ m_faders.push_back(new FaderStruct());
+
+ // Store the first ID
+ //
+ if (firstInstrument == -1)
+ firstInstrument = (*iIt)->getId();
+
+
+ // Add the controls
+ //
+ for (unsigned int i = 0; i < controls.size(); ++i) {
+ QColor knobColour = Qt::white;
+
+ if (controls[i].getColourIndex() > 0) {
+ Colour c =
+ m_document->getComposition().getGeneralColourMap().
+ getColourByIndex(controls[i].getColourIndex());
+
+ knobColour = QColor(c.getRed(),
+ c.getGreen(), c.getBlue());
+ }
+
+ Rotary *controller =
+ new Rotary(m_tabFrame,
+ controls[i].getMin(),
+ controls[i].getMax(),
+ 1.0,
+ 5.0,
+ controls[i].getDefault(),
+ 20,
+ Rotary::NoTicks,
+ false,
+ controls[i].getDefault() == 64); //!!! hacky
+
+ controller->setKnobColour(knobColour);
+
+ connect(controller, SIGNAL(valueChanged(float)),
+ this, SLOT(slotControllerChanged(float)));
+
+ mainLayout->addWidget(controller, i + 1, posCount,
+ Qt::AlignCenter);
+
+ // Store the rotary
+ //
+ m_faders[faderCount]->m_controllerRotaries.push_back(
+ std::pair<MidiByte, Rotary*>
+ (controls[i].getControllerValue(), controller));
+ }
+
+ // Pan rotary
+ //
+ MidiMixerVUMeter *meter =
+ new MidiMixerVUMeter(m_tabFrame,
+ VUMeter::FixedHeightVisiblePeakHold, 6, 30);
+ mainLayout->addWidget(meter, controls.size() + 1,
+ posCount, Qt::AlignCenter);
+ m_faders[faderCount]->m_vuMeter = meter;
+
+ // Volume fader
+ //
+ Fader *fader =
+ new Fader(0, 127, 100, 20, 80, m_tabFrame);
+ mainLayout->addWidget(fader, controls.size() + 2,
+ posCount, Qt::AlignCenter);
+ m_faders[faderCount]->m_volumeFader = fader;
+ //fader->setFader(float((*iIt)->getVolume()));
+
+ // Label
+ //
+ QLabel *idLabel = new QLabel(QString("%1").
+ arg((*iIt)->getId() - firstInstrument + 1),
+ m_tabFrame, "idLabel");
+
+ mainLayout->addWidget(idLabel, controls.size() + 3,
+ posCount, Qt::AlignCenter);
+
+ // store id in struct
+ m_faders[faderCount]->m_id = (*iIt)->getId();
+
+ // Connect them up
+ //
+ connect(fader, SIGNAL(faderChanged(float)),
+ this, SLOT(slotFaderLevelChanged(float)));
+
+ // Update all the faders and controllers
+ //
+ slotUpdateInstrument((*iIt)->getId());
+
+ // Increment counters
+ //
+ posCount++;
+ faderCount++;
+ }
+
+ QString name = QString("%1 (%2)").arg(strtoqstr(dev->getName()))
+ .arg(deviceCount++);
+
+ addTab(m_tabFrame, name);
+ }
+ }
+}
+
+void
+MidiMixerWindow::addTab(QWidget *tab, const QString &title)
+{
+ m_tabWidget->addTab(tab, title);
+}
+
+void
+MidiMixerWindow::slotFaderLevelChanged(float value)
+{
+ const QObject *s = sender();
+
+ for (FaderVector::const_iterator it = m_faders.begin();
+ it != m_faders.end(); ++it) {
+ if ((*it)->m_volumeFader == s) {
+ Instrument *instr = m_studio->
+ getInstrumentById((*it)->m_id);
+
+ if (instr) {
+
+ instr->setVolume(MidiByte(value));
+
+ MappedEvent mE((*it)->m_id,
+ MappedEvent::MidiController,
+ MIDI_CONTROLLER_VOLUME,
+ MidiByte(value));
+ StudioControl::sendMappedEvent(mE);
+
+ // send out to external controllers as well.
+ //!!! really want some notification of whether we have any!
+ int tabIndex = m_tabWidget->currentPageIndex();
+ if (tabIndex < 0)
+ tabIndex = 0;
+ int i = 0;
+ for (DeviceList::const_iterator dit = m_studio->begin();
+ dit != m_studio->end(); ++dit) {
+ RG_DEBUG << "slotFaderLevelChanged: i = " << i << ", tabIndex " << tabIndex << endl;
+ if (!dynamic_cast<MidiDevice*>(*dit))
+ continue;
+ if (i != tabIndex) {
+ ++i;
+ continue;
+ }
+ RG_DEBUG << "slotFaderLevelChanged: device id = " << instr->getDevice()->getId() << ", visible device id " << (*dit)->getId() << endl;
+ if (instr->getDevice()->getId() == (*dit)->getId()) {
+ RG_DEBUG << "slotFaderLevelChanged: sending control device mapped event for channel " << instr->getMidiChannel() << endl;
+ mE.setRecordedChannel(instr->getMidiChannel());
+ mE.setRecordedDevice(Device::CONTROL_DEVICE);
+ StudioControl::sendMappedEvent(mE);
+ }
+ break;
+ }
+ }
+
+ emit instrumentParametersChanged((*it)->m_id);
+ return ;
+ }
+ }
+}
+
+void
+MidiMixerWindow::slotControllerChanged(float value)
+{
+ const QObject *s = sender();
+ unsigned int i = 0, j = 0;
+
+ for (i = 0; i < m_faders.size(); ++i) {
+ for (j = 0; j < m_faders[i]->m_controllerRotaries.size(); ++j) {
+ if (m_faders[i]->m_controllerRotaries[j].second == s)
+ break;
+ }
+
+ // break out on match
+ if (j != m_faders[i]->m_controllerRotaries.size())
+ break;
+ }
+
+ // Don't do anything if we've not matched and got solid values
+ // for i and j
+ //
+ if (i == m_faders.size() || j == m_faders[i]->m_controllerRotaries.size())
+ return ;
+
+ //RG_DEBUG << "MidiMixerWindow::slotControllerChanged - found a controller"
+ //<< endl;
+
+ Instrument *instr = m_studio->getInstrumentById(
+ m_faders[i]->m_id);
+
+ if (instr) {
+
+ //RG_DEBUG << "MidiMixerWindow::slotControllerChanged - "
+ //<< "got instrument to change" << endl;
+
+ if (m_faders[i]->m_controllerRotaries[j].first ==
+ MIDI_CONTROLLER_PAN)
+ instr->setPan(MidiByte(value));
+ else {
+ instr->setControllerValue(m_faders[i]->
+ m_controllerRotaries[j].first,
+ MidiByte(value));
+ }
+
+ MappedEvent mE(m_faders[i]->m_id,
+ MappedEvent::MidiController,
+ m_faders[i]->m_controllerRotaries[j].first,
+ MidiByte(value));
+ StudioControl::sendMappedEvent(mE);
+
+ int tabIndex = m_tabWidget->currentPageIndex();
+ if (tabIndex < 0)
+ tabIndex = 0;
+ int i = 0;
+ for (DeviceList::const_iterator dit = m_studio->begin();
+ dit != m_studio->end(); ++dit) {
+ RG_DEBUG << "slotControllerChanged: i = " << i << ", tabIndex " << tabIndex << endl;
+ if (!dynamic_cast<MidiDevice*>(*dit))
+ continue;
+ if (i != tabIndex) {
+ ++i;
+ continue;
+ }
+ RG_DEBUG << "slotControllerChanged: device id = " << instr->getDevice()->getId() << ", visible device id " << (*dit)->getId() << endl;
+ if (instr->getDevice()->getId() == (*dit)->getId()) {
+ RG_DEBUG << "slotControllerChanged: sending control device mapped event for channel " << instr->getMidiChannel() << endl;
+ // send out to external controllers as well.
+ //!!! really want some notification of whether we have any!
+ mE.setRecordedChannel(instr->getMidiChannel());
+ mE.setRecordedDevice(Device::CONTROL_DEVICE);
+ StudioControl::sendMappedEvent(mE);
+ }
+ }
+
+ emit instrumentParametersChanged(m_faders[i]->m_id);
+ }
+}
+
+void
+MidiMixerWindow::slotUpdateInstrument(InstrumentId id)
+{
+ //RG_DEBUG << "MidiMixerWindow::slotUpdateInstrument - id = " << id << endl;
+
+ DeviceListConstIterator it;
+ MidiDevice *dev = 0;
+ InstrumentList instruments;
+ InstrumentList::const_iterator iIt;
+ int count = 0;
+
+ blockSignals(true);
+
+ for (it = m_studio->begin(); it != m_studio->end(); ++it) {
+ dev = dynamic_cast<MidiDevice*>(*it);
+
+ if (dev) {
+ instruments = dev->getPresentationInstruments();
+ ControlList controls = dev->getIPBControlParameters();
+
+ for (iIt = instruments.begin(); iIt != instruments.end(); ++iIt) {
+ // Match and set
+ //
+ if ((*iIt)->getId() == id) {
+ // Set Volume fader
+ //
+ m_faders[count]->m_volumeFader->blockSignals(true);
+ m_faders[count]->m_volumeFader->
+ setFader(float((*iIt)->getVolume()));
+ m_faders[count]->m_volumeFader->blockSignals(false);
+
+ /*
+ StaticControllers &staticControls =
+ (*iIt)->getStaticControllers();
+ RG_DEBUG << "STATIC CONTROLS SIZE = "
+ << staticControls.size() << endl;
+ */
+
+ // Set all controllers for this Instrument
+ //
+ for (unsigned int i = 0; i < controls.size(); ++i) {
+ float value = 0.0;
+
+ m_faders[count]->m_controllerRotaries[i].second->blockSignals(true);
+
+ if (controls[i].getControllerValue() ==
+ MIDI_CONTROLLER_PAN) {
+ m_faders[count]->m_controllerRotaries[i].
+ second->setPosition((*iIt)->getPan());
+ } else {
+ // The ControllerValues might not yet be set on
+ // the actual Instrument so don't always expect
+ // to find one. There might be a hole here for
+ // deleted Controllers to hang around on
+ // Instruments..
+ //
+ try {
+ value = float((*iIt)->getControllerValue
+ (controls[i].getControllerValue()));
+ } catch (std::string s) {
+ /*
+ RG_DEBUG <<
+ "MidiMixerWindow::slotUpdateInstrument - "
+ << "can't match controller "
+ << int(controls[i].
+ getControllerValue()) << " - \""
+ << s << "\"" << endl;
+ */
+ continue;
+ }
+
+ /*
+ RG_DEBUG << "MidiMixerWindow::slotUpdateInstrument"
+ << " - MATCHED "
+ << int(controls[i].getControllerValue())
+ << endl;
+ */
+
+ m_faders[count]->m_controllerRotaries[i].
+ second->setPosition(value);
+ }
+
+ m_faders[count]->m_controllerRotaries[i].second->blockSignals(false);
+ }
+ }
+ count++;
+ }
+ }
+ }
+
+ blockSignals(false);
+}
+
+void
+MidiMixerWindow::updateMeters(SequencerMapper *mapper)
+{
+ for (unsigned int i = 0; i != m_faders.size(); ++i) {
+ LevelInfo info;
+ if (!mapper->
+ getInstrumentLevelForMixer(m_faders[i]->m_id, info))
+ continue;
+ m_faders[i]->m_vuMeter->setLevel(double(info.level / 127.0));
+ RG_DEBUG << "MidiMixerWindow::updateMeters - level " << info.level << endl;
+ }
+}
+
+void
+MidiMixerWindow::updateMonitorMeter(SequencerMapper *)
+{
+ // none here
+}
+
+void
+MidiMixerWindow::slotControllerDeviceEventReceived(MappedEvent *e,
+ const void *preferredCustomer)
+{
+ if (preferredCustomer != this)
+ return ;
+ RG_DEBUG << "MidiMixerWindow::slotControllerDeviceEventReceived: this one's for me" << endl;
+ raise();
+
+ // get channel number n from event
+ // get nth instrument on current tab
+
+ if (e->getType() != MappedEvent::MidiController)
+ return ;
+ unsigned int channel = e->getRecordedChannel();
+ MidiByte controller = e->getData1();
+ MidiByte value = e->getData2();
+
+ int tabIndex = m_tabWidget->currentPageIndex();
+
+ int i = 0;
+
+ for (DeviceList::const_iterator it = m_studio->begin();
+ it != m_studio->end(); ++it) {
+
+ MidiDevice *dev =
+ dynamic_cast<MidiDevice*>(*it);
+
+ if (!dev)
+ continue;
+ if (i != tabIndex) {
+ ++i;
+ continue;
+ }
+
+ InstrumentList instruments = dev->getPresentationInstruments();
+
+ for (InstrumentList::const_iterator iIt =
+ instruments.begin(); iIt != instruments.end(); ++iIt) {
+
+ Instrument *instrument = *iIt;
+
+ if (instrument->getMidiChannel() != channel)
+ continue;
+
+ 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 = dev->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;
+ }
+ }
+
+ MappedEvent mE(instrument->getId(),
+ MappedEvent::MidiController,
+ MidiByte(controller),
+ MidiByte(value));
+ StudioControl::sendMappedEvent(mE);
+
+ slotUpdateInstrument(instrument->getId());
+ emit instrumentParametersChanged(instrument->getId());
+ }
+
+ break;
+ }
+}
+
+void
+MidiMixerWindow::slotCurrentTabChanged(QWidget *)
+{
+ sendControllerRefresh();
+}
+
+void
+MidiMixerWindow::sendControllerRefresh()
+{
+ //!!! need to know if we have a current external controller device,
+ // as this is expensive
+
+ int tabIndex = m_tabWidget->currentPageIndex();
+ RG_DEBUG << "MidiMixerWindow::slotCurrentTabChanged: current is " << tabIndex << endl;
+
+ if (tabIndex < 0)
+ return ;
+
+ int i = 0;
+
+ for (DeviceList::const_iterator dit = m_studio->begin();
+ dit != m_studio->end(); ++dit) {
+
+ MidiDevice *dev = dynamic_cast<MidiDevice*>(*dit);
+ RG_DEBUG << "device is " << (*dit)->getId() << ", dev " << dev << endl;
+
+ if (!dev)
+ continue;
+ if (i != tabIndex) {
+ ++i;
+ continue;
+ }
+
+ InstrumentList instruments = dev->getPresentationInstruments();
+ ControlList controls = dev->getIPBControlParameters();
+
+ RG_DEBUG << "device has " << instruments.size() << " presentation instruments, " << dev->getAllInstruments().size() << " instruments " << endl;
+
+ for (InstrumentList::const_iterator iIt =
+ instruments.begin(); iIt != instruments.end(); ++iIt) {
+
+ Instrument *instrument = *iIt;
+ int channel = instrument->getMidiChannel();
+
+ RG_DEBUG << "instrument is " << instrument->getId() << endl;
+
+ for (ControlList::const_iterator cIt =
+ controls.begin(); cIt != controls.end(); ++cIt) {
+
+ int controller = (*cIt).getControllerValue();
+ int value;
+ if (controller == MIDI_CONTROLLER_PAN) {
+ value = instrument->getPan();
+ } else {
+ try {
+ value = instrument->getControllerValue(controller);
+ } catch (std::string s) {
+ std::cerr << "Exception in MidiMixerWindow::currentChanged: " << s << " (controller " << controller << ", instrument " << instrument->getId() << ")" << std::endl;
+ value = 0;
+ }
+ }
+
+ MappedEvent mE(instrument->getId(),
+ MappedEvent::MidiController,
+ controller, value);
+ mE.setRecordedChannel(channel);
+ mE.setRecordedDevice(Device::CONTROL_DEVICE);
+ StudioControl::sendMappedEvent(mE);
+ }
+
+ MappedEvent mE(instrument->getId(),
+ MappedEvent::MidiController,
+ MIDI_CONTROLLER_VOLUME,
+ instrument->getVolume());
+ mE.setRecordedChannel(channel);
+ mE.setRecordedDevice(Device::CONTROL_DEVICE);
+ RG_DEBUG << "sending controller mapped event for channel " << channel << ", volume " << instrument->getVolume() << endl;
+ StudioControl::sendMappedEvent(mE);
+ }
+
+ break;
+ }
+}
+
+void
+MidiMixerWindow::slotSynchronise()
+{
+ RG_DEBUG << "MidiMixer::slotSynchronise" << endl;
+ //setupTabs();
+}
+
+}
+#include "MidiMixerWindow.moc"
diff --git a/src/gui/studio/MidiMixerWindow.h b/src/gui/studio/MidiMixerWindow.h
new file mode 100644
index 0000000..d90dc55
--- /dev/null
+++ b/src/gui/studio/MidiMixerWindow.h
@@ -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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_MIDIMIXERWINDOW_H_
+#define _RG_MIDIMIXERWINDOW_H_
+
+#include "base/MidiProgram.h"
+#include "MixerWindow.h"
+#include <vector>
+
+
+class QWidget;
+class QTabWidget;
+class QString;
+class QFrame;
+
+
+namespace Rosegarden
+{
+
+class SequencerMapper;
+class Rotary;
+class RosegardenGUIDoc;
+class MidiMixerVUMeter;
+class MappedEvent;
+class Fader;
+
+
+class MidiMixerWindow : public MixerWindow
+{
+ Q_OBJECT
+
+public:
+ MidiMixerWindow(QWidget *parent, RosegardenGUIDoc *document);
+
+ /**
+ * Setup the tabs on the Mixer according to the Studio
+ */
+ void setupTabs();
+
+ /*
+ * Update the VU meters
+ */
+ void updateMeters(SequencerMapper *mapper);
+ void updateMonitorMeter(SequencerMapper *mapper);
+
+public slots:
+ void slotSynchronise(); // synchronise with updated studio
+
+ void slotControllerDeviceEventReceived(MappedEvent *,
+ const void *);
+
+ void slotCurrentTabChanged(QWidget *);
+
+signals:
+ void play();
+ void stop();
+ void fastForwardPlayback();
+ void rewindPlayback();
+ void fastForwardPlaybackToEnd();
+ void rewindPlaybackToBeginning();
+ void record();
+ void panic();
+
+ // to be redirected to the instrument parameter box if necessary
+ void instrumentParametersChanged(InstrumentId);
+
+protected slots:
+ void slotUpdateInstrument(InstrumentId);
+
+ //void slotPanChanged(float);
+ void slotFaderLevelChanged(float);
+ void slotControllerChanged(float);
+
+protected:
+ void addTab(QWidget *tab, const QString &title);
+
+ virtual void sendControllerRefresh();
+
+ QTabWidget *m_tabWidget;
+
+ struct FaderStruct {
+
+ FaderStruct():m_id(0), m_vuMeter(0), m_volumeFader(0) {}
+
+ InstrumentId m_id;
+ MidiMixerVUMeter *m_vuMeter;
+ Fader *m_volumeFader;
+ std::vector<std::pair<MidiByte, Rotary*> > m_controllerRotaries;
+
+ };
+
+ typedef std::vector<FaderStruct*> FaderVector;
+ FaderVector m_faders;
+
+ QFrame *m_tabFrame;
+
+};
+
+
+}
+
+#endif
diff --git a/src/gui/studio/MidiProgramsEditor.cpp b/src/gui/studio/MidiProgramsEditor.cpp
new file mode 100644
index 0000000..8f81a04
--- /dev/null
+++ b/src/gui/studio/MidiProgramsEditor.cpp
@@ -0,0 +1,631 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
+
+/*
+ Rosegarden
+ A MIDI and audio sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "MidiProgramsEditor.h"
+#include "MidiBankListViewItem.h"
+#include "NameSetEditor.h"
+#include "misc/Debug.h"
+#include "misc/Strings.h"
+#include "BankEditorDialog.h"
+#include "base/Device.h"
+#include "base/MidiDevice.h"
+#include "base/MidiProgram.h"
+#include "gui/widgets/RosegardenPopupMenu.h"
+#include <kcompletion.h>
+#include <kglobal.h>
+#include <klineedit.h>
+#include <klocale.h>
+#include <kstddirs.h>
+#include <qcheckbox.h>
+#include <qcursor.h>
+#include <qfile.h>
+#include <qframe.h>
+#include <qlabel.h>
+#include <qlayout.h>
+#include <qobjectlist.h>
+#include <qpixmap.h>
+#include <qpoint.h>
+#include <qpopupmenu.h>
+#include <qpushbutton.h>
+#include <qspinbox.h>
+#include <qstring.h>
+#include <qtooltip.h>
+#include <qvgroupbox.h>
+#include <qwidget.h>
+#include <algorithm>
+
+namespace Rosegarden
+{
+
+MidiProgramsEditor::MidiProgramsEditor(BankEditorDialog* bankEditor,
+ QWidget* parent,
+ const char* name)
+ : NameSetEditor(bankEditor,
+ i18n("Bank and Program details"),
+ parent, name, i18n("Programs"), true),
+ m_device(0),
+ m_bankList(bankEditor->getBankList()),
+ m_programList(bankEditor->getProgramList()),
+ m_oldBank(false, 0, 0)
+{
+ QWidget *additionalWidget = makeAdditionalWidget(m_mainFrame);
+ if (additionalWidget) {
+ m_mainLayout->addMultiCellWidget(additionalWidget, 0, 2, 0, 2);
+ }
+}
+
+QWidget *
+MidiProgramsEditor::makeAdditionalWidget(QWidget *parent)
+{
+ QFrame *frame = new QFrame(parent);
+
+ m_percussion = new QCheckBox(frame);
+ m_msb = new QSpinBox(frame);
+ m_lsb = new QSpinBox(frame);
+
+ QGridLayout *gridLayout = new QGridLayout(frame,
+ 3, // rows
+ 2, // cols
+ 2); // margin
+
+ gridLayout->addWidget(new QLabel(i18n("Percussion"), frame),
+ 0, 0, AlignLeft);
+ gridLayout->addWidget(m_percussion, 0, 1, AlignLeft);
+ connect(m_percussion, SIGNAL(clicked()),
+ this, SLOT(slotNewPercussion()));
+
+ gridLayout->addWidget(new QLabel(i18n("MSB Value"), frame),
+ 1, 0, AlignLeft);
+ m_msb->setMinValue(0);
+ m_msb->setMaxValue(127);
+ gridLayout->addWidget(m_msb, 1, 1, AlignLeft);
+
+ QToolTip::add
+ (m_msb,
+ i18n("Selects a MSB controller Bank number (MSB/LSB pairs are always unique for any Device)"));
+
+ QToolTip::add
+ (m_lsb,
+ i18n("Selects a LSB controller Bank number (MSB/LSB pairs are always unique for any Device)"));
+
+ connect(m_msb, SIGNAL(valueChanged(int)),
+ this, SLOT(slotNewMSB(int)));
+
+ gridLayout->addWidget(new QLabel(i18n("LSB Value"), frame),
+ 2, 0, AlignLeft);
+ m_lsb->setMinValue(0);
+ m_lsb->setMaxValue(127);
+ gridLayout->addWidget(m_lsb, 2, 1, AlignLeft);
+
+ connect(m_lsb, SIGNAL(valueChanged(int)),
+ this, SLOT(slotNewLSB(int)));
+
+ return frame;
+}
+
+ProgramList
+MidiProgramsEditor::getBankSubset(const MidiBank &bank)
+{
+ ProgramList program;
+ ProgramList::iterator it;
+
+ for (it = m_programList.begin(); it != m_programList.end(); it++) {
+ if (it->getBank() == bank)
+ program.push_back(*it);
+ }
+
+ return program;
+}
+
+MidiBank*
+MidiProgramsEditor::getCurrentBank()
+{
+ return m_currentBank;
+}
+
+void
+MidiProgramsEditor::modifyCurrentPrograms(const MidiBank &oldBank,
+ const MidiBank &newBank)
+{
+ ProgramList::iterator it;
+
+ for (it = m_programList.begin(); it != m_programList.end(); it++) {
+ if (it->getBank() == oldBank) {
+ *it = MidiProgram(newBank, it->getProgram(), it->getName());
+ }
+ }
+}
+
+void
+MidiProgramsEditor::clearAll()
+{
+ blockAllSignals(true);
+
+ for (unsigned int i = 0; i < m_names.size(); ++i)
+ m_names[i]->clear();
+
+ setTitle(i18n("Bank and Program details"));
+
+ m_percussion->setChecked(false);
+ m_msb->setValue(0);
+ m_lsb->setValue(0);
+ m_librarian->clear();
+ m_librarianEmail->clear();
+ m_currentBank = 0;
+ setEnabled(false);
+
+ blockAllSignals(false);
+}
+
+void
+MidiProgramsEditor::populate(QListViewItem* item)
+{
+ RG_DEBUG << "MidiProgramsEditor::populate\n";
+
+ MidiBankListViewItem* bankItem = dynamic_cast<MidiBankListViewItem*>(item);
+ if (!bankItem) {
+ RG_DEBUG << "MidiProgramsEditor::populate : not a bank item - returning\n";
+ return ;
+ }
+
+ DeviceId deviceId = bankItem->getDeviceId();
+ m_device = m_bankEditor->getMidiDevice(deviceId);
+ if (!m_device)
+ return ;
+
+ setEnabled(true);
+
+ setBankName(item->text(0));
+
+ RG_DEBUG << "MidiProgramsEditor::populate : bankItem->getBank = "
+ << bankItem->getBank() << endl;
+
+ m_currentBank = &(m_bankList[bankItem->getBank()]); // m_device->getBankByIndex(bankItem->getBank());
+
+ blockAllSignals(true);
+
+ // set the bank values
+ m_percussion->setChecked(m_currentBank->isPercussion());
+ m_msb->setValue(m_currentBank->getMSB());
+ m_lsb->setValue(m_currentBank->getLSB());
+
+ m_oldBank = *m_currentBank;
+
+ // Librarian details
+ //
+ m_librarian->setText(strtoqstr(m_device->getLibrarianName()));
+ m_librarianEmail->setText(strtoqstr(m_device->getLibrarianEmail()));
+
+ ProgramList programSubset = getBankSubset(*m_currentBank);
+ ProgramList::iterator it;
+
+ QPixmap noKeyPixmap, keyPixmap;
+ QString pixmapDir = KGlobal::dirs()->findResource("appdata", "pixmaps/");
+ QString file = pixmapDir + "/toolbar/key-white.png";
+ if (QFile(file).exists())
+ noKeyPixmap = QPixmap(file);
+ file = pixmapDir + "/toolbar/key-green.png";
+ if (QFile(file).exists())
+ keyPixmap = QPixmap(file);
+
+ bool haveKeyMappings = m_currentBank->isPercussion()
+ && (m_device->getKeyMappings().size() > 0);
+
+ for (unsigned int i = 0; i < m_names.size(); i++) {
+ m_names[i]->clear();
+ getEntryButton(i)->setEnabled(haveKeyMappings);
+ getEntryButton(i)->setPixmap(noKeyPixmap);
+ QToolTip::remove
+ ( getEntryButton(i) );
+
+ for (it = programSubset.begin(); it != programSubset.end(); it++) {
+ if (it->getProgram() == i) {
+
+ QString programName = strtoqstr(it->getName());
+ m_completion.addItem(programName);
+ m_names[i]->setText(programName);
+
+ if (m_device->getKeyMappingForProgram(*it)) {
+ getEntryButton(i)->setPixmap(keyPixmap);
+ QToolTip::add
+ (getEntryButton(i),
+ i18n("Key Mapping: %1").arg(
+ strtoqstr(m_device->getKeyMappingForProgram(*it)->getName())));
+ }
+
+ break;
+ }
+ }
+
+ // show start of label
+ m_names[i]->setCursorPosition(0);
+ }
+
+ blockAllSignals(false);
+}
+
+void
+MidiProgramsEditor::reset()
+{
+ m_percussion->blockSignals(true);
+ m_msb->blockSignals(true);
+ m_lsb->blockSignals(true);
+
+ m_percussion->setChecked(m_oldBank.isPercussion());
+ m_msb->setValue(m_oldBank.getMSB());
+ m_lsb->setValue(m_oldBank.getLSB());
+
+ if (m_currentBank) {
+ modifyCurrentPrograms(*m_currentBank, m_oldBank);
+ *m_currentBank = m_oldBank;
+ }
+
+ m_percussion->blockSignals(false);
+ m_msb->blockSignals(false);
+ m_lsb->blockSignals(false);
+}
+
+void
+MidiProgramsEditor::slotNewPercussion()
+{
+ RG_DEBUG << "MidiProgramsEditor::slotNewPercussion" << endl;
+ bool percussion = m_percussion->isChecked();
+ m_percussion->blockSignals(true);
+ if (banklistContains(MidiBank(percussion, m_msb->value(), m_lsb->value()))) {
+ RG_DEBUG << "MidiProgramsEditor::slotNewPercussion: calling setChecked(" << !percussion << ")" << endl;
+ m_percussion->setChecked(!percussion);
+ } else {
+ MidiBank newBank(percussion,
+ m_msb->value(),
+ m_lsb->value());
+ modifyCurrentPrograms(*getCurrentBank(), newBank);
+ *getCurrentBank() = newBank;
+ }
+ m_percussion->blockSignals(false);
+ m_bankEditor->setModified(true);
+}
+
+void
+MidiProgramsEditor::slotNewMSB(int value)
+{
+ RG_DEBUG << "MidiProgramsEditor::slotNewMSB(" << value << ")\n";
+
+ m_msb->blockSignals(true);
+
+ int msb;
+
+ try {
+ msb = ensureUniqueMSB(value, value > getCurrentBank()->getMSB());
+ } catch (bool) {
+ msb = getCurrentBank()->getMSB();
+ }
+
+ MidiBank newBank(m_percussion->isChecked(),
+ msb,
+ m_lsb->value());
+
+ modifyCurrentPrograms(*getCurrentBank(), newBank);
+
+ m_msb->setValue(msb);
+ *getCurrentBank() = newBank;
+
+ m_msb->blockSignals(false);
+
+ m_bankEditor->setModified(true);
+}
+
+void
+MidiProgramsEditor::slotNewLSB(int value)
+{
+ RG_DEBUG << "MidiProgramsEditor::slotNewLSB(" << value << ")\n";
+
+ m_lsb->blockSignals(true);
+
+ int lsb;
+
+ try {
+ lsb = ensureUniqueLSB(value, value > getCurrentBank()->getLSB());
+ } catch (bool) {
+ lsb = getCurrentBank()->getLSB();
+ }
+
+ MidiBank newBank(m_percussion->isChecked(),
+ m_msb->value(),
+ lsb);
+
+ modifyCurrentPrograms(*getCurrentBank(), newBank);
+
+ m_lsb->setValue(lsb);
+ *getCurrentBank() = newBank;
+
+ m_lsb->blockSignals(false);
+
+ m_bankEditor->setModified(true);
+}
+
+struct ProgramCmp
+{
+ bool operator()(const Rosegarden::MidiProgram &p1,
+ const Rosegarden::MidiProgram &p2)
+ {
+ if (p1.getProgram() == p2.getProgram()) {
+ const Rosegarden::MidiBank &b1(p1.getBank());
+ const Rosegarden::MidiBank &b2(p2.getBank());
+ if (b1.getMSB() == b2.getMSB())
+ if (b1.getLSB() == b2.getLSB())
+ return ((b1.isPercussion() ? 1 : 0) < (b2.isPercussion() ? 1 : 0));
+ else return (b1.getLSB() < b2.getLSB());
+ else return (b1.getMSB() < b2.getMSB());
+ } else return (p1.getProgram() < p2.getProgram());
+ }
+};
+
+void
+MidiProgramsEditor::slotNameChanged(const QString& programName)
+{
+ const KLineEdit* lineEdit = dynamic_cast<const KLineEdit*>(sender());
+ if (!lineEdit) {
+ RG_DEBUG << "MidiProgramsEditor::slotProgramChanged() : %%% ERROR - signal sender is not a KLineEdit\n";
+ return ;
+ }
+
+ QString senderName = sender()->name();
+
+ // Adjust value back to zero rated
+ //
+ unsigned int id = senderName.toUInt() - 1;
+
+ RG_DEBUG << "MidiProgramsEditor::slotNameChanged("
+ << programName << ") : id = " << id << endl;
+
+ MidiProgram *program = getProgram(*getCurrentBank(), id);
+
+ if (program == 0) {
+ // Do nothing if program name is empty
+ if (programName.isEmpty())
+ return ;
+
+ program = new MidiProgram(*getCurrentBank(), id);
+ m_programList.push_back(*program);
+
+ // Sort the program list by id
+ std::sort(m_programList.begin(), m_programList.end(), ProgramCmp());
+
+ // Now, get with the program
+ //
+ program = getProgram(*getCurrentBank(), id);
+ } else {
+ // If we've found a program and the label is now empty
+ // then remove it from the program list.
+ //
+ if (programName.isEmpty()) {
+ ProgramList::iterator it = m_programList.begin();
+ ProgramList tmpProg;
+
+ for (; it != m_programList.end(); it++) {
+ if (((unsigned int)it->getProgram()) == id) {
+ m_programList.erase(it);
+ m_bankEditor->setModified(true);
+ RG_DEBUG << "deleting empty program (" << id << ")" << endl;
+ return ;
+ }
+ }
+ }
+ }
+
+ if (qstrtostr(programName) != program->getName()) {
+ program->setName(qstrtostr(programName));
+ m_bankEditor->setModified(true);
+ }
+}
+
+void
+MidiProgramsEditor::slotEntryButtonPressed()
+{
+ QPushButton* button = dynamic_cast<QPushButton*>(const_cast<QObject *>(sender()));
+ if (!button) {
+ RG_DEBUG << "MidiProgramsEditor::slotEntryButtonPressed() : %%% ERROR - signal sender is not a QPushButton\n";
+ return ;
+ }
+
+ QString senderName = button->name();
+
+ if (!m_device)
+ return ;
+
+ const KeyMappingList &kml = m_device->getKeyMappings();
+ if (kml.empty())
+ return ;
+
+ // Adjust value back to zero rated
+ //
+ unsigned int id = senderName.toUInt() - 1;
+ MidiProgram *program = getProgram(*getCurrentBank(), id);
+ if (!program)
+ return ;
+ m_currentMenuProgram = id;
+
+ RosegardenPopupMenu *menu = new RosegardenPopupMenu(button);
+
+ const MidiKeyMapping *currentMapping =
+ m_device->getKeyMappingForProgram(*program);
+ int currentEntry = 0;
+
+ menu->insertItem(i18n("<no key mapping>"), this,
+ SLOT(slotEntryMenuItemSelected(int)), 0, 0);
+ menu->setItemParameter(0, 0);
+
+ for (int i = 0; i < kml.size(); ++i) {
+ menu->insertItem(strtoqstr(kml[i].getName()),
+ this, SLOT(slotEntryMenuItemSelected(int)),
+ 0, i + 1);
+ menu->setItemParameter(i + 1, i + 1);
+ if (currentMapping && (kml[i] == *currentMapping))
+ currentEntry = i + 1;
+ }
+
+ int itemHeight = menu->itemHeight(0) + 2;
+ QPoint pos = QCursor::pos();
+
+ pos.rx() -= 10;
+ pos.ry() -= (itemHeight / 2 + currentEntry * itemHeight);
+
+ menu->popup(pos);
+}
+
+void
+MidiProgramsEditor::slotEntryMenuItemSelected(int i)
+{
+ if (!m_device)
+ return ;
+
+ const KeyMappingList &kml = m_device->getKeyMappings();
+ if (kml.empty())
+ return ;
+
+ MidiProgram *program = getProgram(*getCurrentBank(), m_currentMenuProgram);
+ if (!program)
+ return ;
+
+ std::string newMapping;
+
+ if (i == 0) { // no key mapping
+ newMapping = "";
+ } else {
+ --i;
+ if (i < kml.size()) {
+ newMapping = kml[i].getName();
+ }
+ }
+
+ m_device->setKeyMappingForProgram(*program, newMapping);
+ QString pixmapDir = KGlobal::dirs()->findResource("appdata", "pixmaps/");
+ bool haveKeyMappings = (m_device->getKeyMappings().size() > 0);
+ QPushButton *btn = getEntryButton(m_currentMenuProgram);
+
+ if (newMapping.empty()) {
+ QString file = pixmapDir + "/toolbar/key-white.png";
+ if (QFile(file).exists()) {
+ btn->setPixmap(QPixmap(file));
+ }
+ QToolTip::remove
+ (btn);
+ } else {
+ QString file = pixmapDir + "/toolbar/key-green.png";
+ if (QFile(file).exists()) {
+ btn->setPixmap(QPixmap(file));
+ }
+ QToolTip::add
+ (btn, i18n("Key Mapping: %1").arg(strtoqstr(newMapping)));
+ }
+ btn->setEnabled(haveKeyMappings);
+}
+
+int
+MidiProgramsEditor::ensureUniqueMSB(int msb, bool ascending)
+{
+ int newMSB = msb;
+ while (banklistContains(MidiBank(m_percussion->isChecked(),
+ newMSB, m_lsb->value()))
+ && newMSB < 128
+ && newMSB > -1)
+ if (ascending)
+ newMSB++;
+ else
+ newMSB--;
+
+ if (newMSB == -1 || newMSB == 128)
+ throw false;
+
+ return newMSB;
+}
+
+int
+MidiProgramsEditor::ensureUniqueLSB(int lsb, bool ascending)
+{
+ int newLSB = lsb;
+ while (banklistContains(MidiBank(m_percussion->isChecked(),
+ m_msb->value(), newLSB))
+ && newLSB < 128
+ && newLSB > -1)
+ if (ascending)
+ newLSB++;
+ else
+ newLSB--;
+
+ if (newLSB == -1 || newLSB == 128)
+ throw false;
+
+ return newLSB;
+}
+
+bool
+MidiProgramsEditor::banklistContains(const MidiBank &bank)
+{
+ BankList::iterator it;
+
+ for (it = m_bankList.begin(); it != m_bankList.end(); it++)
+ if (*it == bank)
+ return true;
+
+ return false;
+}
+
+MidiProgram*
+MidiProgramsEditor::getProgram(const MidiBank &bank, int programNo)
+{
+ ProgramList::iterator it = m_programList.begin();
+
+ for (; it != m_programList.end(); it++) {
+ if (it->getBank() == bank && it->getProgram() == programNo)
+ return &(*it);
+ }
+
+ return 0;
+}
+
+void
+MidiProgramsEditor::setBankName(const QString& s)
+{
+ setTitle(s);
+}
+
+void MidiProgramsEditor::blockAllSignals(bool block)
+{
+ const QObjectList* allChildren = queryList("KLineEdit", "[0-9]+");
+ QObjectListIt it(*allChildren);
+ QObject *obj;
+
+ while ( (obj = it.current()) != 0 ) {
+ obj->blockSignals(block);
+ ++it;
+ }
+
+ m_msb->blockSignals(block);
+ m_lsb->blockSignals(block);
+}
+
+}
+#include "MidiProgramsEditor.moc"
diff --git a/src/gui/studio/MidiProgramsEditor.h b/src/gui/studio/MidiProgramsEditor.h
new file mode 100644
index 0000000..d0ef565
--- /dev/null
+++ b/src/gui/studio/MidiProgramsEditor.h
@@ -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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_MIDIPROGRAMSEDITOR_H_
+#define _RG_MIDIPROGRAMSEDITOR_H_
+
+#include "base/MidiProgram.h"
+#include "NameSetEditor.h"
+
+
+class QWidget;
+class QString;
+class QSpinBox;
+class QListViewItem;
+class QCheckBox;
+class BankList;
+
+
+namespace Rosegarden
+{
+
+class MidiProgram;
+class MidiDevice;
+class BankEditorDialog;
+
+
+class MidiProgramsEditor : public NameSetEditor
+{
+ Q_OBJECT
+public:
+ MidiProgramsEditor(BankEditorDialog *bankEditor,
+ QWidget *parent,
+ const char *name = 0);
+
+ void clearAll();
+ void populate(QListViewItem*);
+ void reset();
+
+public slots:
+
+ // Check that any new MSB/LSB combination is unique for this device
+ //
+ void slotNewMSB(int value);
+ void slotNewLSB(int value);
+ void slotNewPercussion(); // gets value from checkbox
+
+ virtual void slotNameChanged(const QString &);
+ virtual void slotEntryButtonPressed();
+ void slotEntryMenuItemSelected(int);
+
+protected:
+
+ MidiBank* getCurrentBank();
+
+ int ensureUniqueMSB(int msb, bool ascending);
+ int ensureUniqueLSB(int lsb, bool ascending);
+
+ // Does the banklist contain this combination already?
+ //
+ bool banklistContains(const MidiBank &);
+
+ ProgramList getBankSubset(const MidiBank &);
+
+ /// Set the currently loaded programs to new MSB and LSB
+ void modifyCurrentPrograms(const MidiBank &oldBank,
+ const MidiBank &newBank);
+
+ // Get a program (pointer into program list) for modification
+ //
+ MidiProgram* getProgram(const MidiBank &bank, int program);
+
+ void setBankName(const QString& s);
+
+ virtual QWidget *makeAdditionalWidget(QWidget *parent);
+
+ void blockAllSignals(bool block);
+
+ //--------------- Data members ---------------------------------
+ QCheckBox *m_percussion;
+ QSpinBox *m_msb;
+ QSpinBox *m_lsb;
+
+ MidiDevice *m_device;
+
+ MidiBank *m_currentBank;
+ BankList &m_bankList;
+ ProgramList &m_programList;
+
+ MidiBank m_oldBank;
+
+ unsigned int m_currentMenuProgram;
+};
+
+
+}
+
+#endif
diff --git a/src/gui/studio/MixerWindow.cpp b/src/gui/studio/MixerWindow.cpp
new file mode 100644
index 0000000..2a65024
--- /dev/null
+++ b/src/gui/studio/MixerWindow.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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "MixerWindow.h"
+
+#include "misc/Debug.h"
+#include "base/MidiProgram.h"
+#include "base/Studio.h"
+#include "document/RosegardenGUIDoc.h"
+#include <kmainwindow.h>
+#include <qaccel.h>
+#include <qwidget.h>
+
+
+namespace Rosegarden
+{
+
+MixerWindow::MixerWindow(QWidget *parent,
+ RosegardenGUIDoc *document) :
+ KMainWindow(parent, "mixerwindow"),
+ m_document(document),
+ m_studio(&document->getStudio()),
+ m_currentId(0)
+{
+ m_accelerators = new QAccel(this);
+}
+
+void
+MixerWindow::closeEvent(QCloseEvent *e)
+{
+ RG_DEBUG << "MixerWindow::closeEvent()\n";
+ emit closing();
+ KMainWindow::closeEvent(e);
+}
+
+void
+MixerWindow::windowActivationChange(bool)
+{
+ if (isActiveWindow()) {
+ emit windowActivated();
+ sendControllerRefresh();
+ }
+}
+
+void
+MixerWindow::slotClose()
+{
+ RG_DEBUG << "MixerWindow::slotClose()\n";
+ close();
+}
+
+}
+#include "MixerWindow.moc"
diff --git a/src/gui/studio/MixerWindow.h b/src/gui/studio/MixerWindow.h
new file mode 100644
index 0000000..9e5f9cf
--- /dev/null
+++ b/src/gui/studio/MixerWindow.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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_MIXERWINDOW_H_
+#define _RG_MIXERWINDOW_H_
+
+#include "base/MidiProgram.h"
+#include <kmainwindow.h>
+
+
+class QWidget;
+class QCloseEvent;
+class QAccel;
+
+
+namespace Rosegarden
+{
+
+class Studio;
+class RosegardenGUIDoc;
+
+
+class MixerWindow: public KMainWindow
+{
+ Q_OBJECT
+
+public:
+ MixerWindow(QWidget *parent, RosegardenGUIDoc *document);
+ QAccel* getAccelerators() { return m_accelerators; }
+
+signals:
+ void closing();
+ void windowActivated();
+
+protected slots:
+ void slotClose();
+
+protected:
+ virtual void closeEvent(QCloseEvent *);
+ virtual void windowActivationChange(bool);
+
+ virtual void sendControllerRefresh() = 0;
+
+ RosegardenGUIDoc *m_document;
+ Studio *m_studio;
+ InstrumentId m_currentId;
+
+ QAccel *m_accelerators;
+
+};
+
+
+}
+
+#endif
diff --git a/src/gui/studio/NameSetEditor.cpp b/src/gui/studio/NameSetEditor.cpp
new file mode 100644
index 0000000..8dadd0c
--- /dev/null
+++ b/src/gui/studio/NameSetEditor.cpp
@@ -0,0 +1,190 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
+
+/*
+ Rosegarden
+ A MIDI and audio sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "NameSetEditor.h"
+#include "BankEditorDialog.h"
+#include <kcompletion.h>
+#include <kglobalsettings.h>
+#include <klineedit.h>
+#include <klocale.h>
+#include <qframe.h>
+#include <qgroupbox.h>
+#include <qhbox.h>
+#include <qlabel.h>
+#include <qlayout.h>
+#include <qpushbutton.h>
+#include <qstring.h>
+#include <qtabwidget.h>
+#include <qtooltip.h>
+#include <qvbox.h>
+#include <qvgroupbox.h>
+#include <qwidget.h>
+#include <iostream>
+
+namespace Rosegarden
+{
+
+NameSetEditor::NameSetEditor(BankEditorDialog* bankEditor,
+ QString title,
+ QWidget* parent,
+ const char* name,
+ QString headingPrefix,
+ bool showEntryButtons)
+ : QVGroupBox(title, parent, name),
+ m_bankEditor(bankEditor),
+ m_mainFrame(new QFrame(this))
+{
+ m_mainLayout = new QGridLayout(m_mainFrame,
+ 4, // rows
+ 6, // cols
+ 2); // margin
+
+ // Librarian
+ //
+ QGroupBox *groupBox = new QGroupBox(2,
+ Qt::Horizontal,
+ i18n("Librarian"),
+ m_mainFrame);
+ m_mainLayout->addMultiCellWidget(groupBox, 0, 2, 3, 5);
+
+ new QLabel(i18n("Name"), groupBox);
+ m_librarian = new QLabel(groupBox);
+
+ new QLabel(i18n("Email"), groupBox);
+ m_librarianEmail = new QLabel(groupBox);
+
+ QToolTip::add
+ (groupBox,
+ i18n("The librarian maintains the Rosegarden device data for this device.\nIf you've made modifications to suit your own device, it might be worth\nliaising with the librarian in order to publish your information for the benefit\nof others."));
+
+ QTabWidget* tabw = new QTabWidget(this);
+
+ tabw->setMargin(10);
+
+ QHBox *h;
+ QVBox *v;
+ QHBox *numBox;
+
+ unsigned int tabs = 4;
+ unsigned int cols = 2;
+ unsigned int labelId = 0;
+
+ for (unsigned int tab = 0; tab < tabs; ++tab) {
+ h = new QHBox(tabw);
+
+ for (unsigned int col = 0; col < cols; ++col) {
+ v = new QVBox(h);
+
+ for (unsigned int row = 0; row < 128 / (tabs*cols); ++row) {
+ numBox = new QHBox(v);
+ QString numberText = QString("%1").arg(labelId + 1);
+
+ if (tab == 0 && col == 0 && row == 0) {
+ // Initial label; button to adjust whether labels start at 0 or 1
+ m_initialLabel = new QPushButton(numberText, numBox);
+ connect(m_initialLabel,
+ SIGNAL(clicked()),
+ this,
+ SLOT(slotToggleInitialLabel()));
+ } else {
+ QLabel *label = new QLabel(numberText, numBox);
+ label->setFixedWidth(40);
+ label->setAlignment(AlignCenter);
+ m_labels.push_back(label);
+ }
+
+
+
+ if (showEntryButtons) {
+ QPushButton *button = new QPushButton("", numBox, numberText);
+ button->setMaximumWidth(40);
+ button->setMaximumHeight(20);
+ button->setFlat(true);
+ connect(button, SIGNAL(clicked()),
+ this, SLOT(slotEntryButtonPressed()));
+ m_entryButtons.push_back(button);
+ }
+
+ KLineEdit* lineEdit = new KLineEdit(numBox, numberText);
+ lineEdit->setMinimumWidth(110);
+ lineEdit->setCompletionMode(KGlobalSettings::CompletionAuto);
+ lineEdit->setCompletionObject(&m_completion);
+ m_names.push_back(lineEdit);
+
+ connect(m_names[labelId],
+ SIGNAL(textChanged(const QString&)),
+ this,
+ SLOT(slotNameChanged(const QString&)));
+
+ ++labelId;
+ }
+ }
+
+ tabw->addTab(h,
+ (tab == 0 ? headingPrefix + QString(" %1 - %2") :
+ QString("%1 - %2")).
+ arg(tab * (128 / tabs) + 1).
+ arg((tab + 1) * (128 / tabs)));
+ }
+
+ m_initialLabel->setMaximumSize(m_labels.front()->size());
+}
+
+void
+NameSetEditor::slotToggleInitialLabel()
+{
+ QString initial = m_initialLabel->text();
+
+ // strip some unrequested nice-ification.. urg!
+ if (initial.startsWith("&")) {
+ initial = initial.right(initial.length() - 1);
+ }
+
+ bool ok;
+ unsigned index = initial.toUInt(&ok);
+
+ if (!ok) {
+ std::cerr << "conversion of '"
+ << initial.ascii()
+ << "' to number failed"
+ << std::endl;
+ return ;
+ }
+
+ if (index == 0)
+ index = 1;
+ else
+ index = 0;
+
+ m_initialLabel->setText(QString("%1").arg(index++));
+ for (std::vector<QLabel*>::iterator it( m_labels.begin() );
+ it != m_labels.end();
+ ++it) {
+ (*it)->setText(QString("%1").arg(index++));
+ }
+}
+
+}
+#include "NameSetEditor.moc"
diff --git a/src/gui/studio/NameSetEditor.h b/src/gui/studio/NameSetEditor.h
new file mode 100644
index 0000000..e1e1476
--- /dev/null
+++ b/src/gui/studio/NameSetEditor.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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_NAMESETEDITOR_H_
+#define _RG_NAMESETEDITOR_H_
+
+#include <kcompletion.h>
+#include <qstring.h>
+#include <qvgroupbox.h>
+#include <vector>
+
+
+class QWidget;
+class QPushButton;
+class QListViewItem;
+class QLabel;
+class QGridLayout;
+class QFrame;
+class KLineEdit;
+
+
+namespace Rosegarden
+{
+
+class BankEditorDialog;
+
+
+class NameSetEditor : public QVGroupBox
+{
+ Q_OBJECT
+public:
+ virtual void clearAll() = 0;
+
+ virtual void populate(QListViewItem *) = 0;
+ virtual void reset() = 0;
+
+public slots:
+ virtual void slotNameChanged(const QString&) = 0;
+ virtual void slotEntryButtonPressed() = 0;
+ void slotToggleInitialLabel();
+
+protected:
+ NameSetEditor(BankEditorDialog *bankEditor,
+ QString title,
+ QWidget *parent,
+ const char *name,
+ QString headingPrefix = "",
+ bool showEntryButtons = false);
+
+ QPushButton *getEntryButton(int n) { return m_entryButtons[n]; }
+ const QPushButton *getEntryButton(int n) const { return m_entryButtons[n]; }
+
+ QGridLayout *m_mainLayout;
+ BankEditorDialog* m_bankEditor;
+ KCompletion m_completion;
+ QPushButton *m_initialLabel;
+ std::vector<QLabel*> m_labels;
+ std::vector<KLineEdit*> m_names;
+ QFrame *m_mainFrame;
+ QLabel *m_librarian;
+ QLabel *m_librarianEmail;
+ std::vector<QPushButton *> m_entryButtons;
+};
+
+
+}
+
+#endif
diff --git a/src/gui/studio/OSCMessage.cpp b/src/gui/studio/OSCMessage.cpp
new file mode 100644
index 0000000..6496732
--- /dev/null
+++ b/src/gui/studio/OSCMessage.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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#ifdef HAVE_LIBLO
+
+#include "OSCMessage.h"
+
+#include <cstdlib>
+#include <cstring>
+
+namespace Rosegarden
+{
+
+OSCMessage::~OSCMessage()
+{
+ clearArgs();
+}
+
+void
+OSCMessage::clearArgs()
+{
+ while (!m_args.empty()) {
+ free(m_args[0].second);
+ m_args.erase(m_args.begin());
+ }
+}
+
+void
+OSCMessage::addArg(char type, lo_arg *arg)
+{
+ lo_arg *newarg = 0;
+
+ if (type == 's') {
+
+ size_t sz = strlen((char *)arg) + 1;
+ if (sz < sizeof(lo_arg))
+ sz = sizeof(lo_arg);
+ newarg = (lo_arg *)malloc(sz);
+ strcpy((char *)newarg, (char *)arg);
+
+ } else {
+
+ newarg = (lo_arg *)malloc(sizeof(lo_arg));
+ memcpy((char *)newarg, (char *)arg, sizeof(lo_arg));
+ }
+
+ m_args.push_back(OSCArg(type, newarg));
+}
+
+size_t
+OSCMessage::getArgCount() const
+{
+ return m_args.size();
+}
+
+const lo_arg *
+OSCMessage::getArg(size_t i, char &type) const
+{
+ type = m_args[i].first;
+ return m_args[i].second;
+}
+
+}
+
+#endif // HAVE_LIBLO
diff --git a/src/gui/studio/OSCMessage.h b/src/gui/studio/OSCMessage.h
new file mode 100644
index 0000000..05bf63f
--- /dev/null
+++ b/src/gui/studio/OSCMessage.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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_OSCMESSAGE_H_
+#define _RG_OSCMESSAGE_H_
+
+#include <lo/lo.h>
+
+#include <string>
+#include <utility>
+#include <vector>
+
+namespace Rosegarden
+{
+
+
+
+class OSCMessage
+{
+public:
+ OSCMessage() { }
+ ~OSCMessage();
+
+ void setTarget(const int &target) { m_target = target; }
+ int getTarget() const { return m_target; }
+
+ void setTargetData(const int &targetData) { m_targetData = targetData; }
+ int getTargetData() const { return m_targetData; }
+
+ void setMethod(const std::string &method) { m_method = method; }
+ std::string getMethod() const { return m_method; }
+
+ void clearArgs();
+ void addArg(char type, lo_arg *arg);
+
+ size_t getArgCount() const;
+ const lo_arg *getArg(size_t i, char &type) const;
+
+private:
+ int m_target;
+ int m_targetData;
+ std::string m_method;
+ typedef std::pair<char, lo_arg *> OSCArg;
+ std::vector<OSCArg> m_args;
+};
+
+
+class TimerCallbackAssistant;
+
+
+}
+
+#endif
diff --git a/src/gui/studio/RemapInstrumentDialog.cpp b/src/gui/studio/RemapInstrumentDialog.cpp
new file mode 100644
index 0000000..dae43da
--- /dev/null
+++ b/src/gui/studio/RemapInstrumentDialog.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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "RemapInstrumentDialog.h"
+
+#include "misc/Strings.h"
+#include "base/Device.h"
+#include "base/Instrument.h"
+#include "base/MidiDevice.h"
+#include "base/SoftSynthDevice.h"
+#include "base/Studio.h"
+#include "commands/studio/ModifyDeviceMappingCommand.h"
+#include "commands/studio/ModifyInstrumentMappingCommand.h"
+#include "document/MultiViewCommandHistory.h"
+#include "document/RosegardenGUIDoc.h"
+#include <kcombobox.h>
+#include <kcommand.h>
+#include <kdialogbase.h>
+#include <klocale.h>
+#include <qbuttongroup.h>
+#include <qgroupbox.h>
+#include <qlabel.h>
+#include <qradiobutton.h>
+#include <qvbox.h>
+#include <qwidget.h>
+
+
+namespace Rosegarden
+{
+
+RemapInstrumentDialog::RemapInstrumentDialog(QWidget *parent,
+ RosegardenGUIDoc *doc):
+ KDialogBase(parent, "", true, i18n("Remap Instrument assigments..."),
+ Ok | Apply | Cancel),
+ m_doc(doc)
+{
+ QVBox *vBox = makeVBoxMainWidget();
+
+ m_buttonGroup = new QButtonGroup(1, Qt::Horizontal,
+ i18n("Device or Instrument"),
+ vBox);
+
+ new QLabel(i18n("Remap Tracks by all Instruments on a Device or by single Instrument"), m_buttonGroup);
+ m_deviceButton = new QRadioButton(i18n("Device"), m_buttonGroup);
+ m_instrumentButton = new QRadioButton(i18n("Instrument"), m_buttonGroup);
+
+
+ connect(m_buttonGroup, SIGNAL(released(int)),
+ this, SLOT(slotRemapReleased(int)));
+
+ QGroupBox *groupBox = new QGroupBox(2, Qt::Horizontal,
+ i18n("Choose Source and Destination"),
+ vBox);
+
+ new QLabel(i18n("From"), groupBox);
+ new QLabel(i18n("To"), groupBox);
+ m_fromCombo = new KComboBox(groupBox);
+ m_toCombo = new KComboBox(groupBox);
+
+ m_buttonGroup->setButton(0);
+ populateCombo(0);
+}
+
+void
+RemapInstrumentDialog::populateCombo(int id)
+{
+ m_fromCombo->clear();
+ m_toCombo->clear();
+ Studio *studio = &m_doc->getStudio();
+
+ if (id == 0) {
+ DeviceList *devices = studio->getDevices();
+ DeviceListIterator it;
+ m_devices.clear();
+
+ for (it = devices->begin(); it != devices->end(); it++) {
+ MidiDevice *md =
+ dynamic_cast<MidiDevice *>(*it);
+
+ if (md) {
+ if (md->getDirection() == MidiDevice::Play) {
+ m_devices.push_back(*it);
+ m_fromCombo->insertItem(strtoqstr((*it)->getName()));
+ m_toCombo->insertItem(strtoqstr((*it)->getName()));
+ }
+ } else {
+ SoftSynthDevice *sd =
+ dynamic_cast<SoftSynthDevice *>(*it);
+ if (sd) {
+ m_devices.push_back(*it);
+ m_fromCombo->insertItem(strtoqstr((*it)->getName()));
+ m_toCombo->insertItem(strtoqstr((*it)->getName()));
+ }
+ }
+ }
+
+ if (m_devices.size() == 0) {
+ m_fromCombo->insertItem(i18n("<no devices>"));
+ m_toCombo->insertItem(i18n("<no devices>"));
+ }
+ } else {
+ m_instruments = studio->getPresentationInstruments();
+ InstrumentList::iterator it = m_instruments.begin();
+
+ for (; it != m_instruments.end(); it++) {
+ m_fromCombo->insertItem(strtoqstr((*it)->getPresentationName()));
+ m_toCombo->insertItem(strtoqstr((*it)->getPresentationName()));
+ }
+ }
+}
+
+void
+RemapInstrumentDialog::slotRemapReleased(int id)
+{
+ populateCombo(id);
+}
+
+void
+RemapInstrumentDialog::slotOk()
+{
+ slotApply();
+ accept();
+}
+
+void
+RemapInstrumentDialog::slotApply()
+{
+ if (m_buttonGroup->id(m_buttonGroup->selected()) == 0) // devices
+ {
+ ModifyDeviceMappingCommand *command =
+ new ModifyDeviceMappingCommand
+ (m_doc,
+ m_devices[m_fromCombo->currentItem()]->getId(),
+ m_devices[m_toCombo->currentItem()]->getId());
+ addCommandToHistory(command);
+ } else // instruments
+ {
+ ModifyInstrumentMappingCommand *command =
+ new ModifyInstrumentMappingCommand
+ (m_doc,
+ m_instruments[m_fromCombo->currentItem()]->getId(),
+ m_instruments[m_toCombo->currentItem()]->getId());
+ addCommandToHistory(command);
+ }
+
+ emit applyClicked();
+}
+
+void
+RemapInstrumentDialog::addCommandToHistory(KCommand *command)
+{
+ getCommandHistory()->addCommand(command);
+}
+
+MultiViewCommandHistory*
+RemapInstrumentDialog::getCommandHistory()
+{
+ return m_doc->getCommandHistory();
+}
+
+}
+#include "RemapInstrumentDialog.moc"
diff --git a/src/gui/studio/RemapInstrumentDialog.h b/src/gui/studio/RemapInstrumentDialog.h
new file mode 100644
index 0000000..669020e
--- /dev/null
+++ b/src/gui/studio/RemapInstrumentDialog.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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_REMAPINSTRUMENTDIALOG_H_
+#define _RG_REMAPINSTRUMENTDIALOG_H_
+
+#include "base/Studio.h"
+#include <kdialogbase.h>
+
+
+class QWidget;
+class QRadioButton;
+class QButtonGroup;
+class KCommand;
+class KComboBox;
+
+
+namespace Rosegarden
+{
+
+
+class RosegardenGUIDoc;
+class MultiViewCommandHistory;
+
+
+class RemapInstrumentDialog : public KDialogBase
+{
+ Q_OBJECT
+public:
+ RemapInstrumentDialog(QWidget *parent,
+ RosegardenGUIDoc *doc);
+
+ void populateCombo(int id);
+
+ void addCommandToHistory(KCommand *command);
+ MultiViewCommandHistory* getCommandHistory();
+
+public slots:
+ void slotRemapReleased(int id);
+
+ void slotOk();
+ void slotApply();
+
+protected:
+
+ RosegardenGUIDoc *m_doc;
+
+ QRadioButton *m_deviceButton;
+ QRadioButton *m_instrumentButton;
+
+ QButtonGroup *m_buttonGroup;
+ KComboBox *m_fromCombo;
+ KComboBox *m_toCombo;
+
+ DeviceList m_devices;
+ InstrumentList m_instruments;
+};
+
+
+}
+
+#endif
diff --git a/src/gui/studio/StudioControl.cpp b/src/gui/studio/StudioControl.cpp
new file mode 100644
index 0000000..e94016a
--- /dev/null
+++ b/src/gui/studio/StudioControl.cpp
@@ -0,0 +1,582 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
+
+/*
+ Rosegarden
+ A MIDI and audio sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "StudioControl.h"
+
+#include "sound/Midi.h"
+#include "misc/Debug.h"
+#include "base/MidiProgram.h"
+#include "base/Profiler.h"
+#include "base/RealTime.h"
+#include "gui/application/RosegardenApplication.h"
+#include "sound/MappedCommon.h"
+#include "sound/MappedComposition.h"
+#include "sound/MappedEvent.h"
+#include "sound/MappedInstrument.h"
+#include "sound/MappedStudio.h"
+#include <qcstring.h>
+#include <qdatastream.h>
+#include <qstring.h>
+
+
+namespace Rosegarden
+{
+
+MappedObjectId
+StudioControl::createStudioObject(MappedObject::MappedObjectType type)
+{
+Profiler profiler("StudioControl::createStudioObject", true);
+
+int value = -1;
+QByteArray data;
+QCString replyType;
+QByteArray replyData;
+QDataStream streamOut(data, IO_WriteOnly);
+
+streamOut << (int)type;
+
+if (!rgapp->sequencerCall("createMappedObject(int)",
+ replyType, replyData, data))
+{
+ SEQMAN_DEBUG << "createStudioObject - "
+ << "failed to contact Rosegarden sequencer"
+ << endl;
+} else
+{
+ QDataStream streamIn(replyData, IO_ReadOnly);
+ streamIn >> value;
+}
+
+return value;
+}
+
+bool
+StudioControl::destroyStudioObject(MappedObjectId id)
+{
+ Profiler profiler("StudioControl::destroyStudioObject", true);
+
+ int value = 0;
+ QByteArray data;
+ QCString replyType;
+ QByteArray replyData;
+ QDataStream streamOut(data, IO_WriteOnly);
+
+ streamOut << int(id);
+
+ if (!rgapp->sequencerCall("destroyMappedObject(int)",
+ replyType, replyData, data)) {
+ SEQMAN_DEBUG << "destroyStudioObject - "
+ << "failed to contact Rosegarden sequencer"
+ << endl;
+ } else {
+ QDataStream streamIn(replyData, IO_ReadOnly);
+ streamIn >> value;
+ }
+
+ if (value == 1)
+ return true;
+ else
+ return false;
+}
+
+MappedObjectPropertyList
+StudioControl::getStudioObjectProperty(MappedObjectId id,
+ const MappedObjectProperty &property)
+{
+ Profiler profiler("StudioControl::getStudioObjectProperty", true);
+
+ MappedObjectPropertyList list;
+
+ QByteArray data;
+ QCString replyType;
+ QByteArray replyData;
+ QDataStream streamOut(data, IO_WriteOnly);
+
+ streamOut << (int)id;
+ streamOut << QString(property);
+
+ if (!rgapp->sequencerCall("getPropertyList(int, QString)",
+ replyType, replyData, data)) {
+ SEQMAN_DEBUG << "getStudioObjectProperty - "
+ << "failed to contact Rosegarden sequencer"
+ << endl;
+ } else {
+ QDataStream streamIn(replyData, IO_ReadOnly);
+ streamIn >> list;
+ }
+
+ return list;
+}
+
+bool
+StudioControl::setStudioObjectProperty(MappedObjectId id,
+ const MappedObjectProperty &property,
+ MappedObjectValue value)
+{
+ Profiler profiler("StudioControl::setStudioObjectProperty(float)", true);
+
+ QByteArray data;
+ QDataStream streamOut(data, IO_WriteOnly);
+
+ streamOut << (int)id;
+ streamOut << (QString)property;
+ streamOut << (float)value;
+
+ rgapp->sequencerSend("setMappedProperty(int, QString, float)", data);
+
+ return true;
+}
+
+bool
+StudioControl::setStudioObjectProperties(const MappedObjectIdList &ids,
+ const MappedObjectPropertyList &properties,
+ const MappedObjectValueList &values)
+{
+ Profiler profiler("StudioControl::setStudioObjectProperties(floats)", true);
+
+ QByteArray data;
+ QDataStream streamOut(data, IO_WriteOnly);
+
+ streamOut << ids;
+ streamOut << properties;
+ streamOut << values;
+
+ rgapp->sequencerSend("setMappedProperties(MappedObjectIdList, MappedObjectPropertyList, MappedObjectValueList)", data);
+
+ return true;
+}
+
+bool
+StudioControl::setStudioObjectProperty(MappedObjectId id,
+ const MappedObjectProperty &property,
+ const QString &value)
+{
+ Profiler profiler("StudioControl::setStudioObjectProperty(string)", true);
+
+ QByteArray data;
+ QDataStream streamOut(data, IO_WriteOnly);
+
+ streamOut << (int)id;
+ streamOut << (QString)property;
+ streamOut << (QString)value;
+
+ rgapp->sequencerSend("setMappedProperty(int, QString, QString)", data);
+
+ return true;
+}
+
+bool
+StudioControl::setStudioObjectPropertyList(MappedObjectId id,
+ const MappedObjectProperty &property,
+ const MappedObjectPropertyList &values)
+{
+ Profiler profiler("StudioControl::setStudioObjectPropertyList", true);
+
+ QByteArray data;
+ QDataStream streamOut(data, IO_WriteOnly);
+
+ streamOut << (int)id;
+ streamOut << (QString)property;
+ streamOut << values;
+
+ RG_DEBUG << "StudioControl::setStudioObjectPropertyList: " << values.size() << " values for property " << property << endl;
+
+ rgapp->sequencerSend("setMappedPropertyList(int, QString, MappedObjectPropertyList)",
+ data);
+
+ return true;
+}
+
+MappedObjectId
+StudioControl::getStudioObjectByType(MappedObject::MappedObjectType type)
+{
+ Profiler profiler("StudioControl::getStudioObjectByType", true);
+
+ int value = -1;
+ QByteArray data;
+ QCString replyType;
+ QByteArray replyData;
+ QDataStream streamOut(data, IO_WriteOnly);
+
+ streamOut << (int)type;
+
+ if (!rgapp->sequencerCall("getMappedObjectId(int)",
+ replyType, replyData, data)) {
+ SEQMAN_DEBUG << "getStudioObjectByType - "
+ << "failed to contact Rosegarden sequencer"
+ << endl;
+ } else {
+ QDataStream streamIn(replyData, IO_ReadOnly);
+ streamIn >> value;
+ }
+
+ return value;
+}
+
+void
+StudioControl::setStudioPluginPort(MappedObjectId pluginId,
+ unsigned long portId,
+ MappedObjectValue value)
+{
+ Profiler profiler("StudioControl::setStudioPluginPort", true);
+
+ QByteArray data;
+ QDataStream streamOut(data, IO_WriteOnly);
+
+ // Use new MappedEvent interface
+ //
+ streamOut << (int)pluginId;
+ streamOut << (unsigned long)portId;
+ streamOut << (float)value;
+
+ rgapp->sequencerSend("setMappedPort(int, unsigned long int, float)", data);
+}
+
+MappedObjectValue
+StudioControl::getStudioPluginPort(MappedObjectId pluginId,
+ unsigned long portId)
+{
+ Profiler profiler("StudioControl::getStudioPluginPort", true);
+
+ float value = 0.0;
+ QByteArray data;
+ QCString replyType;
+ QByteArray replyData;
+ QDataStream streamOut(data, IO_WriteOnly);
+
+ streamOut << (int)pluginId;
+ streamOut << (unsigned long)portId;
+
+ if (!rgapp->sequencerCall("getMappedPort(int, unsigned long int)",
+ replyType, replyData, data)) {
+ SEQMAN_DEBUG << "getStudioPluginPort - "
+ << "failed to contact Rosegarden sequencer"
+ << endl;
+ } else {
+ QDataStream streamIn(replyData, IO_ReadOnly);
+ streamIn >> value;
+ }
+
+ return value;
+}
+
+MappedObjectPropertyList
+StudioControl::getPluginInformation()
+{
+ MappedObjectPropertyList list;
+
+ QByteArray data;
+ QCString replyType;
+ QByteArray replyData;
+
+ if (!rgapp->sequencerCall("getPluginInformation()",
+ replyType, replyData, data)) {
+ SEQMAN_DEBUG << "getPluginInformation - "
+ << "failed to contact Rosegarden sequencer"
+ << endl;
+ } else {
+ QDataStream streamIn(replyData, IO_ReadOnly);
+ streamIn >> list;
+ }
+
+ return list;
+}
+
+QString
+StudioControl::getPluginProgram(MappedObjectId id, int bank, int program)
+{
+ QByteArray data;
+ QCString replyType;
+ QByteArray replyData;
+ QDataStream streamOut(data, IO_WriteOnly);
+
+ streamOut << (int)id;
+ streamOut << (int)bank;
+ streamOut << (int)program;
+
+ QString programName;
+
+ if (!rgapp->sequencerCall("getPluginProgram(int, int, int)",
+ replyType, replyData, data)) {
+ SEQMAN_DEBUG << "getPluginProgram - "
+ << "failed to contact Rosegarden sequencer"
+ << endl;
+ } else {
+ QDataStream streamIn(replyData, IO_ReadOnly);
+ streamIn >> programName;
+ }
+
+ return programName;
+}
+
+unsigned long
+StudioControl::getPluginProgram(MappedObjectId id, QString name)
+{
+ QByteArray data;
+ QCString replyType;
+ QByteArray replyData;
+ QDataStream streamOut(data, IO_WriteOnly);
+
+ streamOut << (int)id;
+ streamOut << name;
+
+ unsigned long rv;
+
+ if (!rgapp->sequencerCall("getPluginProgram(int, QString)",
+ replyType, replyData, data)) {
+ SEQMAN_DEBUG << "getPluginProgram - "
+ << "failed to contact Rosegarden sequencer"
+ << endl;
+ } else {
+ QDataStream streamIn(replyData, IO_ReadOnly);
+ streamIn >> rv;
+ }
+
+ return rv;
+}
+
+void
+StudioControl::connectStudioObjects(MappedObjectId id1,
+ MappedObjectId id2)
+{
+ Profiler profiler("StudioControl::connectStudioObjects", true);
+
+ QByteArray data;
+ QDataStream streamOut(data, IO_WriteOnly);
+
+ streamOut << (int)id1;
+ streamOut << (int)id2;
+
+ if (!rgapp->sequencerSend("connectMappedObjects(int, int)", data)) {
+ SEQMAN_DEBUG << "connectStudioObjects - "
+ << "failed to contact Rosegarden sequencer"
+ << endl;
+ }
+
+ return ;
+}
+
+void
+StudioControl::disconnectStudioObjects(MappedObjectId id1,
+ MappedObjectId id2)
+{
+ Profiler profiler("StudioControl::disconnectStudioObjects", true);
+
+ QByteArray data;
+ QDataStream streamOut(data, IO_WriteOnly);
+
+ streamOut << (int)id1;
+ streamOut << (int)id2;
+
+ if (!rgapp->sequencerSend("disconnectMappedObjects(int, int)", data)) {
+ SEQMAN_DEBUG << "disconnectStudioObjects - "
+ << "failed to contact Rosegarden sequencer"
+ << endl;
+ }
+
+ return ;
+}
+
+void
+StudioControl::disconnectStudioObject(MappedObjectId id)
+{
+ Profiler profiler("StudioControl::disconnectStudioObject", true);
+
+ QByteArray data;
+ QDataStream streamOut(data, IO_WriteOnly);
+
+ streamOut << (int)id;
+
+ if (!rgapp->sequencerSend("disconnectMappedObject(int)", data)) {
+ SEQMAN_DEBUG << "disconnectStudioObject - "
+ << "failed to contact Rosegarden sequencer"
+ << endl;
+ }
+
+ return ;
+}
+
+void
+StudioControl::sendMappedEvent(const MappedEvent &mE)
+{
+ Profiler profiler("StudioControl::sendMappedEvent", true);
+
+ static MappedEvent mEs;
+
+ mEs = mE; // just in case the passed mapped event has dubious
+ // origins and taking its address isn't safe
+
+ mEs.setPersistent(true); // to avoid that MappedComposition dtor try to free it
+
+ MappedComposition mC;
+ mC.insert(&mEs);
+ StudioControl::sendMappedComposition(mC);
+}
+
+void
+StudioControl::sendMappedComposition(const MappedComposition &mC)
+{
+ Profiler profiler("StudioControl::sendMappedComposition", true);
+
+ if (mC.size() == 0)
+ return ;
+
+ QCString replyType;
+ QByteArray replyData;
+
+ MappedComposition::const_iterator it = mC.begin();
+
+ for (; it != mC.end(); it++) {
+ QByteArray data;
+ QDataStream streamOut(data, IO_WriteOnly);
+
+ streamOut << (*it);
+ rgapp->sequencerSend("processMappedEvent(MappedEvent)", data);
+ }
+}
+
+void
+StudioControl::sendMappedInstrument(const MappedInstrument &mI)
+{
+ Profiler profiler("StudioControl::sendMappedInstrument", true);
+
+ QByteArray data;
+ QDataStream streamOut(data, IO_WriteOnly);
+
+ streamOut << (int)mI.getType();
+ streamOut << (unsigned char)mI.getChannel();
+ streamOut << (unsigned int)mI.getId();
+
+ rgapp->sequencerSend("setMappedInstrument(int, unsigned char, unsigned int)", data);
+}
+
+void
+StudioControl::sendQuarterNoteLength(const RealTime &length)
+{
+ Profiler profiler("StudioControl::sendQuarterNoteLength", true);
+
+ QByteArray data;
+ QDataStream streamOut(data, IO_WriteOnly);
+
+ streamOut << (long)length.sec;
+ streamOut << (long)length.nsec;
+
+ rgapp->sequencerSend("setQuarterNoteLength(long int, long int)", data);
+}
+
+void
+StudioControl::sendRPN(InstrumentId instrumentId,
+ MidiByte paramMSB,
+ MidiByte paramLSB,
+ MidiByte /* controller */,
+ MidiByte value)
+{
+ Profiler profiler("StudioControl::sendRPN", true);
+
+ MappedComposition mC;
+ MappedEvent *mE =
+ new MappedEvent(instrumentId,
+ MappedEvent::MidiController,
+ MIDI_CONTROLLER_RPN_2,
+ paramMSB);
+ mC.insert(mE);
+
+ mE = new MappedEvent(instrumentId,
+ MappedEvent::MidiController,
+ MIDI_CONTROLLER_RPN_1,
+ paramLSB);
+ mC.insert(mE);
+
+ mE = new MappedEvent(instrumentId,
+ MappedEvent::MidiController,
+ 6, // data value changed
+ value);
+ mC.insert(mE);
+
+
+ // Null the controller using - this is "best practice"
+ //
+ mE = new MappedEvent(instrumentId,
+ MappedEvent::MidiController,
+ MIDI_CONTROLLER_RPN_2,
+ MidiMaxValue); // null
+ mC.insert(mE);
+
+ mE = new MappedEvent(instrumentId,
+ MappedEvent::MidiController,
+ MIDI_CONTROLLER_RPN_1,
+ MidiMaxValue); // null
+ mC.insert(mE);
+
+
+ StudioControl::sendMappedComposition(mC);
+}
+
+void
+StudioControl::sendNRPN(InstrumentId instrumentId,
+ MidiByte paramMSB,
+ MidiByte paramLSB,
+ MidiByte /* controller */,
+ MidiByte value)
+{
+ Profiler profiler("StudioControl::sendNRPN", true);
+
+ MappedComposition mC;
+ MappedEvent *mE =
+ new MappedEvent(instrumentId,
+ MappedEvent::MidiController,
+ MIDI_CONTROLLER_NRPN_2,
+ paramMSB);
+ mC.insert(mE);
+
+ mE = new MappedEvent(instrumentId,
+ MappedEvent::MidiController,
+ MIDI_CONTROLLER_NRPN_1,
+ paramLSB);
+ mC.insert(mE);
+
+ mE = new MappedEvent(instrumentId,
+ MappedEvent::MidiController,
+ 6, // data value changed
+ value);
+ mC.insert(mE);
+
+
+ // Null the controller using - this is "best practice"
+ //
+ mE = new MappedEvent(instrumentId,
+ MappedEvent::MidiController,
+ MIDI_CONTROLLER_RPN_2,
+ MidiMaxValue); // null
+ mC.insert(mE);
+
+ mE = new MappedEvent(instrumentId,
+ MappedEvent::MidiController,
+ MIDI_CONTROLLER_RPN_1,
+ MidiMaxValue); // null
+ mC.insert(mE);
+}
+
+}
diff --git a/src/gui/studio/StudioControl.h b/src/gui/studio/StudioControl.h
new file mode 100644
index 0000000..cf05d44
--- /dev/null
+++ b/src/gui/studio/StudioControl.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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_STUDIOCONTROL_H_
+#define _RG_STUDIOCONTROL_H_
+
+#include "base/MidiProgram.h"
+#include "sound/MappedCommon.h"
+#include "sound/MappedStudio.h"
+#include <qstring.h>
+
+
+class MappedObjectValueList;
+class MappedObjectIdList;
+
+
+namespace Rosegarden
+{
+
+class RealTime;
+class MappedInstrument;
+class MappedEvent;
+class MappedComposition;
+
+typedef std::pair<Rosegarden::MidiByte, Rosegarden::MidiByte> MidiControlPair;
+
+class StudioControl
+{
+public:
+
+ // Object management
+ //
+ static MappedObjectId
+ createStudioObject(MappedObject::MappedObjectType type);
+ static MappedObjectId
+ getStudioObjectByType(MappedObject::MappedObjectType type);
+ static bool destroyStudioObject(MappedObjectId id);
+
+ // Properties
+ //
+ static MappedObjectPropertyList
+ getStudioObjectProperty(MappedObjectId id,
+ const MappedObjectProperty &property);
+
+ // Set a value to a value
+ //
+ static bool setStudioObjectProperty(MappedObjectId id,
+ const MappedObjectProperty &property,
+ MappedObjectValue value);
+
+ // Set many values to values
+ //
+ static bool setStudioObjectProperties(const MappedObjectIdList &ids,
+ const MappedObjectPropertyList &properties,
+ const MappedObjectValueList &values);
+
+ // Set a value to a string
+ //
+ static bool setStudioObjectProperty(MappedObjectId id,
+ const MappedObjectProperty &property,
+ const QString &value);
+
+ // Set a value to a string list
+ //
+ static bool setStudioObjectPropertyList(MappedObjectId id,
+ const MappedObjectProperty &property,
+ const MappedObjectPropertyList &values);
+
+ static void setStudioPluginPort(MappedObjectId pluginId,
+ unsigned long portId,
+ MappedObjectValue value);
+
+ static MappedObjectValue getStudioPluginPort(MappedObjectId pluginId,
+ unsigned long portId);
+
+ // Get all plugin information
+ //
+ static MappedObjectPropertyList getPluginInformation();
+
+ // Get program name for a given program
+ //
+ static QString getPluginProgram(MappedObjectId, int bank, int program);
+
+ // Get program numbers for a given name (rv is bank << 16 + program)
+ // This is one of the nastiest hacks in the whole application
+ //
+ static unsigned long getPluginProgram(MappedObjectId, QString name);
+
+ // Connection
+ //
+ static void connectStudioObjects(MappedObjectId id1,
+ MappedObjectId id2);
+ static void disconnectStudioObjects(MappedObjectId id1,
+ MappedObjectId id2);
+ static void disconnectStudioObject(MappedObjectId id);
+
+ // Send controllers and other one off MIDI events using these
+ // interfaces.
+ //
+ static void sendMappedEvent(const MappedEvent& mE);
+ static void sendMappedComposition(const MappedComposition &mC);
+
+ // MappedInstrument
+ //
+ static void sendMappedInstrument(const MappedInstrument &mI);
+
+ // Send the Quarter Note Length has changed to the sequencer
+ //
+ static void sendQuarterNoteLength(const RealTime &length);
+
+ // Convenience wrappers for RPNs and NRPNs
+ //
+ static void sendRPN(InstrumentId instrumentId,
+ MidiByte paramMSB,
+ MidiByte paramLSB,
+ MidiByte controller,
+ MidiByte value);
+
+ static void sendNRPN(InstrumentId instrumentId,
+ MidiByte paramMSB,
+ MidiByte paramLSB,
+ MidiByte controller,
+ MidiByte value);
+};
+
+
+
+}
+
+#endif
diff --git a/src/gui/studio/SynthPluginManagerDialog.cpp b/src/gui/studio/SynthPluginManagerDialog.cpp
new file mode 100644
index 0000000..f9a54ea
--- /dev/null
+++ b/src/gui/studio/SynthPluginManagerDialog.cpp
@@ -0,0 +1,360 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
+
+/*
+ Rosegarden
+ A MIDI and audio sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "SynthPluginManagerDialog.h"
+#include <qlayout.h>
+
+#include <klocale.h>
+#include "misc/Debug.h"
+#include "AudioPlugin.h"
+#include "AudioPluginManager.h"
+#include "AudioPluginOSCGUIManager.h"
+#include "base/AudioPluginInstance.h"
+#include "base/Instrument.h"
+#include "base/MidiProgram.h"
+#include "base/Studio.h"
+#include "document/RosegardenGUIDoc.h"
+#include "document/ConfigGroups.h"
+#include <kaction.h>
+#include <kcombobox.h>
+#include <kmainwindow.h>
+#include <kstdaction.h>
+#include <qframe.h>
+#include <qgroupbox.h>
+#include <qlabel.h>
+#include <qobject.h>
+#include <qpushbutton.h>
+#include <qsizepolicy.h>
+#include <qstring.h>
+#include <qwidget.h>
+
+
+namespace Rosegarden
+{
+
+SynthPluginManagerDialog::SynthPluginManagerDialog(QWidget *parent,
+ RosegardenGUIDoc *doc
+#ifdef HAVE_LIBLO
+ , AudioPluginOSCGUIManager *guiManager
+#endif
+ ) :
+ KMainWindow(parent, "synthpluginmanagerdialog"),
+ m_document(doc),
+ m_studio(&doc->getStudio()),
+ m_pluginManager(doc->getPluginManager())
+#ifdef HAVE_LIBLO
+ , m_guiManager(guiManager)
+#endif
+ {
+ setCaption(i18n("Manage Synth Plugins"));
+
+ QFrame *mainBox = new QFrame(this);
+ setCentralWidget(mainBox);
+
+ QVBoxLayout *mainLayout = new QVBoxLayout(mainBox, 10, 10);
+
+ QGroupBox *groupBox = new QGroupBox(1, Horizontal, i18n("Synth plugins"), mainBox);
+ mainLayout->addWidget(groupBox);
+
+ QFrame *pluginFrame = new QFrame(groupBox);
+ QGridLayout *pluginLayout = new QGridLayout(pluginFrame, 1, 4, 3, 3);
+
+ m_synthPlugins.clear();
+ m_synthPlugins.push_back( -1);
+
+ int count = 0;
+
+ for (PluginIterator itr = m_pluginManager->begin();
+ itr != m_pluginManager->end(); ++itr) {
+
+ if ((*itr)->isSynth()) {
+ m_synthPlugins.push_back(count);
+ }
+
+ ++count;
+ }
+
+ for (int i = 0; i < SoftSynthInstrumentCount; ++i) {
+
+ InstrumentId id = SoftSynthInstrumentBase + i;
+ Instrument *instrument = m_studio->getInstrumentById(id);
+ if (!instrument)
+ continue;
+
+ // pluginLayout->addWidget(new QLabel(instrument->getPresentationName().c_str(),
+ // pluginFrame), i, 0);
+ pluginLayout->addWidget(new QLabel(QString("%1").arg(i + 1),
+ pluginFrame), i, 0);
+
+ AudioPluginInstance *plugin = instrument->getPlugin
+ (Instrument::SYNTH_PLUGIN_POSITION);
+
+ std::string identifier;
+ if (plugin)
+ identifier = plugin->getIdentifier();
+
+ int currentItem = 0;
+
+ KComboBox *pluginCombo = new KComboBox(pluginFrame);
+ pluginCombo->insertItem(i18n("<none>"));
+
+ for (size_t j = 0; j < m_synthPlugins.size(); ++j) {
+
+ if (m_synthPlugins[j] == -1)
+ continue;
+
+ AudioPlugin *plugin =
+ m_pluginManager->getPlugin(m_synthPlugins[j]);
+
+ pluginCombo->insertItem(plugin->getName());
+
+ if (plugin->getIdentifier() == identifier.c_str()) {
+ pluginCombo->setCurrentItem(pluginCombo->count() - 1);
+ }
+ }
+
+ connect(pluginCombo, SIGNAL(activated(int)),
+ this, SLOT(slotPluginChanged(int)));
+
+ pluginLayout->addWidget(pluginCombo, i, 1);
+
+ m_synthCombos.push_back(pluginCombo);
+
+ QPushButton *controlsButton = new QPushButton(i18n("Controls"), pluginFrame);
+ pluginLayout->addWidget(controlsButton, i, 2);
+ connect(controlsButton, SIGNAL(clicked()), this, SLOT(slotControlsButtonClicked()));
+ m_controlsButtons.push_back(controlsButton);
+
+#ifdef HAVE_LIBLO
+
+ QPushButton *guiButton = new QPushButton(i18n("Editor >>"), pluginFrame);
+ pluginLayout->addWidget(guiButton, i, 3);
+ guiButton->setEnabled(m_guiManager->hasGUI
+ (id, Instrument::SYNTH_PLUGIN_POSITION));
+ connect(guiButton, SIGNAL(clicked()), this, SLOT(slotGUIButtonClicked()));
+ m_guiButtons.push_back(guiButton);
+#endif
+
+ }
+
+ QFrame* btnBox = new QFrame(mainBox);
+
+ btnBox->setSizePolicy(
+ QSizePolicy(QSizePolicy::Minimum, QSizePolicy::Fixed));
+
+ QPushButton *closeButton = new QPushButton(i18n("Close"), btnBox);
+
+ QHBoxLayout* layout = new QHBoxLayout(btnBox, 0, 10);
+ layout->addStretch(10);
+ layout->addWidget(closeButton);
+ layout->addSpacing(5);
+
+ KAction* close = KStdAction::close(this,
+ SLOT(slotClose()),
+ actionCollection());
+
+ closeButton->setText(close->text());
+ connect(closeButton, SIGNAL(clicked()), this, SLOT(slotClose()));
+
+ mainLayout->addWidget(btnBox);
+
+ createGUI("synthpluginmanager.rc");
+
+ setAutoSaveSettings(SynthPluginManagerConfigGroup, true);
+ }
+
+ SynthPluginManagerDialog::~SynthPluginManagerDialog()
+ {
+ RG_DEBUG << "\n*** SynthPluginManagerDialog::~SynthPluginManagerDialog()"
+ << endl;
+ }
+
+ void
+ SynthPluginManagerDialog::updatePlugin(InstrumentId id, int plugin)
+ {
+ if (id < SoftSynthInstrumentBase)
+ return ;
+ int row = id - SoftSynthInstrumentBase;
+ if (row >= m_synthCombos.size())
+ return ;
+
+ KComboBox *comboBox = m_synthCombos[row];
+
+ for (unsigned int i = 0; i < m_synthPlugins.size(); ++i) {
+ if (m_synthPlugins[i] == plugin) {
+ blockSignals(true);
+ comboBox->setCurrentItem(i);
+ blockSignals(false);
+ return ;
+ }
+ }
+
+ blockSignals(true);
+ comboBox->setCurrentItem(0);
+ blockSignals(false);
+ return ;
+ }
+
+ void
+ SynthPluginManagerDialog::slotClose()
+ {
+ close();
+ }
+
+ void
+ SynthPluginManagerDialog::closeEvent(QCloseEvent *e)
+ {
+ emit closing();
+ KMainWindow::closeEvent(e);
+ }
+
+ void
+ SynthPluginManagerDialog::slotGUIButtonClicked()
+ {
+ const QObject *s = sender();
+
+ int instrumentNo = -1;
+
+ for (unsigned int i = 0; i < m_guiButtons.size(); ++i) {
+ if (s == m_guiButtons[i])
+ instrumentNo = i;
+ }
+
+ if (instrumentNo == -1) {
+ RG_DEBUG << "WARNING: SynthPluginManagerDialog::slotGUIButtonClicked: unknown sender" << endl;
+ return ;
+ }
+
+ InstrumentId id = SoftSynthInstrumentBase + instrumentNo;
+
+ emit showPluginGUI(id, Instrument::SYNTH_PLUGIN_POSITION);
+ }
+
+ void
+ SynthPluginManagerDialog::slotControlsButtonClicked()
+ {
+ const QObject *s = sender();
+
+ int instrumentNo = -1;
+
+ for (unsigned int i = 0; i < m_controlsButtons.size(); ++i) {
+ if (s == m_controlsButtons[i])
+ instrumentNo = i;
+ }
+
+ if (instrumentNo == -1) {
+ RG_DEBUG << "WARNING: SynthPluginManagerDialog::slotControlsButtonClicked: unknown sender" << endl;
+ return ;
+ }
+
+ InstrumentId id = SoftSynthInstrumentBase + instrumentNo;
+
+ emit showPluginDialog(this, id, Instrument::SYNTH_PLUGIN_POSITION);
+ }
+
+ void
+ SynthPluginManagerDialog::slotPluginChanged(int index)
+ {
+ const QObject *s = sender();
+
+ RG_DEBUG << "SynthPluginManagerDialog::slotPluginChanged(" << index
+ << ")" << endl;
+
+ int instrumentNo = -1;
+
+ for (unsigned int i = 0; i < m_synthCombos.size(); ++i) {
+ if (s == m_synthCombos[i])
+ instrumentNo = i;
+ }
+
+ if (instrumentNo == -1) {
+ RG_DEBUG << "WARNING: SynthPluginManagerDialog::slotValueChanged: unknown sender" << endl;
+ return ;
+ }
+
+ InstrumentId id = SoftSynthInstrumentBase + instrumentNo;
+
+ if (index >= int(m_synthPlugins.size())) {
+ RG_DEBUG << "WARNING: SynthPluginManagerDialog::slotValueChanged: synth "
+ << index << " out of range" << endl;
+ return ;
+ }
+
+ // NB m_synthPlugins[0] is -1 to represent the <none> item
+
+ AudioPlugin *plugin = m_pluginManager->getPlugin(m_synthPlugins[index]);
+ Instrument *instrument = m_studio->getInstrumentById(id);
+
+ if (instrument) {
+
+ AudioPluginInstance *pluginInstance = instrument->getPlugin
+ (Instrument::SYNTH_PLUGIN_POSITION);
+
+ if (pluginInstance) {
+
+ if (plugin) {
+ RG_DEBUG << "plugin is " << plugin->getIdentifier() << endl;
+ pluginInstance->setIdentifier(plugin->getIdentifier().data());
+
+ // set ports to defaults
+
+ AudioPlugin::PortIterator it = plugin->begin();
+ int count = 0;
+
+ for (; it != plugin->end(); ++it) {
+
+ if (((*it)->getType() & PluginPort::Control) &&
+ ((*it)->getType() & PluginPort::Input)) {
+
+ if (pluginInstance->getPort(count) == 0) {
+ pluginInstance->addPort(count, (float)(*it)->getDefaultValue());
+ } else {
+ pluginInstance->getPort(count)->value = (*it)->getDefaultValue();
+ }
+ }
+
+ ++count;
+ }
+
+ } else {
+ pluginInstance->setIdentifier("");
+ }
+ }
+ }
+
+#ifdef HAVE_LIBLO
+ if (instrumentNo < m_guiButtons.size()) {
+ m_guiButtons[instrumentNo]->setEnabled
+ (m_guiManager->hasGUI
+ (id, Instrument::SYNTH_PLUGIN_POSITION));
+ }
+#endif
+
+ emit pluginSelected(id, Instrument::SYNTH_PLUGIN_POSITION,
+ m_synthPlugins[index]);
+ }
+
+ }
+#include "SynthPluginManagerDialog.moc"
diff --git a/src/gui/studio/SynthPluginManagerDialog.h b/src/gui/studio/SynthPluginManagerDialog.h
new file mode 100644
index 0000000..b66a338
--- /dev/null
+++ b/src/gui/studio/SynthPluginManagerDialog.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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_SYNTHPLUGINMANAGERDIALOG_H_
+#define _RG_SYNTHPLUGINMANAGERDIALOG_H_
+
+#include "base/MidiProgram.h"
+#include <kmainwindow.h>
+#include <vector>
+
+
+class QWidget;
+class QPushButton;
+class QCloseEvent;
+class KComboBox;
+
+
+namespace Rosegarden
+{
+
+class Studio;
+class RosegardenGUIDoc;
+class AudioPluginOSCGUIManager;
+class AudioPluginManager;
+
+
+class SynthPluginManagerDialog : public KMainWindow
+{
+ Q_OBJECT
+
+public:
+ SynthPluginManagerDialog(QWidget *parent,
+ RosegardenGUIDoc *doc
+#ifdef HAVE_LIBLO
+ , AudioPluginOSCGUIManager *guiManager
+#endif
+ );
+
+ virtual ~SynthPluginManagerDialog();
+
+ void updatePlugin(InstrumentId id, int plugin);
+
+signals:
+ void closing();
+ void pluginSelected(InstrumentId, int pluginIndex, int plugin);
+ void showPluginDialog(QWidget *, InstrumentId, int pluginIndex);
+ void showPluginGUI(InstrumentId, int pluginIndex);
+
+protected slots:
+ void slotClose();
+ void slotPluginChanged(int index);
+ void slotControlsButtonClicked();
+ void slotGUIButtonClicked();
+
+protected:
+ virtual void closeEvent(QCloseEvent *);
+
+protected:
+ RosegardenGUIDoc *m_document;
+ Studio *m_studio;
+ AudioPluginManager *m_pluginManager;
+ std::vector<int> m_synthPlugins;
+ std::vector<KComboBox *> m_synthCombos;
+ std::vector<QPushButton *> m_controlsButtons;
+ std::vector<QPushButton *> m_guiButtons;
+
+#ifdef HAVE_LIBLO
+ AudioPluginOSCGUIManager *m_guiManager;
+#endif
+};
+
+
+
+}
+
+#endif
diff --git a/src/gui/studio/TimerCallbackAssistant.cpp b/src/gui/studio/TimerCallbackAssistant.cpp
new file mode 100644
index 0000000..ec8518c
--- /dev/null
+++ b/src/gui/studio/TimerCallbackAssistant.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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "TimerCallbackAssistant.h"
+
+#include <qobject.h>
+#include <qtimer.h>
+
+
+namespace Rosegarden
+{
+
+TimerCallbackAssistant::TimerCallbackAssistant(int ms, void (*callback)(void *data),
+ void *data) :
+ m_callback(callback),
+ m_data(data)
+{
+ QTimer *timer = new QTimer(this);
+ connect(timer, SIGNAL(timeout()), this, SLOT(slotCallback()));
+ timer->start(ms, FALSE);
+}
+
+TimerCallbackAssistant::~TimerCallbackAssistant()
+{
+ // nothing -- the QTimer is deleted automatically by its parent QObject (me)
+}
+
+void
+TimerCallbackAssistant::slotCallback()
+{
+ m_callback(m_data);
+}
+
+}
+#include "TimerCallbackAssistant.moc"
diff --git a/src/gui/studio/TimerCallbackAssistant.h b/src/gui/studio/TimerCallbackAssistant.h
new file mode 100644
index 0000000..2a8e353
--- /dev/null
+++ b/src/gui/studio/TimerCallbackAssistant.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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_TIMERCALLBACKASSISTANT_H_
+#define _RG_TIMERCALLBACKASSISTANT_H_
+
+#include <qobject.h>
+
+
+
+
+namespace Rosegarden
+{
+
+
+/* This assistant class is here simply to work around the fact that
+ AudioPluginOSCGUI cannot be a QObject because it's only
+ conditionally compiled. */
+
+class TimerCallbackAssistant : public QObject
+{
+ Q_OBJECT
+
+public:
+ TimerCallbackAssistant(int ms, void (*callback)(void *data), void *data);
+ virtual ~TimerCallbackAssistant();
+
+protected slots:
+ void slotCallback();
+
+private:
+ void (*m_callback)(void *);
+ void *m_data;
+};
+
+
+}
+
+#endif
diff --git a/src/gui/ui/RosegardenTransport.ui b/src/gui/ui/RosegardenTransport.ui
new file mode 100644
index 0000000..347b30b
--- /dev/null
+++ b/src/gui/ui/RosegardenTransport.ui
@@ -0,0 +1,4361 @@
+<!DOCTYPE UI><UI version="3.3" stdsetdef="1">
+<class>RosegardenTransport</class>
+<widget class="QFrame">
+ <property name="name">
+ <cstring>RosegardenTransport</cstring>
+ </property>
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>417</width>
+ <height>134</height>
+ </rect>
+ </property>
+ <property name="caption">
+ <string>Rosegarden Transport</string>
+ </property>
+ <property name="frameShape">
+ <enum>NoFrame</enum>
+ </property>
+ <property name="frameShadow">
+ <enum>Plain</enum>
+ </property>
+ <widget class="QFrame">
+ <property name="name">
+ <cstring>RecordingFrame</cstring>
+ </property>
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>87</y>
+ <width>416</width>
+ <height>47</height>
+ </rect>
+ </property>
+ <property name="frameShape">
+ <enum>StyledPanel</enum>
+ </property>
+ <property name="frameShadow">
+ <enum>Raised</enum>
+ </property>
+ <widget class="QPushButton">
+ <property name="name">
+ <cstring>PanelCloseButton</cstring>
+ </property>
+ <property name="geometry">
+ <rect>
+ <x>3</x>
+ <y>4</y>
+ <width>25</width>
+ <height>23</height>
+ </rect>
+ </property>
+ <property name="font">
+ <font>
+ <family>lucida</family>
+ <bold>1</bold>
+ </font>
+ </property>
+ <property name="text">
+ <string></string>
+ </property>
+ <property name="pixmap">
+ <pixmap>image0</pixmap>
+ </property>
+ <property name="flat">
+ <bool>true</bool>
+ </property>
+ <property name="toolTip" stdset="0">
+ <string>Hide additional controls</string>
+ </property>
+ </widget>
+ <widget class="QPushButton">
+ <property name="name">
+ <cstring>PanicButton</cstring>
+ </property>
+ <property name="geometry">
+ <rect>
+ <x>174</x>
+ <y>4</y>
+ <width>42</width>
+ <height>38</height>
+ </rect>
+ </property>
+ <property name="font">
+ <font>
+ <family>lucidatypewriter</family>
+ <pointsize>8</pointsize>
+ </font>
+ </property>
+ <property name="cursor">
+ <cursor>0</cursor>
+ </property>
+ <property name="focusPolicy">
+ <enum>NoFocus</enum>
+ </property>
+ <property name="text">
+ <string></string>
+ </property>
+ <property name="pixmap">
+ <pixmap>image1</pixmap>
+ </property>
+ <property name="toggleButton">
+ <bool>false</bool>
+ </property>
+ <property name="toolTip" stdset="0">
+ <string>Panic Button</string>
+ </property>
+ <property name="whatsThis" stdset="0">
+ <string>Resets all MIDI devices if you've got stuck notes</string>
+ </property>
+ </widget>
+ <widget class="QPushButton">
+ <property name="name">
+ <cstring>MetronomeButton</cstring>
+ </property>
+ <property name="geometry">
+ <rect>
+ <x>217</x>
+ <y>4</y>
+ <width>42</width>
+ <height>38</height>
+ </rect>
+ </property>
+ <property name="font">
+ <font>
+ <family>lucidatypewriter</family>
+ <pointsize>8</pointsize>
+ </font>
+ </property>
+ <property name="cursor">
+ <cursor>0</cursor>
+ </property>
+ <property name="focusPolicy">
+ <enum>NoFocus</enum>
+ </property>
+ <property name="text">
+ <string></string>
+ </property>
+ <property name="pixmap">
+ <pixmap>image2</pixmap>
+ </property>
+ <property name="toggleButton">
+ <bool>true</bool>
+ </property>
+ <property name="toolTip" stdset="0">
+ <string>Metronome</string>
+ </property>
+ <property name="whatsThis" stdset="0">
+ <string>Provides a metronome click for you to play along with</string>
+ </property>
+ </widget>
+ <widget class="QFrame">
+ <property name="name">
+ <cstring>MidiEventFrame</cstring>
+ </property>
+ <property name="geometry">
+ <rect>
+ <x>31</x>
+ <y>7</y>
+ <width>137</width>
+ <height>34</height>
+ </rect>
+ </property>
+ <property name="palette">
+ <palette>
+ <active>
+ <color>
+ <red>0</red>
+ <green>0</green>
+ <blue>0</blue>
+ </color>
+ <color>
+ <red>192</red>
+ <green>192</green>
+ <blue>192</blue>
+ </color>
+ <color>
+ <red>255</red>
+ <green>255</green>
+ <blue>255</blue>
+ </color>
+ <color>
+ <red>223</red>
+ <green>223</green>
+ <blue>223</blue>
+ </color>
+ <color>
+ <red>96</red>
+ <green>96</green>
+ <blue>96</blue>
+ </color>
+ <color>
+ <red>128</red>
+ <green>128</green>
+ <blue>128</blue>
+ </color>
+ <color>
+ <red>0</red>
+ <green>0</green>
+ <blue>0</blue>
+ </color>
+ <color>
+ <red>255</red>
+ <green>255</green>
+ <blue>255</blue>
+ </color>
+ <color>
+ <red>0</red>
+ <green>0</green>
+ <blue>0</blue>
+ </color>
+ <color>
+ <red>255</red>
+ <green>255</green>
+ <blue>255</blue>
+ </color>
+ <color>
+ <red>0</red>
+ <green>0</green>
+ <blue>0</blue>
+ </color>
+ <color>
+ <red>0</red>
+ <green>0</green>
+ <blue>0</blue>
+ </color>
+ <color>
+ <red>0</red>
+ <green>0</green>
+ <blue>0</blue>
+ </color>
+ <color>
+ <red>255</red>
+ <green>255</green>
+ <blue>255</blue>
+ </color>
+ <color>
+ <red>0</red>
+ <green>0</green>
+ <blue>0</blue>
+ </color>
+ <color>
+ <red>0</red>
+ <green>0</green>
+ <blue>0</blue>
+ </color>
+ </active>
+ <disabled>
+ <color>
+ <red>128</red>
+ <green>128</green>
+ <blue>128</blue>
+ </color>
+ <color>
+ <red>192</red>
+ <green>192</green>
+ <blue>192</blue>
+ </color>
+ <color>
+ <red>255</red>
+ <green>255</green>
+ <blue>255</blue>
+ </color>
+ <color>
+ <red>220</red>
+ <green>220</green>
+ <blue>220</blue>
+ </color>
+ <color>
+ <red>96</red>
+ <green>96</green>
+ <blue>96</blue>
+ </color>
+ <color>
+ <red>128</red>
+ <green>128</green>
+ <blue>128</blue>
+ </color>
+ <color>
+ <red>0</red>
+ <green>0</green>
+ <blue>0</blue>
+ </color>
+ <color>
+ <red>255</red>
+ <green>255</green>
+ <blue>255</blue>
+ </color>
+ <color>
+ <red>128</red>
+ <green>128</green>
+ <blue>128</blue>
+ </color>
+ <color>
+ <red>255</red>
+ <green>255</green>
+ <blue>255</blue>
+ </color>
+ <color>
+ <red>0</red>
+ <green>0</green>
+ <blue>0</blue>
+ </color>
+ <color>
+ <red>0</red>
+ <green>0</green>
+ <blue>0</blue>
+ </color>
+ <color>
+ <red>0</red>
+ <green>0</green>
+ <blue>0</blue>
+ </color>
+ <color>
+ <red>255</red>
+ <green>255</green>
+ <blue>255</blue>
+ </color>
+ <color>
+ <red>0</red>
+ <green>0</green>
+ <blue>0</blue>
+ </color>
+ <color>
+ <red>0</red>
+ <green>0</green>
+ <blue>0</blue>
+ </color>
+ </disabled>
+ <inactive>
+ <color>
+ <red>0</red>
+ <green>0</green>
+ <blue>0</blue>
+ </color>
+ <color>
+ <red>192</red>
+ <green>192</green>
+ <blue>192</blue>
+ </color>
+ <color>
+ <red>255</red>
+ <green>255</green>
+ <blue>255</blue>
+ </color>
+ <color>
+ <red>220</red>
+ <green>220</green>
+ <blue>220</blue>
+ </color>
+ <color>
+ <red>96</red>
+ <green>96</green>
+ <blue>96</blue>
+ </color>
+ <color>
+ <red>128</red>
+ <green>128</green>
+ <blue>128</blue>
+ </color>
+ <color>
+ <red>0</red>
+ <green>0</green>
+ <blue>0</blue>
+ </color>
+ <color>
+ <red>255</red>
+ <green>255</green>
+ <blue>255</blue>
+ </color>
+ <color>
+ <red>0</red>
+ <green>0</green>
+ <blue>0</blue>
+ </color>
+ <color>
+ <red>255</red>
+ <green>255</green>
+ <blue>255</blue>
+ </color>
+ <color>
+ <red>0</red>
+ <green>0</green>
+ <blue>0</blue>
+ </color>
+ <color>
+ <red>0</red>
+ <green>0</green>
+ <blue>0</blue>
+ </color>
+ <color>
+ <red>0</red>
+ <green>0</green>
+ <blue>0</blue>
+ </color>
+ <color>
+ <red>255</red>
+ <green>255</green>
+ <blue>255</blue>
+ </color>
+ <color>
+ <red>0</red>
+ <green>0</green>
+ <blue>0</blue>
+ </color>
+ <color>
+ <red>0</red>
+ <green>0</green>
+ <blue>0</blue>
+ </color>
+ </inactive>
+ </palette>
+ </property>
+ <property name="frameShape">
+ <enum>StyledPanel</enum>
+ </property>
+ <property name="frameShadow">
+ <enum>Sunken</enum>
+ </property>
+ <property name="whatsThis" stdset="0">
+ <string>Shows MIDI activity in and out of Rosegarden</string>
+ </property>
+ <widget class="QLabel">
+ <property name="name">
+ <cstring>InLabel</cstring>
+ </property>
+ <property name="geometry">
+ <rect>
+ <x>18</x>
+ <y>1</y>
+ <width>25</width>
+ <height>16</height>
+ </rect>
+ </property>
+ <property name="palette">
+ <palette>
+ <active>
+ <color>
+ <red>192</red>
+ <green>216</green>
+ <blue>255</blue>
+ </color>
+ <color>
+ <red>192</red>
+ <green>192</green>
+ <blue>192</blue>
+ </color>
+ <color>
+ <red>255</red>
+ <green>255</green>
+ <blue>255</blue>
+ </color>
+ <color>
+ <red>223</red>
+ <green>223</green>
+ <blue>223</blue>
+ </color>
+ <color>
+ <red>96</red>
+ <green>96</green>
+ <blue>96</blue>
+ </color>
+ <color>
+ <red>128</red>
+ <green>128</green>
+ <blue>128</blue>
+ </color>
+ <color>
+ <red>0</red>
+ <green>0</green>
+ <blue>0</blue>
+ </color>
+ <color>
+ <red>255</red>
+ <green>255</green>
+ <blue>255</blue>
+ </color>
+ <color>
+ <red>0</red>
+ <green>0</green>
+ <blue>0</blue>
+ </color>
+ <color>
+ <red>255</red>
+ <green>255</green>
+ <blue>255</blue>
+ </color>
+ <color>
+ <red>0</red>
+ <green>0</green>
+ <blue>0</blue>
+ </color>
+ <color>
+ <red>0</red>
+ <green>0</green>
+ <blue>0</blue>
+ </color>
+ <color>
+ <red>0</red>
+ <green>0</green>
+ <blue>0</blue>
+ </color>
+ <color>
+ <red>255</red>
+ <green>255</green>
+ <blue>255</blue>
+ </color>
+ <color>
+ <red>0</red>
+ <green>0</green>
+ <blue>0</blue>
+ </color>
+ <color>
+ <red>0</red>
+ <green>0</green>
+ <blue>0</blue>
+ </color>
+ </active>
+ <disabled>
+ <color>
+ <red>128</red>
+ <green>128</green>
+ <blue>128</blue>
+ </color>
+ <color>
+ <red>192</red>
+ <green>192</green>
+ <blue>192</blue>
+ </color>
+ <color>
+ <red>255</red>
+ <green>255</green>
+ <blue>255</blue>
+ </color>
+ <color>
+ <red>220</red>
+ <green>220</green>
+ <blue>220</blue>
+ </color>
+ <color>
+ <red>96</red>
+ <green>96</green>
+ <blue>96</blue>
+ </color>
+ <color>
+ <red>128</red>
+ <green>128</green>
+ <blue>128</blue>
+ </color>
+ <color>
+ <red>0</red>
+ <green>0</green>
+ <blue>0</blue>
+ </color>
+ <color>
+ <red>255</red>
+ <green>255</green>
+ <blue>255</blue>
+ </color>
+ <color>
+ <red>128</red>
+ <green>128</green>
+ <blue>128</blue>
+ </color>
+ <color>
+ <red>255</red>
+ <green>255</green>
+ <blue>255</blue>
+ </color>
+ <color>
+ <red>0</red>
+ <green>0</green>
+ <blue>0</blue>
+ </color>
+ <color>
+ <red>0</red>
+ <green>0</green>
+ <blue>0</blue>
+ </color>
+ <color>
+ <red>0</red>
+ <green>0</green>
+ <blue>0</blue>
+ </color>
+ <color>
+ <red>255</red>
+ <green>255</green>
+ <blue>255</blue>
+ </color>
+ <color>
+ <red>0</red>
+ <green>0</green>
+ <blue>0</blue>
+ </color>
+ <color>
+ <red>0</red>
+ <green>0</green>
+ <blue>0</blue>
+ </color>
+ </disabled>
+ <inactive>
+ <color>
+ <red>192</red>
+ <green>216</green>
+ <blue>255</blue>
+ </color>
+ <color>
+ <red>192</red>
+ <green>192</green>
+ <blue>192</blue>
+ </color>
+ <color>
+ <red>255</red>
+ <green>255</green>
+ <blue>255</blue>
+ </color>
+ <color>
+ <red>220</red>
+ <green>220</green>
+ <blue>220</blue>
+ </color>
+ <color>
+ <red>96</red>
+ <green>96</green>
+ <blue>96</blue>
+ </color>
+ <color>
+ <red>128</red>
+ <green>128</green>
+ <blue>128</blue>
+ </color>
+ <color>
+ <red>0</red>
+ <green>0</green>
+ <blue>0</blue>
+ </color>
+ <color>
+ <red>255</red>
+ <green>255</green>
+ <blue>255</blue>
+ </color>
+ <color>
+ <red>0</red>
+ <green>0</green>
+ <blue>0</blue>
+ </color>
+ <color>
+ <red>255</red>
+ <green>255</green>
+ <blue>255</blue>
+ </color>
+ <color>
+ <red>0</red>
+ <green>0</green>
+ <blue>0</blue>
+ </color>
+ <color>
+ <red>0</red>
+ <green>0</green>
+ <blue>0</blue>
+ </color>
+ <color>
+ <red>0</red>
+ <green>0</green>
+ <blue>0</blue>
+ </color>
+ <color>
+ <red>255</red>
+ <green>255</green>
+ <blue>255</blue>
+ </color>
+ <color>
+ <red>0</red>
+ <green>0</green>
+ <blue>0</blue>
+ </color>
+ <color>
+ <red>0</red>
+ <green>0</green>
+ <blue>0</blue>
+ </color>
+ </inactive>
+ </palette>
+ </property>
+ <property name="font">
+ <font>
+ <family>lucida</family>
+ <italic>1</italic>
+ </font>
+ </property>
+ <property name="text">
+ <string>IN</string>
+ </property>
+ </widget>
+ <widget class="QLabel">
+ <property name="name">
+ <cstring>OutLabel</cstring>
+ </property>
+ <property name="geometry">
+ <rect>
+ <x>8</x>
+ <y>15</y>
+ <width>25</width>
+ <height>16</height>
+ </rect>
+ </property>
+ <property name="palette">
+ <palette>
+ <active>
+ <color>
+ <red>192</red>
+ <green>216</green>
+ <blue>255</blue>
+ </color>
+ <color>
+ <red>192</red>
+ <green>192</green>
+ <blue>192</blue>
+ </color>
+ <color>
+ <red>255</red>
+ <green>255</green>
+ <blue>255</blue>
+ </color>
+ <color>
+ <red>223</red>
+ <green>223</green>
+ <blue>223</blue>
+ </color>
+ <color>
+ <red>96</red>
+ <green>96</green>
+ <blue>96</blue>
+ </color>
+ <color>
+ <red>128</red>
+ <green>128</green>
+ <blue>128</blue>
+ </color>
+ <color>
+ <red>0</red>
+ <green>0</green>
+ <blue>0</blue>
+ </color>
+ <color>
+ <red>255</red>
+ <green>255</green>
+ <blue>255</blue>
+ </color>
+ <color>
+ <red>0</red>
+ <green>0</green>
+ <blue>0</blue>
+ </color>
+ <color>
+ <red>255</red>
+ <green>255</green>
+ <blue>255</blue>
+ </color>
+ <color>
+ <red>0</red>
+ <green>0</green>
+ <blue>0</blue>
+ </color>
+ <color>
+ <red>0</red>
+ <green>0</green>
+ <blue>0</blue>
+ </color>
+ <color>
+ <red>0</red>
+ <green>0</green>
+ <blue>0</blue>
+ </color>
+ <color>
+ <red>255</red>
+ <green>255</green>
+ <blue>255</blue>
+ </color>
+ <color>
+ <red>0</red>
+ <green>0</green>
+ <blue>0</blue>
+ </color>
+ <color>
+ <red>0</red>
+ <green>0</green>
+ <blue>0</blue>
+ </color>
+ </active>
+ <disabled>
+ <color>
+ <red>128</red>
+ <green>128</green>
+ <blue>128</blue>
+ </color>
+ <color>
+ <red>192</red>
+ <green>192</green>
+ <blue>192</blue>
+ </color>
+ <color>
+ <red>255</red>
+ <green>255</green>
+ <blue>255</blue>
+ </color>
+ <color>
+ <red>220</red>
+ <green>220</green>
+ <blue>220</blue>
+ </color>
+ <color>
+ <red>96</red>
+ <green>96</green>
+ <blue>96</blue>
+ </color>
+ <color>
+ <red>128</red>
+ <green>128</green>
+ <blue>128</blue>
+ </color>
+ <color>
+ <red>0</red>
+ <green>0</green>
+ <blue>0</blue>
+ </color>
+ <color>
+ <red>255</red>
+ <green>255</green>
+ <blue>255</blue>
+ </color>
+ <color>
+ <red>128</red>
+ <green>128</green>
+ <blue>128</blue>
+ </color>
+ <color>
+ <red>255</red>
+ <green>255</green>
+ <blue>255</blue>
+ </color>
+ <color>
+ <red>0</red>
+ <green>0</green>
+ <blue>0</blue>
+ </color>
+ <color>
+ <red>0</red>
+ <green>0</green>
+ <blue>0</blue>
+ </color>
+ <color>
+ <red>0</red>
+ <green>0</green>
+ <blue>0</blue>
+ </color>
+ <color>
+ <red>255</red>
+ <green>255</green>
+ <blue>255</blue>
+ </color>
+ <color>
+ <red>0</red>
+ <green>0</green>
+ <blue>0</blue>
+ </color>
+ <color>
+ <red>0</red>
+ <green>0</green>
+ <blue>0</blue>
+ </color>
+ </disabled>
+ <inactive>
+ <color>
+ <red>192</red>
+ <green>216</green>
+ <blue>255</blue>
+ </color>
+ <color>
+ <red>192</red>
+ <green>192</green>
+ <blue>192</blue>
+ </color>
+ <color>
+ <red>255</red>
+ <green>255</green>
+ <blue>255</blue>
+ </color>
+ <color>
+ <red>220</red>
+ <green>220</green>
+ <blue>220</blue>
+ </color>
+ <color>
+ <red>96</red>
+ <green>96</green>
+ <blue>96</blue>
+ </color>
+ <color>
+ <red>128</red>
+ <green>128</green>
+ <blue>128</blue>
+ </color>
+ <color>
+ <red>0</red>
+ <green>0</green>
+ <blue>0</blue>
+ </color>
+ <color>
+ <red>255</red>
+ <green>255</green>
+ <blue>255</blue>
+ </color>
+ <color>
+ <red>0</red>
+ <green>0</green>
+ <blue>0</blue>
+ </color>
+ <color>
+ <red>255</red>
+ <green>255</green>
+ <blue>255</blue>
+ </color>
+ <color>
+ <red>0</red>
+ <green>0</green>
+ <blue>0</blue>
+ </color>
+ <color>
+ <red>0</red>
+ <green>0</green>
+ <blue>0</blue>
+ </color>
+ <color>
+ <red>0</red>
+ <green>0</green>
+ <blue>0</blue>
+ </color>
+ <color>
+ <red>255</red>
+ <green>255</green>
+ <blue>255</blue>
+ </color>
+ <color>
+ <red>0</red>
+ <green>0</green>
+ <blue>0</blue>
+ </color>
+ <color>
+ <red>0</red>
+ <green>0</green>
+ <blue>0</blue>
+ </color>
+ </inactive>
+ </palette>
+ </property>
+ <property name="font">
+ <font>
+ <family>lucida</family>
+ <italic>1</italic>
+ </font>
+ </property>
+ <property name="text">
+ <string>OUT</string>
+ </property>
+ </widget>
+ <widget class="QLabel">
+ <property name="name">
+ <cstring>InDisplay</cstring>
+ </property>
+ <property name="geometry">
+ <rect>
+ <x>40</x>
+ <y>1</y>
+ <width>82</width>
+ <height>16</height>
+ </rect>
+ </property>
+ <property name="palette">
+ <palette>
+ <active>
+ <color>
+ <red>192</red>
+ <green>216</green>
+ <blue>255</blue>
+ </color>
+ <color>
+ <red>192</red>
+ <green>192</green>
+ <blue>192</blue>
+ </color>
+ <color>
+ <red>255</red>
+ <green>255</green>
+ <blue>255</blue>
+ </color>
+ <color>
+ <red>223</red>
+ <green>223</green>
+ <blue>223</blue>
+ </color>
+ <color>
+ <red>96</red>
+ <green>96</green>
+ <blue>96</blue>
+ </color>
+ <color>
+ <red>128</red>
+ <green>128</green>
+ <blue>128</blue>
+ </color>
+ <color>
+ <red>0</red>
+ <green>0</green>
+ <blue>0</blue>
+ </color>
+ <color>
+ <red>255</red>
+ <green>255</green>
+ <blue>255</blue>
+ </color>
+ <color>
+ <red>0</red>
+ <green>0</green>
+ <blue>0</blue>
+ </color>
+ <color>
+ <red>255</red>
+ <green>255</green>
+ <blue>255</blue>
+ </color>
+ <color>
+ <red>0</red>
+ <green>0</green>
+ <blue>0</blue>
+ </color>
+ <color>
+ <red>0</red>
+ <green>0</green>
+ <blue>0</blue>
+ </color>
+ <color>
+ <red>0</red>
+ <green>0</green>
+ <blue>0</blue>
+ </color>
+ <color>
+ <red>255</red>
+ <green>255</green>
+ <blue>255</blue>
+ </color>
+ <color>
+ <red>0</red>
+ <green>0</green>
+ <blue>0</blue>
+ </color>
+ <color>
+ <red>0</red>
+ <green>0</green>
+ <blue>0</blue>
+ </color>
+ </active>
+ <disabled>
+ <color>
+ <red>128</red>
+ <green>128</green>
+ <blue>128</blue>
+ </color>
+ <color>
+ <red>192</red>
+ <green>192</green>
+ <blue>192</blue>
+ </color>
+ <color>
+ <red>255</red>
+ <green>255</green>
+ <blue>255</blue>
+ </color>
+ <color>
+ <red>220</red>
+ <green>220</green>
+ <blue>220</blue>
+ </color>
+ <color>
+ <red>96</red>
+ <green>96</green>
+ <blue>96</blue>
+ </color>
+ <color>
+ <red>128</red>
+ <green>128</green>
+ <blue>128</blue>
+ </color>
+ <color>
+ <red>0</red>
+ <green>0</green>
+ <blue>0</blue>
+ </color>
+ <color>
+ <red>255</red>
+ <green>255</green>
+ <blue>255</blue>
+ </color>
+ <color>
+ <red>128</red>
+ <green>128</green>
+ <blue>128</blue>
+ </color>
+ <color>
+ <red>255</red>
+ <green>255</green>
+ <blue>255</blue>
+ </color>
+ <color>
+ <red>0</red>
+ <green>0</green>
+ <blue>0</blue>
+ </color>
+ <color>
+ <red>0</red>
+ <green>0</green>
+ <blue>0</blue>
+ </color>
+ <color>
+ <red>0</red>
+ <green>0</green>
+ <blue>0</blue>
+ </color>
+ <color>
+ <red>255</red>
+ <green>255</green>
+ <blue>255</blue>
+ </color>
+ <color>
+ <red>0</red>
+ <green>0</green>
+ <blue>0</blue>
+ </color>
+ <color>
+ <red>0</red>
+ <green>0</green>
+ <blue>0</blue>
+ </color>
+ </disabled>
+ <inactive>
+ <color>
+ <red>192</red>
+ <green>216</green>
+ <blue>255</blue>
+ </color>
+ <color>
+ <red>192</red>
+ <green>192</green>
+ <blue>192</blue>
+ </color>
+ <color>
+ <red>255</red>
+ <green>255</green>
+ <blue>255</blue>
+ </color>
+ <color>
+ <red>220</red>
+ <green>220</green>
+ <blue>220</blue>
+ </color>
+ <color>
+ <red>96</red>
+ <green>96</green>
+ <blue>96</blue>
+ </color>
+ <color>
+ <red>128</red>
+ <green>128</green>
+ <blue>128</blue>
+ </color>
+ <color>
+ <red>0</red>
+ <green>0</green>
+ <blue>0</blue>
+ </color>
+ <color>
+ <red>255</red>
+ <green>255</green>
+ <blue>255</blue>
+ </color>
+ <color>
+ <red>0</red>
+ <green>0</green>
+ <blue>0</blue>
+ </color>
+ <color>
+ <red>255</red>
+ <green>255</green>
+ <blue>255</blue>
+ </color>
+ <color>
+ <red>0</red>
+ <green>0</green>
+ <blue>0</blue>
+ </color>
+ <color>
+ <red>0</red>
+ <green>0</green>
+ <blue>0</blue>
+ </color>
+ <color>
+ <red>0</red>
+ <green>0</green>
+ <blue>0</blue>
+ </color>
+ <color>
+ <red>255</red>
+ <green>255</green>
+ <blue>255</blue>
+ </color>
+ <color>
+ <red>0</red>
+ <green>0</green>
+ <blue>0</blue>
+ </color>
+ <color>
+ <red>0</red>
+ <green>0</green>
+ <blue>0</blue>
+ </color>
+ </inactive>
+ </palette>
+ </property>
+ <property name="font">
+ <font>
+ <family>lucida</family>
+ <bold>1</bold>
+ </font>
+ </property>
+ <property name="text">
+ <string>NO EVENTS</string>
+ </property>
+ </widget>
+ <widget class="QLabel">
+ <property name="name">
+ <cstring>OutDisplay</cstring>
+ </property>
+ <property name="geometry">
+ <rect>
+ <x>40</x>
+ <y>15</y>
+ <width>82</width>
+ <height>16</height>
+ </rect>
+ </property>
+ <property name="palette">
+ <palette>
+ <active>
+ <color>
+ <red>192</red>
+ <green>216</green>
+ <blue>255</blue>
+ </color>
+ <color>
+ <red>192</red>
+ <green>192</green>
+ <blue>192</blue>
+ </color>
+ <color>
+ <red>255</red>
+ <green>255</green>
+ <blue>255</blue>
+ </color>
+ <color>
+ <red>223</red>
+ <green>223</green>
+ <blue>223</blue>
+ </color>
+ <color>
+ <red>96</red>
+ <green>96</green>
+ <blue>96</blue>
+ </color>
+ <color>
+ <red>128</red>
+ <green>128</green>
+ <blue>128</blue>
+ </color>
+ <color>
+ <red>0</red>
+ <green>0</green>
+ <blue>0</blue>
+ </color>
+ <color>
+ <red>255</red>
+ <green>255</green>
+ <blue>255</blue>
+ </color>
+ <color>
+ <red>0</red>
+ <green>0</green>
+ <blue>0</blue>
+ </color>
+ <color>
+ <red>255</red>
+ <green>255</green>
+ <blue>255</blue>
+ </color>
+ <color>
+ <red>0</red>
+ <green>0</green>
+ <blue>0</blue>
+ </color>
+ <color>
+ <red>0</red>
+ <green>0</green>
+ <blue>0</blue>
+ </color>
+ <color>
+ <red>0</red>
+ <green>0</green>
+ <blue>0</blue>
+ </color>
+ <color>
+ <red>255</red>
+ <green>255</green>
+ <blue>255</blue>
+ </color>
+ <color>
+ <red>0</red>
+ <green>0</green>
+ <blue>0</blue>
+ </color>
+ <color>
+ <red>0</red>
+ <green>0</green>
+ <blue>0</blue>
+ </color>
+ </active>
+ <disabled>
+ <color>
+ <red>128</red>
+ <green>128</green>
+ <blue>128</blue>
+ </color>
+ <color>
+ <red>192</red>
+ <green>192</green>
+ <blue>192</blue>
+ </color>
+ <color>
+ <red>255</red>
+ <green>255</green>
+ <blue>255</blue>
+ </color>
+ <color>
+ <red>220</red>
+ <green>220</green>
+ <blue>220</blue>
+ </color>
+ <color>
+ <red>96</red>
+ <green>96</green>
+ <blue>96</blue>
+ </color>
+ <color>
+ <red>128</red>
+ <green>128</green>
+ <blue>128</blue>
+ </color>
+ <color>
+ <red>0</red>
+ <green>0</green>
+ <blue>0</blue>
+ </color>
+ <color>
+ <red>255</red>
+ <green>255</green>
+ <blue>255</blue>
+ </color>
+ <color>
+ <red>128</red>
+ <green>128</green>
+ <blue>128</blue>
+ </color>
+ <color>
+ <red>255</red>
+ <green>255</green>
+ <blue>255</blue>
+ </color>
+ <color>
+ <red>0</red>
+ <green>0</green>
+ <blue>0</blue>
+ </color>
+ <color>
+ <red>0</red>
+ <green>0</green>
+ <blue>0</blue>
+ </color>
+ <color>
+ <red>0</red>
+ <green>0</green>
+ <blue>0</blue>
+ </color>
+ <color>
+ <red>255</red>
+ <green>255</green>
+ <blue>255</blue>
+ </color>
+ <color>
+ <red>0</red>
+ <green>0</green>
+ <blue>0</blue>
+ </color>
+ <color>
+ <red>0</red>
+ <green>0</green>
+ <blue>0</blue>
+ </color>
+ </disabled>
+ <inactive>
+ <color>
+ <red>192</red>
+ <green>216</green>
+ <blue>255</blue>
+ </color>
+ <color>
+ <red>192</red>
+ <green>192</green>
+ <blue>192</blue>
+ </color>
+ <color>
+ <red>255</red>
+ <green>255</green>
+ <blue>255</blue>
+ </color>
+ <color>
+ <red>220</red>
+ <green>220</green>
+ <blue>220</blue>
+ </color>
+ <color>
+ <red>96</red>
+ <green>96</green>
+ <blue>96</blue>
+ </color>
+ <color>
+ <red>128</red>
+ <green>128</green>
+ <blue>128</blue>
+ </color>
+ <color>
+ <red>0</red>
+ <green>0</green>
+ <blue>0</blue>
+ </color>
+ <color>
+ <red>255</red>
+ <green>255</green>
+ <blue>255</blue>
+ </color>
+ <color>
+ <red>0</red>
+ <green>0</green>
+ <blue>0</blue>
+ </color>
+ <color>
+ <red>255</red>
+ <green>255</green>
+ <blue>255</blue>
+ </color>
+ <color>
+ <red>0</red>
+ <green>0</green>
+ <blue>0</blue>
+ </color>
+ <color>
+ <red>0</red>
+ <green>0</green>
+ <blue>0</blue>
+ </color>
+ <color>
+ <red>0</red>
+ <green>0</green>
+ <blue>0</blue>
+ </color>
+ <color>
+ <red>255</red>
+ <green>255</green>
+ <blue>255</blue>
+ </color>
+ <color>
+ <red>0</red>
+ <green>0</green>
+ <blue>0</blue>
+ </color>
+ <color>
+ <red>0</red>
+ <green>0</green>
+ <blue>0</blue>
+ </color>
+ </inactive>
+ </palette>
+ </property>
+ <property name="font">
+ <font>
+ <family>lucida</family>
+ <bold>1</bold>
+ </font>
+ </property>
+ <property name="text">
+ <string>NO EVENTS</string>
+ </property>
+ </widget>
+ </widget>
+ <widget class="QPushButton">
+ <property name="name">
+ <cstring>RecordButton</cstring>
+ </property>
+ <property name="geometry">
+ <rect>
+ <x>370</x>
+ <y>4</y>
+ <width>42</width>
+ <height>38</height>
+ </rect>
+ </property>
+ <property name="cursor">
+ <cursor>0</cursor>
+ </property>
+ <property name="focusPolicy">
+ <enum>NoFocus</enum>
+ </property>
+ <property name="text">
+ <string></string>
+ </property>
+ <property name="pixmap">
+ <pixmap>image3</pixmap>
+ </property>
+ <property name="toggleButton">
+ <bool>true</bool>
+ </property>
+ <property name="toolTip" stdset="0">
+ <string>Record</string>
+ </property>
+ <property name="whatsThis" stdset="0">
+ <string>Record either MIDI or audio</string>
+ </property>
+ </widget>
+ <widget class="QPushButton">
+ <property name="name">
+ <cstring>LoopButton</cstring>
+ </property>
+ <property name="geometry">
+ <rect>
+ <x>320</x>
+ <y>4</y>
+ <width>32</width>
+ <height>38</height>
+ </rect>
+ </property>
+ <property name="font">
+ <font>
+ <family>lucidatypewriter</family>
+ <pointsize>8</pointsize>
+ </font>
+ </property>
+ <property name="cursor">
+ <cursor>0</cursor>
+ </property>
+ <property name="focusPolicy">
+ <enum>NoFocus</enum>
+ </property>
+ <property name="text">
+ <string></string>
+ </property>
+ <property name="pixmap">
+ <pixmap>image4</pixmap>
+ </property>
+ <property name="toggleButton">
+ <bool>true</bool>
+ </property>
+ <property name="toolTip" stdset="0">
+ <string>Loop</string>
+ </property>
+ <property name="whatsThis" stdset="0">
+ <string>Turn on and off the loop markers (if set)</string>
+ </property>
+ </widget>
+ <widget class="QPushButton">
+ <property name="name">
+ <cstring>SoloButton</cstring>
+ </property>
+ <property name="geometry">
+ <rect>
+ <x>260</x>
+ <y>4</y>
+ <width>42</width>
+ <height>38</height>
+ </rect>
+ </property>
+ <property name="font">
+ <font>
+ <family>lucidatypewriter</family>
+ <pointsize>8</pointsize>
+ </font>
+ </property>
+ <property name="cursor">
+ <cursor>0</cursor>
+ </property>
+ <property name="focusPolicy">
+ <enum>NoFocus</enum>
+ </property>
+ <property name="text">
+ <string></string>
+ </property>
+ <property name="pixmap">
+ <pixmap>image5</pixmap>
+ </property>
+ <property name="toggleButton">
+ <bool>true</bool>
+ </property>
+ <property name="toolTip" stdset="0">
+ <string>Solo</string>
+ </property>
+ <property name="whatsThis" stdset="0">
+ <string>Mutes all but the currently selected track</string>
+ </property>
+ </widget>
+ <widget class="QPushButton">
+ <property name="name">
+ <cstring>SetStartLPButton</cstring>
+ </property>
+ <property name="geometry">
+ <rect>
+ <x>303</x>
+ <y>4</y>
+ <width>16</width>
+ <height>38</height>
+ </rect>
+ </property>
+ <property name="text">
+ <string></string>
+ </property>
+ <property name="pixmap">
+ <pixmap>image6</pixmap>
+ </property>
+ <property name="toolTip" stdset="0">
+ <string>Start loop or range here</string>
+ </property>
+ </widget>
+ <widget class="QPushButton">
+ <property name="name">
+ <cstring>SetStopLPButton</cstring>
+ </property>
+ <property name="geometry">
+ <rect>
+ <x>353</x>
+ <y>4</y>
+ <width>16</width>
+ <height>38</height>
+ </rect>
+ </property>
+ <property name="text">
+ <string></string>
+ </property>
+ <property name="pixmap">
+ <pixmap>image7</pixmap>
+ </property>
+ <property name="toolTip" stdset="0">
+ <string>End loop or range here</string>
+ </property>
+ </widget>
+ </widget>
+ <widget class="QFrame">
+ <property name="name">
+ <cstring>MainFrame</cstring>
+ </property>
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>416</width>
+ <height>87</height>
+ </rect>
+ </property>
+ <property name="frameShape">
+ <enum>StyledPanel</enum>
+ </property>
+ <property name="frameShadow">
+ <enum>Raised</enum>
+ </property>
+ <widget class="QFrame">
+ <property name="name">
+ <cstring>LCDBoxFrame</cstring>
+ </property>
+ <property name="geometry">
+ <rect>
+ <x>31</x>
+ <y>7</y>
+ <width>225</width>
+ <height>71</height>
+ </rect>
+ </property>
+ <property name="palette">
+ <palette>
+ <active>
+ <color>
+ <red>0</red>
+ <green>0</green>
+ <blue>0</blue>
+ </color>
+ <color>
+ <red>192</red>
+ <green>192</green>
+ <blue>192</blue>
+ </color>
+ <color>
+ <red>255</red>
+ <green>255</green>
+ <blue>255</blue>
+ </color>
+ <color>
+ <red>223</red>
+ <green>223</green>
+ <blue>223</blue>
+ </color>
+ <color>
+ <red>96</red>
+ <green>96</green>
+ <blue>96</blue>
+ </color>
+ <color>
+ <red>128</red>
+ <green>128</green>
+ <blue>128</blue>
+ </color>
+ <color>
+ <red>0</red>
+ <green>0</green>
+ <blue>0</blue>
+ </color>
+ <color>
+ <red>255</red>
+ <green>255</green>
+ <blue>255</blue>
+ </color>
+ <color>
+ <red>0</red>
+ <green>0</green>
+ <blue>0</blue>
+ </color>
+ <color>
+ <red>255</red>
+ <green>255</green>
+ <blue>255</blue>
+ </color>
+ <color>
+ <red>0</red>
+ <green>0</green>
+ <blue>0</blue>
+ </color>
+ <color>
+ <red>0</red>
+ <green>0</green>
+ <blue>0</blue>
+ </color>
+ <color>
+ <red>0</red>
+ <green>0</green>
+ <blue>0</blue>
+ </color>
+ <color>
+ <red>255</red>
+ <green>255</green>
+ <blue>255</blue>
+ </color>
+ <color>
+ <red>0</red>
+ <green>0</green>
+ <blue>0</blue>
+ </color>
+ <color>
+ <red>0</red>
+ <green>0</green>
+ <blue>0</blue>
+ </color>
+ </active>
+ <disabled>
+ <color>
+ <red>128</red>
+ <green>128</green>
+ <blue>128</blue>
+ </color>
+ <color>
+ <red>192</red>
+ <green>192</green>
+ <blue>192</blue>
+ </color>
+ <color>
+ <red>255</red>
+ <green>255</green>
+ <blue>255</blue>
+ </color>
+ <color>
+ <red>220</red>
+ <green>220</green>
+ <blue>220</blue>
+ </color>
+ <color>
+ <red>96</red>
+ <green>96</green>
+ <blue>96</blue>
+ </color>
+ <color>
+ <red>128</red>
+ <green>128</green>
+ <blue>128</blue>
+ </color>
+ <color>
+ <red>0</red>
+ <green>0</green>
+ <blue>0</blue>
+ </color>
+ <color>
+ <red>255</red>
+ <green>255</green>
+ <blue>255</blue>
+ </color>
+ <color>
+ <red>128</red>
+ <green>128</green>
+ <blue>128</blue>
+ </color>
+ <color>
+ <red>255</red>
+ <green>255</green>
+ <blue>255</blue>
+ </color>
+ <color>
+ <red>0</red>
+ <green>0</green>
+ <blue>0</blue>
+ </color>
+ <color>
+ <red>0</red>
+ <green>0</green>
+ <blue>0</blue>
+ </color>
+ <color>
+ <red>0</red>
+ <green>0</green>
+ <blue>0</blue>
+ </color>
+ <color>
+ <red>255</red>
+ <green>255</green>
+ <blue>255</blue>
+ </color>
+ <color>
+ <red>0</red>
+ <green>0</green>
+ <blue>0</blue>
+ </color>
+ <color>
+ <red>0</red>
+ <green>0</green>
+ <blue>0</blue>
+ </color>
+ </disabled>
+ <inactive>
+ <color>
+ <red>0</red>
+ <green>0</green>
+ <blue>0</blue>
+ </color>
+ <color>
+ <red>192</red>
+ <green>192</green>
+ <blue>192</blue>
+ </color>
+ <color>
+ <red>255</red>
+ <green>255</green>
+ <blue>255</blue>
+ </color>
+ <color>
+ <red>220</red>
+ <green>220</green>
+ <blue>220</blue>
+ </color>
+ <color>
+ <red>96</red>
+ <green>96</green>
+ <blue>96</blue>
+ </color>
+ <color>
+ <red>128</red>
+ <green>128</green>
+ <blue>128</blue>
+ </color>
+ <color>
+ <red>0</red>
+ <green>0</green>
+ <blue>0</blue>
+ </color>
+ <color>
+ <red>255</red>
+ <green>255</green>
+ <blue>255</blue>
+ </color>
+ <color>
+ <red>0</red>
+ <green>0</green>
+ <blue>0</blue>
+ </color>
+ <color>
+ <red>255</red>
+ <green>255</green>
+ <blue>255</blue>
+ </color>
+ <color>
+ <red>0</red>
+ <green>0</green>
+ <blue>0</blue>
+ </color>
+ <color>
+ <red>0</red>
+ <green>0</green>
+ <blue>0</blue>
+ </color>
+ <color>
+ <red>0</red>
+ <green>0</green>
+ <blue>0</blue>
+ </color>
+ <color>
+ <red>255</red>
+ <green>255</green>
+ <blue>255</blue>
+ </color>
+ <color>
+ <red>0</red>
+ <green>0</green>
+ <blue>0</blue>
+ </color>
+ <color>
+ <red>0</red>
+ <green>0</green>
+ <blue>0</blue>
+ </color>
+ </inactive>
+ </palette>
+ </property>
+ <property name="frameShape">
+ <enum>StyledPanel</enum>
+ </property>
+ <property name="frameShadow">
+ <enum>Sunken</enum>
+ </property>
+ <widget class="Rosegarden::Label">
+ <property name="name">
+ <cstring>UnitHoursPixmap</cstring>
+ </property>
+ <property name="geometry">
+ <rect>
+ <x>39</x>
+ <y>6</y>
+ <width>20</width>
+ <height>34</height>
+ </rect>
+ </property>
+ <property name="pixmap">
+ <pixmap>image8</pixmap>
+ </property>
+ <property name="scaledContents">
+ <bool>true</bool>
+ </property>
+ <property name="buddy" stdset="0">
+ <cstring></cstring>
+ </property>
+ </widget>
+ <widget class="Rosegarden::Label">
+ <property name="name">
+ <cstring>HourColonPixmap</cstring>
+ </property>
+ <property name="geometry">
+ <rect>
+ <x>59</x>
+ <y>6</y>
+ <width>5</width>
+ <height>34</height>
+ </rect>
+ </property>
+ <property name="pixmap">
+ <pixmap>image9</pixmap>
+ </property>
+ <property name="scaledContents">
+ <bool>true</bool>
+ </property>
+ <property name="buddy" stdset="0">
+ <cstring></cstring>
+ </property>
+ </widget>
+ <widget class="Rosegarden::Label">
+ <property name="name">
+ <cstring>TenMinutesPixmap</cstring>
+ </property>
+ <property name="geometry">
+ <rect>
+ <x>64</x>
+ <y>6</y>
+ <width>20</width>
+ <height>34</height>
+ </rect>
+ </property>
+ <property name="pixmap">
+ <pixmap>image8</pixmap>
+ </property>
+ <property name="scaledContents">
+ <bool>true</bool>
+ </property>
+ <property name="buddy" stdset="0">
+ <cstring></cstring>
+ </property>
+ </widget>
+ <widget class="Rosegarden::Label">
+ <property name="name">
+ <cstring>UnitMinutesPixmap</cstring>
+ </property>
+ <property name="geometry">
+ <rect>
+ <x>84</x>
+ <y>6</y>
+ <width>20</width>
+ <height>34</height>
+ </rect>
+ </property>
+ <property name="pixmap">
+ <pixmap>image8</pixmap>
+ </property>
+ <property name="scaledContents">
+ <bool>true</bool>
+ </property>
+ <property name="buddy" stdset="0">
+ <cstring></cstring>
+ </property>
+ </widget>
+ <widget class="Rosegarden::Label">
+ <property name="name">
+ <cstring>TenSecondsPixmap</cstring>
+ </property>
+ <property name="geometry">
+ <rect>
+ <x>109</x>
+ <y>6</y>
+ <width>20</width>
+ <height>34</height>
+ </rect>
+ </property>
+ <property name="pixmap">
+ <pixmap>image8</pixmap>
+ </property>
+ <property name="scaledContents">
+ <bool>true</bool>
+ </property>
+ <property name="buddy" stdset="0">
+ <cstring></cstring>
+ </property>
+ </widget>
+ <widget class="Rosegarden::Label">
+ <property name="name">
+ <cstring>UnitSecondsPixmap</cstring>
+ </property>
+ <property name="geometry">
+ <rect>
+ <x>129</x>
+ <y>6</y>
+ <width>20</width>
+ <height>34</height>
+ </rect>
+ </property>
+ <property name="pixmap">
+ <pixmap>image8</pixmap>
+ </property>
+ <property name="scaledContents">
+ <bool>true</bool>
+ </property>
+ <property name="buddy" stdset="0">
+ <cstring></cstring>
+ </property>
+ </widget>
+ <widget class="Rosegarden::Label">
+ <property name="name">
+ <cstring>HundredthsPixmap</cstring>
+ </property>
+ <property name="geometry">
+ <rect>
+ <x>169</x>
+ <y>16</y>
+ <width>14</width>
+ <height>24</height>
+ </rect>
+ </property>
+ <property name="pixmap">
+ <pixmap>image8</pixmap>
+ </property>
+ <property name="scaledContents">
+ <bool>true</bool>
+ </property>
+ <property name="buddy" stdset="0">
+ <cstring></cstring>
+ </property>
+ </widget>
+ <widget class="Rosegarden::Label">
+ <property name="name">
+ <cstring>NegativePixmap</cstring>
+ </property>
+ <property name="geometry">
+ <rect>
+ <x>2</x>
+ <y>6</y>
+ <width>20</width>
+ <height>34</height>
+ </rect>
+ </property>
+ <property name="pixmap">
+ <pixmap>image10</pixmap>
+ </property>
+ <property name="scaledContents">
+ <bool>true</bool>
+ </property>
+ <property name="buddy" stdset="0">
+ <cstring></cstring>
+ </property>
+ </widget>
+ <widget class="Rosegarden::Label">
+ <property name="name">
+ <cstring>TenHoursPixmap</cstring>
+ </property>
+ <property name="geometry">
+ <rect>
+ <x>19</x>
+ <y>6</y>
+ <width>20</width>
+ <height>34</height>
+ </rect>
+ </property>
+ <property name="pixmap">
+ <pixmap>image8</pixmap>
+ </property>
+ <property name="scaledContents">
+ <bool>true</bool>
+ </property>
+ <property name="buddy" stdset="0">
+ <cstring></cstring>
+ </property>
+ </widget>
+ <widget class="QLabel">
+ <property name="name">
+ <cstring>TimeSigLabel</cstring>
+ </property>
+ <property name="geometry">
+ <rect>
+ <x>6</x>
+ <y>51</y>
+ <width>28</width>
+ <height>16</height>
+ </rect>
+ </property>
+ <property name="palette">
+ <palette>
+ <active>
+ <color>
+ <red>192</red>
+ <green>216</green>
+ <blue>255</blue>
+ </color>
+ <color>
+ <red>192</red>
+ <green>192</green>
+ <blue>192</blue>
+ </color>
+ <color>
+ <red>255</red>
+ <green>255</green>
+ <blue>255</blue>
+ </color>
+ <color>
+ <red>223</red>
+ <green>223</green>
+ <blue>223</blue>
+ </color>
+ <color>
+ <red>96</red>
+ <green>96</green>
+ <blue>96</blue>
+ </color>
+ <color>
+ <red>128</red>
+ <green>128</green>
+ <blue>128</blue>
+ </color>
+ <color>
+ <red>0</red>
+ <green>0</green>
+ <blue>0</blue>
+ </color>
+ <color>
+ <red>255</red>
+ <green>255</green>
+ <blue>255</blue>
+ </color>
+ <color>
+ <red>0</red>
+ <green>0</green>
+ <blue>0</blue>
+ </color>
+ <color>
+ <red>255</red>
+ <green>255</green>
+ <blue>255</blue>
+ </color>
+ <color>
+ <red>0</red>
+ <green>0</green>
+ <blue>0</blue>
+ </color>
+ <color>
+ <red>0</red>
+ <green>0</green>
+ <blue>0</blue>
+ </color>
+ <color>
+ <red>0</red>
+ <green>0</green>
+ <blue>0</blue>
+ </color>
+ <color>
+ <red>255</red>
+ <green>255</green>
+ <blue>255</blue>
+ </color>
+ <color>
+ <red>0</red>
+ <green>0</green>
+ <blue>0</blue>
+ </color>
+ <color>
+ <red>0</red>
+ <green>0</green>
+ <blue>0</blue>
+ </color>
+ </active>
+ <disabled>
+ <color>
+ <red>128</red>
+ <green>128</green>
+ <blue>128</blue>
+ </color>
+ <color>
+ <red>192</red>
+ <green>192</green>
+ <blue>192</blue>
+ </color>
+ <color>
+ <red>255</red>
+ <green>255</green>
+ <blue>255</blue>
+ </color>
+ <color>
+ <red>220</red>
+ <green>220</green>
+ <blue>220</blue>
+ </color>
+ <color>
+ <red>96</red>
+ <green>96</green>
+ <blue>96</blue>
+ </color>
+ <color>
+ <red>128</red>
+ <green>128</green>
+ <blue>128</blue>
+ </color>
+ <color>
+ <red>0</red>
+ <green>0</green>
+ <blue>0</blue>
+ </color>
+ <color>
+ <red>255</red>
+ <green>255</green>
+ <blue>255</blue>
+ </color>
+ <color>
+ <red>128</red>
+ <green>128</green>
+ <blue>128</blue>
+ </color>
+ <color>
+ <red>255</red>
+ <green>255</green>
+ <blue>255</blue>
+ </color>
+ <color>
+ <red>0</red>
+ <green>0</green>
+ <blue>0</blue>
+ </color>
+ <color>
+ <red>0</red>
+ <green>0</green>
+ <blue>0</blue>
+ </color>
+ <color>
+ <red>0</red>
+ <green>0</green>
+ <blue>0</blue>
+ </color>
+ <color>
+ <red>255</red>
+ <green>255</green>
+ <blue>255</blue>
+ </color>
+ <color>
+ <red>0</red>
+ <green>0</green>
+ <blue>0</blue>
+ </color>
+ <color>
+ <red>0</red>
+ <green>0</green>
+ <blue>0</blue>
+ </color>
+ </disabled>
+ <inactive>
+ <color>
+ <red>192</red>
+ <green>216</green>
+ <blue>255</blue>
+ </color>
+ <color>
+ <red>192</red>
+ <green>192</green>
+ <blue>192</blue>
+ </color>
+ <color>
+ <red>255</red>
+ <green>255</green>
+ <blue>255</blue>
+ </color>
+ <color>
+ <red>220</red>
+ <green>220</green>
+ <blue>220</blue>
+ </color>
+ <color>
+ <red>96</red>
+ <green>96</green>
+ <blue>96</blue>
+ </color>
+ <color>
+ <red>128</red>
+ <green>128</green>
+ <blue>128</blue>
+ </color>
+ <color>
+ <red>0</red>
+ <green>0</green>
+ <blue>0</blue>
+ </color>
+ <color>
+ <red>255</red>
+ <green>255</green>
+ <blue>255</blue>
+ </color>
+ <color>
+ <red>0</red>
+ <green>0</green>
+ <blue>0</blue>
+ </color>
+ <color>
+ <red>255</red>
+ <green>255</green>
+ <blue>255</blue>
+ </color>
+ <color>
+ <red>0</red>
+ <green>0</green>
+ <blue>0</blue>
+ </color>
+ <color>
+ <red>0</red>
+ <green>0</green>
+ <blue>0</blue>
+ </color>
+ <color>
+ <red>0</red>
+ <green>0</green>
+ <blue>0</blue>
+ </color>
+ <color>
+ <red>255</red>
+ <green>255</green>
+ <blue>255</blue>
+ </color>
+ <color>
+ <red>0</red>
+ <green>0</green>
+ <blue>0</blue>
+ </color>
+ <color>
+ <red>0</red>
+ <green>0</green>
+ <blue>0</blue>
+ </color>
+ </inactive>
+ </palette>
+ </property>
+ <property name="font">
+ <font>
+ <family>lucida</family>
+ <italic>1</italic>
+ </font>
+ </property>
+ <property name="text">
+ <string>SIG</string>
+ </property>
+ </widget>
+ <widget class="QLabel">
+ <property name="name">
+ <cstring>DivisionLabel</cstring>
+ </property>
+ <property name="geometry">
+ <rect>
+ <x>66</x>
+ <y>51</y>
+ <width>24</width>
+ <height>16</height>
+ </rect>
+ </property>
+ <property name="palette">
+ <palette>
+ <active>
+ <color>
+ <red>192</red>
+ <green>216</green>
+ <blue>255</blue>
+ </color>
+ <color>
+ <red>192</red>
+ <green>192</green>
+ <blue>192</blue>
+ </color>
+ <color>
+ <red>255</red>
+ <green>255</green>
+ <blue>255</blue>
+ </color>
+ <color>
+ <red>223</red>
+ <green>223</green>
+ <blue>223</blue>
+ </color>
+ <color>
+ <red>96</red>
+ <green>96</green>
+ <blue>96</blue>
+ </color>
+ <color>
+ <red>128</red>
+ <green>128</green>
+ <blue>128</blue>
+ </color>
+ <color>
+ <red>0</red>
+ <green>0</green>
+ <blue>0</blue>
+ </color>
+ <color>
+ <red>255</red>
+ <green>255</green>
+ <blue>255</blue>
+ </color>
+ <color>
+ <red>0</red>
+ <green>0</green>
+ <blue>0</blue>
+ </color>
+ <color>
+ <red>255</red>
+ <green>255</green>
+ <blue>255</blue>
+ </color>
+ <color>
+ <red>0</red>
+ <green>0</green>
+ <blue>0</blue>
+ </color>
+ <color>
+ <red>0</red>
+ <green>0</green>
+ <blue>0</blue>
+ </color>
+ <color>
+ <red>0</red>
+ <green>0</green>
+ <blue>0</blue>
+ </color>
+ <color>
+ <red>255</red>
+ <green>255</green>
+ <blue>255</blue>
+ </color>
+ <color>
+ <red>0</red>
+ <green>0</green>
+ <blue>0</blue>
+ </color>
+ <color>
+ <red>0</red>
+ <green>0</green>
+ <blue>0</blue>
+ </color>
+ </active>
+ <disabled>
+ <color>
+ <red>128</red>
+ <green>128</green>
+ <blue>128</blue>
+ </color>
+ <color>
+ <red>192</red>
+ <green>192</green>
+ <blue>192</blue>
+ </color>
+ <color>
+ <red>255</red>
+ <green>255</green>
+ <blue>255</blue>
+ </color>
+ <color>
+ <red>220</red>
+ <green>220</green>
+ <blue>220</blue>
+ </color>
+ <color>
+ <red>96</red>
+ <green>96</green>
+ <blue>96</blue>
+ </color>
+ <color>
+ <red>128</red>
+ <green>128</green>
+ <blue>128</blue>
+ </color>
+ <color>
+ <red>0</red>
+ <green>0</green>
+ <blue>0</blue>
+ </color>
+ <color>
+ <red>255</red>
+ <green>255</green>
+ <blue>255</blue>
+ </color>
+ <color>
+ <red>128</red>
+ <green>128</green>
+ <blue>128</blue>
+ </color>
+ <color>
+ <red>255</red>
+ <green>255</green>
+ <blue>255</blue>
+ </color>
+ <color>
+ <red>0</red>
+ <green>0</green>
+ <blue>0</blue>
+ </color>
+ <color>
+ <red>0</red>
+ <green>0</green>
+ <blue>0</blue>
+ </color>
+ <color>
+ <red>0</red>
+ <green>0</green>
+ <blue>0</blue>
+ </color>
+ <color>
+ <red>255</red>
+ <green>255</green>
+ <blue>255</blue>
+ </color>
+ <color>
+ <red>0</red>
+ <green>0</green>
+ <blue>0</blue>
+ </color>
+ <color>
+ <red>0</red>
+ <green>0</green>
+ <blue>0</blue>
+ </color>
+ </disabled>
+ <inactive>
+ <color>
+ <red>192</red>
+ <green>216</green>
+ <blue>255</blue>
+ </color>
+ <color>
+ <red>192</red>
+ <green>192</green>
+ <blue>192</blue>
+ </color>
+ <color>
+ <red>255</red>
+ <green>255</green>
+ <blue>255</blue>
+ </color>
+ <color>
+ <red>220</red>
+ <green>220</green>
+ <blue>220</blue>
+ </color>
+ <color>
+ <red>96</red>
+ <green>96</green>
+ <blue>96</blue>
+ </color>
+ <color>
+ <red>128</red>
+ <green>128</green>
+ <blue>128</blue>
+ </color>
+ <color>
+ <red>0</red>
+ <green>0</green>
+ <blue>0</blue>
+ </color>
+ <color>
+ <red>255</red>
+ <green>255</green>
+ <blue>255</blue>
+ </color>
+ <color>
+ <red>0</red>
+ <green>0</green>
+ <blue>0</blue>
+ </color>
+ <color>
+ <red>255</red>
+ <green>255</green>
+ <blue>255</blue>
+ </color>
+ <color>
+ <red>0</red>
+ <green>0</green>
+ <blue>0</blue>
+ </color>
+ <color>
+ <red>0</red>
+ <green>0</green>
+ <blue>0</blue>
+ </color>
+ <color>
+ <red>0</red>
+ <green>0</green>
+ <blue>0</blue>
+ </color>
+ <color>
+ <red>255</red>
+ <green>255</green>
+ <blue>255</blue>
+ </color>
+ <color>
+ <red>0</red>
+ <green>0</green>
+ <blue>0</blue>
+ </color>
+ <color>
+ <red>0</red>
+ <green>0</green>
+ <blue>0</blue>
+ </color>
+ </inactive>
+ </palette>
+ </property>
+ <property name="font">
+ <font>
+ <family>lucida</family>
+ <italic>1</italic>
+ </font>
+ </property>
+ <property name="text">
+ <string>DIV</string>
+ </property>
+ </widget>
+ <widget class="QLabel">
+ <property name="name">
+ <cstring>DivisionDisplay</cstring>
+ </property>
+ <property name="geometry">
+ <rect>
+ <x>86</x>
+ <y>51</y>
+ <width>27</width>
+ <height>16</height>
+ </rect>
+ </property>
+ <property name="palette">
+ <palette>
+ <active>
+ <color>
+ <red>192</red>
+ <green>216</green>
+ <blue>255</blue>
+ </color>
+ <color>
+ <red>192</red>
+ <green>192</green>
+ <blue>192</blue>
+ </color>
+ <color>
+ <red>255</red>
+ <green>255</green>
+ <blue>255</blue>
+ </color>
+ <color>
+ <red>223</red>
+ <green>223</green>
+ <blue>223</blue>
+ </color>
+ <color>
+ <red>96</red>
+ <green>96</green>
+ <blue>96</blue>
+ </color>
+ <color>
+ <red>128</red>
+ <green>128</green>
+ <blue>128</blue>
+ </color>
+ <color>
+ <red>0</red>
+ <green>0</green>
+ <blue>0</blue>
+ </color>
+ <color>
+ <red>255</red>
+ <green>255</green>
+ <blue>255</blue>
+ </color>
+ <color>
+ <red>0</red>
+ <green>0</green>
+ <blue>0</blue>
+ </color>
+ <color>
+ <red>255</red>
+ <green>255</green>
+ <blue>255</blue>
+ </color>
+ <color>
+ <red>0</red>
+ <green>0</green>
+ <blue>0</blue>
+ </color>
+ <color>
+ <red>0</red>
+ <green>0</green>
+ <blue>0</blue>
+ </color>
+ <color>
+ <red>0</red>
+ <green>0</green>
+ <blue>0</blue>
+ </color>
+ <color>
+ <red>255</red>
+ <green>255</green>
+ <blue>255</blue>
+ </color>
+ <color>
+ <red>0</red>
+ <green>0</green>
+ <blue>0</blue>
+ </color>
+ <color>
+ <red>0</red>
+ <green>0</green>
+ <blue>0</blue>
+ </color>
+ </active>
+ <disabled>
+ <color>
+ <red>128</red>
+ <green>128</green>
+ <blue>128</blue>
+ </color>
+ <color>
+ <red>192</red>
+ <green>192</green>
+ <blue>192</blue>
+ </color>
+ <color>
+ <red>255</red>
+ <green>255</green>
+ <blue>255</blue>
+ </color>
+ <color>
+ <red>220</red>
+ <green>220</green>
+ <blue>220</blue>
+ </color>
+ <color>
+ <red>96</red>
+ <green>96</green>
+ <blue>96</blue>
+ </color>
+ <color>
+ <red>128</red>
+ <green>128</green>
+ <blue>128</blue>
+ </color>
+ <color>
+ <red>0</red>
+ <green>0</green>
+ <blue>0</blue>
+ </color>
+ <color>
+ <red>255</red>
+ <green>255</green>
+ <blue>255</blue>
+ </color>
+ <color>
+ <red>128</red>
+ <green>128</green>
+ <blue>128</blue>
+ </color>
+ <color>
+ <red>255</red>
+ <green>255</green>
+ <blue>255</blue>
+ </color>
+ <color>
+ <red>0</red>
+ <green>0</green>
+ <blue>0</blue>
+ </color>
+ <color>
+ <red>0</red>
+ <green>0</green>
+ <blue>0</blue>
+ </color>
+ <color>
+ <red>0</red>
+ <green>0</green>
+ <blue>0</blue>
+ </color>
+ <color>
+ <red>255</red>
+ <green>255</green>
+ <blue>255</blue>
+ </color>
+ <color>
+ <red>0</red>
+ <green>0</green>
+ <blue>0</blue>
+ </color>
+ <color>
+ <red>0</red>
+ <green>0</green>
+ <blue>0</blue>
+ </color>
+ </disabled>
+ <inactive>
+ <color>
+ <red>192</red>
+ <green>216</green>
+ <blue>255</blue>
+ </color>
+ <color>
+ <red>192</red>
+ <green>192</green>
+ <blue>192</blue>
+ </color>
+ <color>
+ <red>255</red>
+ <green>255</green>
+ <blue>255</blue>
+ </color>
+ <color>
+ <red>220</red>
+ <green>220</green>
+ <blue>220</blue>
+ </color>
+ <color>
+ <red>96</red>
+ <green>96</green>
+ <blue>96</blue>
+ </color>
+ <color>
+ <red>128</red>
+ <green>128</green>
+ <blue>128</blue>
+ </color>
+ <color>
+ <red>0</red>
+ <green>0</green>
+ <blue>0</blue>
+ </color>
+ <color>
+ <red>255</red>
+ <green>255</green>
+ <blue>255</blue>
+ </color>
+ <color>
+ <red>0</red>
+ <green>0</green>
+ <blue>0</blue>
+ </color>
+ <color>
+ <red>255</red>
+ <green>255</green>
+ <blue>255</blue>
+ </color>
+ <color>
+ <red>0</red>
+ <green>0</green>
+ <blue>0</blue>
+ </color>
+ <color>
+ <red>0</red>
+ <green>0</green>
+ <blue>0</blue>
+ </color>
+ <color>
+ <red>0</red>
+ <green>0</green>
+ <blue>0</blue>
+ </color>
+ <color>
+ <red>255</red>
+ <green>255</green>
+ <blue>255</blue>
+ </color>
+ <color>
+ <red>0</red>
+ <green>0</green>
+ <blue>0</blue>
+ </color>
+ <color>
+ <red>0</red>
+ <green>0</green>
+ <blue>0</blue>
+ </color>
+ </inactive>
+ </palette>
+ </property>
+ <property name="font">
+ <font>
+ <family>lucida</family>
+ <bold>1</bold>
+ </font>
+ </property>
+ <property name="text">
+ <string>/16</string>
+ </property>
+ <property name="alignment">
+ <set>AlignVCenter|AlignRight</set>
+ </property>
+ <property name="hAlign" stdset="0">
+ </property>
+ </widget>
+ <widget class="QLabel">
+ <property name="name">
+ <cstring>TempoLabel</cstring>
+ </property>
+ <property name="geometry">
+ <rect>
+ <x>124</x>
+ <y>51</y>
+ <width>43</width>
+ <height>16</height>
+ </rect>
+ </property>
+ <property name="palette">
+ <palette>
+ <active>
+ <color>
+ <red>192</red>
+ <green>216</green>
+ <blue>255</blue>
+ </color>
+ <color>
+ <red>192</red>
+ <green>192</green>
+ <blue>192</blue>
+ </color>
+ <color>
+ <red>255</red>
+ <green>255</green>
+ <blue>255</blue>
+ </color>
+ <color>
+ <red>223</red>
+ <green>223</green>
+ <blue>223</blue>
+ </color>
+ <color>
+ <red>96</red>
+ <green>96</green>
+ <blue>96</blue>
+ </color>
+ <color>
+ <red>128</red>
+ <green>128</green>
+ <blue>128</blue>
+ </color>
+ <color>
+ <red>0</red>
+ <green>0</green>
+ <blue>0</blue>
+ </color>
+ <color>
+ <red>255</red>
+ <green>255</green>
+ <blue>255</blue>
+ </color>
+ <color>
+ <red>0</red>
+ <green>0</green>
+ <blue>0</blue>
+ </color>
+ <color>
+ <red>255</red>
+ <green>255</green>
+ <blue>255</blue>
+ </color>
+ <color>
+ <red>0</red>
+ <green>0</green>
+ <blue>0</blue>
+ </color>
+ <color>
+ <red>0</red>
+ <green>0</green>
+ <blue>0</blue>
+ </color>
+ <color>
+ <red>0</red>
+ <green>0</green>
+ <blue>0</blue>
+ </color>
+ <color>
+ <red>255</red>
+ <green>255</green>
+ <blue>255</blue>
+ </color>
+ <color>
+ <red>0</red>
+ <green>0</green>
+ <blue>0</blue>
+ </color>
+ <color>
+ <red>0</red>
+ <green>0</green>
+ <blue>0</blue>
+ </color>
+ </active>
+ <disabled>
+ <color>
+ <red>128</red>
+ <green>128</green>
+ <blue>128</blue>
+ </color>
+ <color>
+ <red>192</red>
+ <green>192</green>
+ <blue>192</blue>
+ </color>
+ <color>
+ <red>255</red>
+ <green>255</green>
+ <blue>255</blue>
+ </color>
+ <color>
+ <red>220</red>
+ <green>220</green>
+ <blue>220</blue>
+ </color>
+ <color>
+ <red>96</red>
+ <green>96</green>
+ <blue>96</blue>
+ </color>
+ <color>
+ <red>128</red>
+ <green>128</green>
+ <blue>128</blue>
+ </color>
+ <color>
+ <red>0</red>
+ <green>0</green>
+ <blue>0</blue>
+ </color>
+ <color>
+ <red>255</red>
+ <green>255</green>
+ <blue>255</blue>
+ </color>
+ <color>
+ <red>128</red>
+ <green>128</green>
+ <blue>128</blue>
+ </color>
+ <color>
+ <red>255</red>
+ <green>255</green>
+ <blue>255</blue>
+ </color>
+ <color>
+ <red>0</red>
+ <green>0</green>
+ <blue>0</blue>
+ </color>
+ <color>
+ <red>0</red>
+ <green>0</green>
+ <blue>0</blue>
+ </color>
+ <color>
+ <red>0</red>
+ <green>0</green>
+ <blue>0</blue>
+ </color>
+ <color>
+ <red>255</red>
+ <green>255</green>
+ <blue>255</blue>
+ </color>
+ <color>
+ <red>0</red>
+ <green>0</green>
+ <blue>0</blue>
+ </color>
+ <color>
+ <red>0</red>
+ <green>0</green>
+ <blue>0</blue>
+ </color>
+ </disabled>
+ <inactive>
+ <color>
+ <red>192</red>
+ <green>216</green>
+ <blue>255</blue>
+ </color>
+ <color>
+ <red>192</red>
+ <green>192</green>
+ <blue>192</blue>
+ </color>
+ <color>
+ <red>255</red>
+ <green>255</green>
+ <blue>255</blue>
+ </color>
+ <color>
+ <red>220</red>
+ <green>220</green>
+ <blue>220</blue>
+ </color>
+ <color>
+ <red>96</red>
+ <green>96</green>
+ <blue>96</blue>
+ </color>
+ <color>
+ <red>128</red>
+ <green>128</green>
+ <blue>128</blue>
+ </color>
+ <color>
+ <red>0</red>
+ <green>0</green>
+ <blue>0</blue>
+ </color>
+ <color>
+ <red>255</red>
+ <green>255</green>
+ <blue>255</blue>
+ </color>
+ <color>
+ <red>0</red>
+ <green>0</green>
+ <blue>0</blue>
+ </color>
+ <color>
+ <red>255</red>
+ <green>255</green>
+ <blue>255</blue>
+ </color>
+ <color>
+ <red>0</red>
+ <green>0</green>
+ <blue>0</blue>
+ </color>
+ <color>
+ <red>0</red>
+ <green>0</green>
+ <blue>0</blue>
+ </color>
+ <color>
+ <red>0</red>
+ <green>0</green>
+ <blue>0</blue>
+ </color>
+ <color>
+ <red>255</red>
+ <green>255</green>
+ <blue>255</blue>
+ </color>
+ <color>
+ <red>0</red>
+ <green>0</green>
+ <blue>0</blue>
+ </color>
+ <color>
+ <red>0</red>
+ <green>0</green>
+ <blue>0</blue>
+ </color>
+ </inactive>
+ </palette>
+ </property>
+ <property name="font">
+ <font>
+ <family>lucida</family>
+ <italic>1</italic>
+ </font>
+ </property>
+ <property name="text">
+ <string>TEMPO</string>
+ </property>
+ </widget>
+ <widget class="Rosegarden::Label">
+ <property name="name">
+ <cstring>TimeSigDisplay</cstring>
+ </property>
+ <property name="geometry">
+ <rect>
+ <x>23</x>
+ <y>51</y>
+ <width>33</width>
+ <height>16</height>
+ </rect>
+ </property>
+ </widget>
+ <widget class="QLabel">
+ <property name="name">
+ <cstring>ToEndLabel</cstring>
+ </property>
+ <property name="geometry">
+ <rect>
+ <x>192</x>
+ <y>1</y>
+ <width>28</width>
+ <height>16</height>
+ </rect>
+ </property>
+ <property name="palette">
+ <palette>
+ <active>
+ <color>
+ <red>192</red>
+ <green>216</green>
+ <blue>255</blue>
+ </color>
+ <color>
+ <red>192</red>
+ <green>192</green>
+ <blue>192</blue>
+ </color>
+ <color>
+ <red>255</red>
+ <green>255</green>
+ <blue>255</blue>
+ </color>
+ <color>
+ <red>223</red>
+ <green>223</green>
+ <blue>223</blue>
+ </color>
+ <color>
+ <red>96</red>
+ <green>96</green>
+ <blue>96</blue>
+ </color>
+ <color>
+ <red>128</red>
+ <green>128</green>
+ <blue>128</blue>
+ </color>
+ <color>
+ <red>0</red>
+ <green>0</green>
+ <blue>0</blue>
+ </color>
+ <color>
+ <red>255</red>
+ <green>255</green>
+ <blue>255</blue>
+ </color>
+ <color>
+ <red>0</red>
+ <green>0</green>
+ <blue>0</blue>
+ </color>
+ <color>
+ <red>255</red>
+ <green>255</green>
+ <blue>255</blue>
+ </color>
+ <color>
+ <red>0</red>
+ <green>0</green>
+ <blue>0</blue>
+ </color>
+ <color>
+ <red>0</red>
+ <green>0</green>
+ <blue>0</blue>
+ </color>
+ <color>
+ <red>0</red>
+ <green>0</green>
+ <blue>0</blue>
+ </color>
+ <color>
+ <red>255</red>
+ <green>255</green>
+ <blue>255</blue>
+ </color>
+ <color>
+ <red>0</red>
+ <green>0</green>
+ <blue>0</blue>
+ </color>
+ <color>
+ <red>0</red>
+ <green>0</green>
+ <blue>0</blue>
+ </color>
+ </active>
+ <disabled>
+ <color>
+ <red>128</red>
+ <green>128</green>
+ <blue>128</blue>
+ </color>
+ <color>
+ <red>192</red>
+ <green>192</green>
+ <blue>192</blue>
+ </color>
+ <color>
+ <red>255</red>
+ <green>255</green>
+ <blue>255</blue>
+ </color>
+ <color>
+ <red>220</red>
+ <green>220</green>
+ <blue>220</blue>
+ </color>
+ <color>
+ <red>96</red>
+ <green>96</green>
+ <blue>96</blue>
+ </color>
+ <color>
+ <red>128</red>
+ <green>128</green>
+ <blue>128</blue>
+ </color>
+ <color>
+ <red>0</red>
+ <green>0</green>
+ <blue>0</blue>
+ </color>
+ <color>
+ <red>255</red>
+ <green>255</green>
+ <blue>255</blue>
+ </color>
+ <color>
+ <red>128</red>
+ <green>128</green>
+ <blue>128</blue>
+ </color>
+ <color>
+ <red>255</red>
+ <green>255</green>
+ <blue>255</blue>
+ </color>
+ <color>
+ <red>0</red>
+ <green>0</green>
+ <blue>0</blue>
+ </color>
+ <color>
+ <red>0</red>
+ <green>0</green>
+ <blue>0</blue>
+ </color>
+ <color>
+ <red>0</red>
+ <green>0</green>
+ <blue>0</blue>
+ </color>
+ <color>
+ <red>255</red>
+ <green>255</green>
+ <blue>255</blue>
+ </color>
+ <color>
+ <red>0</red>
+ <green>0</green>
+ <blue>0</blue>
+ </color>
+ <color>
+ <red>0</red>
+ <green>0</green>
+ <blue>0</blue>
+ </color>
+ </disabled>
+ <inactive>
+ <color>
+ <red>192</red>
+ <green>216</green>
+ <blue>255</blue>
+ </color>
+ <color>
+ <red>192</red>
+ <green>192</green>
+ <blue>192</blue>
+ </color>
+ <color>
+ <red>255</red>
+ <green>255</green>
+ <blue>255</blue>
+ </color>
+ <color>
+ <red>220</red>
+ <green>220</green>
+ <blue>220</blue>
+ </color>
+ <color>
+ <red>96</red>
+ <green>96</green>
+ <blue>96</blue>
+ </color>
+ <color>
+ <red>128</red>
+ <green>128</green>
+ <blue>128</blue>
+ </color>
+ <color>
+ <red>0</red>
+ <green>0</green>
+ <blue>0</blue>
+ </color>
+ <color>
+ <red>255</red>
+ <green>255</green>
+ <blue>255</blue>
+ </color>
+ <color>
+ <red>0</red>
+ <green>0</green>
+ <blue>0</blue>
+ </color>
+ <color>
+ <red>255</red>
+ <green>255</green>
+ <blue>255</blue>
+ </color>
+ <color>
+ <red>0</red>
+ <green>0</green>
+ <blue>0</blue>
+ </color>
+ <color>
+ <red>0</red>
+ <green>0</green>
+ <blue>0</blue>
+ </color>
+ <color>
+ <red>0</red>
+ <green>0</green>
+ <blue>0</blue>
+ </color>
+ <color>
+ <red>255</red>
+ <green>255</green>
+ <blue>255</blue>
+ </color>
+ <color>
+ <red>0</red>
+ <green>0</green>
+ <blue>0</blue>
+ </color>
+ <color>
+ <red>0</red>
+ <green>0</green>
+ <blue>0</blue>
+ </color>
+ </inactive>
+ </palette>
+ </property>
+ <property name="font">
+ <font>
+ <family>lucida</family>
+ <italic>1</italic>
+ </font>
+ </property>
+ <property name="text">
+ <string>END</string>
+ </property>
+ <property name="alignment">
+ <set>AlignVCenter|AlignRight</set>
+ </property>
+ <property name="hAlign" stdset="0">
+ </property>
+ </widget>
+ <widget class="QLabel">
+ <property name="name">
+ <cstring>TimeDisplayLabel</cstring>
+ </property>
+ <property name="geometry">
+ <rect>
+ <x>157</x>
+ <y>1</y>
+ <width>35</width>
+ <height>16</height>
+ </rect>
+ </property>
+ <property name="palette">
+ <palette>
+ <active>
+ <color>
+ <red>192</red>
+ <green>216</green>
+ <blue>255</blue>
+ </color>
+ <color>
+ <red>192</red>
+ <green>192</green>
+ <blue>192</blue>
+ </color>
+ <color>
+ <red>255</red>
+ <green>255</green>
+ <blue>255</blue>
+ </color>
+ <color>
+ <red>223</red>
+ <green>223</green>
+ <blue>223</blue>
+ </color>
+ <color>
+ <red>96</red>
+ <green>96</green>
+ <blue>96</blue>
+ </color>
+ <color>
+ <red>128</red>
+ <green>128</green>
+ <blue>128</blue>
+ </color>
+ <color>
+ <red>0</red>
+ <green>0</green>
+ <blue>0</blue>
+ </color>
+ <color>
+ <red>255</red>
+ <green>255</green>
+ <blue>255</blue>
+ </color>
+ <color>
+ <red>0</red>
+ <green>0</green>
+ <blue>0</blue>
+ </color>
+ <color>
+ <red>255</red>
+ <green>255</green>
+ <blue>255</blue>
+ </color>
+ <color>
+ <red>0</red>
+ <green>0</green>
+ <blue>0</blue>
+ </color>
+ <color>
+ <red>0</red>
+ <green>0</green>
+ <blue>0</blue>
+ </color>
+ <color>
+ <red>0</red>
+ <green>0</green>
+ <blue>0</blue>
+ </color>
+ <color>
+ <red>255</red>
+ <green>255</green>
+ <blue>255</blue>
+ </color>
+ <color>
+ <red>0</red>
+ <green>0</green>
+ <blue>0</blue>
+ </color>
+ <color>
+ <red>0</red>
+ <green>0</green>
+ <blue>0</blue>
+ </color>
+ </active>
+ <disabled>
+ <color>
+ <red>128</red>
+ <green>128</green>
+ <blue>128</blue>
+ </color>
+ <color>
+ <red>192</red>
+ <green>192</green>
+ <blue>192</blue>
+ </color>
+ <color>
+ <red>255</red>
+ <green>255</green>
+ <blue>255</blue>
+ </color>
+ <color>
+ <red>220</red>
+ <green>220</green>
+ <blue>220</blue>
+ </color>
+ <color>
+ <red>96</red>
+ <green>96</green>
+ <blue>96</blue>
+ </color>
+ <color>
+ <red>128</red>
+ <green>128</green>
+ <blue>128</blue>
+ </color>
+ <color>
+ <red>0</red>
+ <green>0</green>
+ <blue>0</blue>
+ </color>
+ <color>
+ <red>255</red>
+ <green>255</green>
+ <blue>255</blue>
+ </color>
+ <color>
+ <red>128</red>
+ <green>128</green>
+ <blue>128</blue>
+ </color>
+ <color>
+ <red>255</red>
+ <green>255</green>
+ <blue>255</blue>
+ </color>
+ <color>
+ <red>0</red>
+ <green>0</green>
+ <blue>0</blue>
+ </color>
+ <color>
+ <red>0</red>
+ <green>0</green>
+ <blue>0</blue>
+ </color>
+ <color>
+ <red>0</red>
+ <green>0</green>
+ <blue>0</blue>
+ </color>
+ <color>
+ <red>255</red>
+ <green>255</green>
+ <blue>255</blue>
+ </color>
+ <color>
+ <red>0</red>
+ <green>0</green>
+ <blue>0</blue>
+ </color>
+ <color>
+ <red>0</red>
+ <green>0</green>
+ <blue>0</blue>
+ </color>
+ </disabled>
+ <inactive>
+ <color>
+ <red>192</red>
+ <green>216</green>
+ <blue>255</blue>
+ </color>
+ <color>
+ <red>192</red>
+ <green>192</green>
+ <blue>192</blue>
+ </color>
+ <color>
+ <red>255</red>
+ <green>255</green>
+ <blue>255</blue>
+ </color>
+ <color>
+ <red>220</red>
+ <green>220</green>
+ <blue>220</blue>
+ </color>
+ <color>
+ <red>96</red>
+ <green>96</green>
+ <blue>96</blue>
+ </color>
+ <color>
+ <red>128</red>
+ <green>128</green>
+ <blue>128</blue>
+ </color>
+ <color>
+ <red>0</red>
+ <green>0</green>
+ <blue>0</blue>
+ </color>
+ <color>
+ <red>255</red>
+ <green>255</green>
+ <blue>255</blue>
+ </color>
+ <color>
+ <red>0</red>
+ <green>0</green>
+ <blue>0</blue>
+ </color>
+ <color>
+ <red>255</red>
+ <green>255</green>
+ <blue>255</blue>
+ </color>
+ <color>
+ <red>0</red>
+ <green>0</green>
+ <blue>0</blue>
+ </color>
+ <color>
+ <red>0</red>
+ <green>0</green>
+ <blue>0</blue>
+ </color>
+ <color>
+ <red>0</red>
+ <green>0</green>
+ <blue>0</blue>
+ </color>
+ <color>
+ <red>255</red>
+ <green>255</green>
+ <blue>255</blue>
+ </color>
+ <color>
+ <red>0</red>
+ <green>0</green>
+ <blue>0</blue>
+ </color>
+ <color>
+ <red>0</red>
+ <green>0</green>
+ <blue>0</blue>
+ </color>
+ </inactive>
+ </palette>
+ </property>
+ <property name="font">
+ <font>
+ <family>lucida</family>
+ <italic>1</italic>
+ </font>
+ </property>
+ <property name="text">
+ <string>BAR</string>
+ </property>
+ <property name="alignment">
+ <set>AlignVCenter|AlignRight</set>
+ </property>
+ <property name="hAlign" stdset="0">
+ </property>
+ </widget>
+ <widget class="Rosegarden::Label">
+ <property name="name">
+ <cstring>TenThousandthsPixmap</cstring>
+ </property>
+ <property name="geometry">
+ <rect>
+ <x>201</x>
+ <y>16</y>
+ <width>14</width>
+ <height>24</height>
+ </rect>
+ </property>
+ <property name="pixmap">
+ <pixmap>image8</pixmap>
+ </property>
+ <property name="scaledContents">
+ <bool>true</bool>
+ </property>
+ <property name="buddy" stdset="0">
+ <cstring></cstring>
+ </property>
+ </widget>
+ <widget class="Rosegarden::Label">
+ <property name="name">
+ <cstring>MinuteColonPixmap</cstring>
+ </property>
+ <property name="geometry">
+ <rect>
+ <x>104</x>
+ <y>6</y>
+ <width>5</width>
+ <height>34</height>
+ </rect>
+ </property>
+ <property name="pixmap">
+ <pixmap>image9</pixmap>
+ </property>
+ <property name="scaledContents">
+ <bool>true</bool>
+ </property>
+ <property name="buddy" stdset="0">
+ <cstring></cstring>
+ </property>
+ </widget>
+ <widget class="Rosegarden::Label">
+ <property name="name">
+ <cstring>SecondColonPixmap</cstring>
+ </property>
+ <property name="geometry">
+ <rect>
+ <x>183</x>
+ <y>14</y>
+ <width>4</width>
+ <height>27</height>
+ </rect>
+ </property>
+ <property name="pixmap">
+ <pixmap>image9</pixmap>
+ </property>
+ <property name="scaledContents">
+ <bool>true</bool>
+ </property>
+ <property name="buddy" stdset="0">
+ <cstring></cstring>
+ </property>
+ </widget>
+ <widget class="Rosegarden::Label">
+ <property name="name">
+ <cstring>HundredthColonPixmap</cstring>
+ </property>
+ <property name="geometry">
+ <rect>
+ <x>151</x>
+ <y>14</y>
+ <width>4</width>
+ <height>27</height>
+ </rect>
+ </property>
+ <property name="pixmap">
+ <pixmap>image9</pixmap>
+ </property>
+ <property name="scaledContents">
+ <bool>true</bool>
+ </property>
+ <property name="buddy" stdset="0">
+ <cstring></cstring>
+ </property>
+ </widget>
+ <widget class="Rosegarden::Label">
+ <property name="name">
+ <cstring>TenthsPixmap</cstring>
+ </property>
+ <property name="geometry">
+ <rect>
+ <x>155</x>
+ <y>16</y>
+ <width>14</width>
+ <height>24</height>
+ </rect>
+ </property>
+ <property name="pixmap">
+ <pixmap>image8</pixmap>
+ </property>
+ <property name="scaledContents">
+ <bool>true</bool>
+ </property>
+ <property name="buddy" stdset="0">
+ <cstring></cstring>
+ </property>
+ </widget>
+ <widget class="Rosegarden::Label">
+ <property name="name">
+ <cstring>ThousandthsPixmap</cstring>
+ </property>
+ <property name="geometry">
+ <rect>
+ <x>187</x>
+ <y>16</y>
+ <width>14</width>
+ <height>24</height>
+ </rect>
+ </property>
+ <property name="pixmap">
+ <pixmap>image8</pixmap>
+ </property>
+ <property name="scaledContents">
+ <bool>true</bool>
+ </property>
+ <property name="buddy" stdset="0">
+ <cstring></cstring>
+ </property>
+ </widget>
+ <widget class="Rosegarden::Label">
+ <property name="name">
+ <cstring>TempoDisplay</cstring>
+ </property>
+ <property name="geometry">
+ <rect>
+ <x>159</x>
+ <y>51</y>
+ <width>55</width>
+ <height>16</height>
+ </rect>
+ </property>
+ </widget>
+ </widget>
+ <widget class="QPushButton">
+ <property name="name">
+ <cstring>TimeDisplayButton</cstring>
+ </property>
+ <property name="geometry">
+ <rect>
+ <x>3</x>
+ <y>4</y>
+ <width>25</width>
+ <height>25</height>
+ </rect>
+ </property>
+ <property name="font">
+ <font>
+ <family>lucida</family>
+ <bold>1</bold>
+ </font>
+ </property>
+ <property name="text">
+ <string></string>
+ </property>
+ <property name="pixmap">
+ <pixmap>image11</pixmap>
+ </property>
+ <property name="toggleButton">
+ <bool>false</bool>
+ </property>
+ <property name="flat">
+ <bool>true</bool>
+ </property>
+ <property name="toolTip" stdset="0">
+ <string>Switch between real time, musical time, and frame count</string>
+ </property>
+ </widget>
+ <widget class="QPushButton">
+ <property name="name">
+ <cstring>PanelOpenButton</cstring>
+ </property>
+ <property name="geometry">
+ <rect>
+ <x>3</x>
+ <y>56</y>
+ <width>25</width>
+ <height>25</height>
+ </rect>
+ </property>
+ <property name="font">
+ <font>
+ <family>lucida</family>
+ <bold>1</bold>
+ </font>
+ </property>
+ <property name="text">
+ <string></string>
+ </property>
+ <property name="pixmap">
+ <pixmap>image12</pixmap>
+ </property>
+ <property name="toggleButton">
+ <bool>true</bool>
+ </property>
+ <property name="on">
+ <bool>true</bool>
+ </property>
+ <property name="flat">
+ <bool>true</bool>
+ </property>
+ <property name="toolTip" stdset="0">
+ <string>Show additional controls</string>
+ </property>
+ </widget>
+ <widget class="QPushButton">
+ <property name="name">
+ <cstring>RewindButton</cstring>
+ </property>
+ <property name="geometry">
+ <rect>
+ <x>260</x>
+ <y>4</y>
+ <width>42</width>
+ <height>38</height>
+ </rect>
+ </property>
+ <property name="cursor">
+ <cursor>0</cursor>
+ </property>
+ <property name="focusPolicy">
+ <enum>NoFocus</enum>
+ </property>
+ <property name="text">
+ <string></string>
+ </property>
+ <property name="pixmap">
+ <pixmap>image13</pixmap>
+ </property>
+ <property name="autoRepeat">
+ <bool>true</bool>
+ </property>
+ <property name="toolTip" stdset="0">
+ <string>Rewind</string>
+ </property>
+ <property name="whatsThis" stdset="0">
+ <string>Moves the current pointer position back one bar.</string>
+ </property>
+ </widget>
+ <widget class="QPushButton">
+ <property name="name">
+ <cstring>RewindEndButton</cstring>
+ </property>
+ <property name="geometry">
+ <rect>
+ <x>260</x>
+ <y>43</y>
+ <width>42</width>
+ <height>38</height>
+ </rect>
+ </property>
+ <property name="cursor">
+ <cursor>0</cursor>
+ </property>
+ <property name="focusPolicy">
+ <enum>NoFocus</enum>
+ </property>
+ <property name="text">
+ <string></string>
+ </property>
+ <property name="pixmap">
+ <pixmap>image14</pixmap>
+ </property>
+ <property name="toolTip" stdset="0">
+ <string>Rewind to beginning</string>
+ </property>
+ <property name="whatsThis" stdset="0">
+ <string>Moves the pointer position to the start of the composition. (This may mean going forwards if the pointer is currently before the start.)</string>
+ </property>
+ </widget>
+ <widget class="QPushButton">
+ <property name="name">
+ <cstring>PlayButton</cstring>
+ </property>
+ <property name="geometry">
+ <rect>
+ <x>303</x>
+ <y>4</y>
+ <width>66</width>
+ <height>38</height>
+ </rect>
+ </property>
+ <property name="cursor">
+ <cursor>0</cursor>
+ </property>
+ <property name="focusPolicy">
+ <enum>NoFocus</enum>
+ </property>
+ <property name="text">
+ <string></string>
+ </property>
+ <property name="pixmap">
+ <pixmap>image15</pixmap>
+ </property>
+ <property name="toggleButton">
+ <bool>true</bool>
+ </property>
+ <property name="default">
+ <bool>false</bool>
+ </property>
+ <property name="toolTip" stdset="0">
+ <string>Play/Pause</string>
+ </property>
+ <property name="whatsThis" stdset="0">
+ <string>Plays from the current pointer position, or pauses playback if already in progress.</string>
+ </property>
+ </widget>
+ <widget class="QPushButton">
+ <property name="name">
+ <cstring>StopButton</cstring>
+ </property>
+ <property name="geometry">
+ <rect>
+ <x>303</x>
+ <y>43</y>
+ <width>66</width>
+ <height>38</height>
+ </rect>
+ </property>
+ <property name="cursor">
+ <cursor>0</cursor>
+ </property>
+ <property name="focusPolicy">
+ <enum>NoFocus</enum>
+ </property>
+ <property name="text">
+ <string></string>
+ </property>
+ <property name="pixmap">
+ <pixmap>image16</pixmap>
+ </property>
+ <property name="toolTip" stdset="0">
+ <string>Stop</string>
+ </property>
+ <property name="whatsThis" stdset="0">
+ <string>Stops playback or recording.</string>
+ </property>
+ </widget>
+ <widget class="QPushButton">
+ <property name="name">
+ <cstring>FfwdButton</cstring>
+ </property>
+ <property name="geometry">
+ <rect>
+ <x>370</x>
+ <y>4</y>
+ <width>42</width>
+ <height>38</height>
+ </rect>
+ </property>
+ <property name="cursor">
+ <cursor>0</cursor>
+ </property>
+ <property name="focusPolicy">
+ <enum>NoFocus</enum>
+ </property>
+ <property name="text">
+ <string></string>
+ </property>
+ <property name="pixmap">
+ <pixmap>image17</pixmap>
+ </property>
+ <property name="autoRepeat">
+ <bool>true</bool>
+ </property>
+ <property name="toolTip" stdset="0">
+ <string>Fast forward</string>
+ </property>
+ <property name="whatsThis" stdset="0">
+ <string>Moves the current pointer position forwards one bar.</string>
+ </property>
+ </widget>
+ <widget class="QPushButton">
+ <property name="name">
+ <cstring>FfwdEndButton</cstring>
+ </property>
+ <property name="geometry">
+ <rect>
+ <x>370</x>
+ <y>43</y>
+ <width>42</width>
+ <height>38</height>
+ </rect>
+ </property>
+ <property name="cursor">
+ <cursor>0</cursor>
+ </property>
+ <property name="focusPolicy">
+ <enum>NoFocus</enum>
+ </property>
+ <property name="text">
+ <string></string>
+ </property>
+ <property name="pixmap">
+ <pixmap>image18</pixmap>
+ </property>
+ <property name="toolTip" stdset="0">
+ <string>Fast forward to end</string>
+ </property>
+ <property name="whatsThis" stdset="0">
+ <string>Moves the pointer position to the end of the composition. (This may mean going backwards if the pointer is already beyond the end.)</string>
+ </property>
+ </widget>
+ <widget class="QPushButton">
+ <property name="name">
+ <cstring>ToEndButton</cstring>
+ </property>
+ <property name="geometry">
+ <rect>
+ <x>3</x>
+ <y>28</y>
+ <width>25</width>
+ <height>25</height>
+ </rect>
+ </property>
+ <property name="font">
+ <font>
+ <family>lucida</family>
+ <bold>1</bold>
+ </font>
+ </property>
+ <property name="text">
+ <string></string>
+ </property>
+ <property name="pixmap">
+ <pixmap>image19</pixmap>
+ </property>
+ <property name="toggleButton">
+ <bool>true</bool>
+ </property>
+ <property name="flat">
+ <bool>true</bool>
+ </property>
+ <property name="toolTip" stdset="0">
+ <string>Display time to end</string>
+ </property>
+ </widget>
+ </widget>
+</widget>
+<customwidgets>
+ <customwidget>
+ <class>Rosegarden::Label</class>
+ <header location="local">gui/widgets/Label.h</header>
+ <sizehint>
+ <width>-1</width>
+ <height>-1</height>
+ </sizehint>
+ <container>0</container>
+ <sizepolicy>
+ <hordata>5</hordata>
+ <verdata>5</verdata>
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ <pixmap>image20</pixmap>
+ </customwidget>
+</customwidgets>
+<images>
+ <image name="image0">
+ <data format="PNG" length="117">89504e470d0a1a0a0000000d494844520000000b0000000b0806000000a9ac77260000003c4944415418956360a02f6061f84facd2ff0c0c0cde509a28850c8434202b8401ac1ab029c4d0c088a49810606444e2e03299818181612b118651000006f40d0bd43b3a0f0000000049454e44ae426082</data>
+ </image>
+ <image name="image1">
+ <data format="PNG" length="233">89504e470d0a1a0a0000000d4948445200000016000000160806000000c4b46c3b000000b049444154388de5d44d0a41511887f11fcac4c4c8166cc0166cc67a0c6dc0c0c0dc0694890c311029260694e2d635a0e496ee474794a74ea77f9df7e9edf49ec38f500a292ba3871362ecd109258f13eb98a59b22544289e789bc09255ea6e4c2e2454a2e2c5e7f4a3cce2bce4a0d91e7b835438961f69046a8a61dce33c793c7bec525a4b8effeac07396abe4703235cb1423b94b8ebf513daa51564bde32891cff9fa7a4f1d431c30452b94f80fb8017e53288b176e48270000000049454e44ae426082</data>
+ </image>
+ <image name="image2">
+ <data format="PNG" length="678">89504e470d0a1a0a0000000d4948445200000016000000160806000000c4b46c3b0000026d49444154388dd594b18b954714c58feb351e7090237cc8ac04f30a4510ed421a0b0b0b5d6c74154c6cd244c93f10b0b6b188ff41c0264520e8be52510bc146110b51c1a86d40631267e1538eebf83e9bb7fabd64dd7d65729b19ee9cf9dd33979901fe6fb16e2d811a1d25b8e06a30080070f57c2965387515256d93749289b300c0e026355a1c0c061dc9874cbccfc44e8d5e2190a6834a3b99f8b7a42e37f905c9ad924e48ea98b848322110249f2ba953a3a3abf1663ecc027300b620f053694b43721f80e3a80080a1ed16151581a1abe1d627a6031b97197c05e3d438b31581393502830bcb3206872401608ec18dd3b523690713af93ec063b06ef247508bc60f0b39e6c03c932f87cd0493ab8b66300a52d4f097eaba445b79e6110a22eb97aa9277b0be06a2905000e4f051ec37f77f559db300c046efe5343f25710b07d0c81f553810180899541cc6a16a8f81e31a973ebeb0c2e29699b92be5c89b162b598891feba87ed1be6ee191b7a794fef06bdf595eafa3ea607c555d7779e49775a95e5bd3b19206e3ab0600570802c639256d9fd051972581e0a195ccfd0bccc4af49ceb8fa37b73e62f811029b0d5f50d29e65ddb33f9f2d187eabacbdfdfc27c1087c534a0183175dfd86c1d300c0e00157df53d2770060fbb95bdf766b8038b292eb7e1bf632b123f98ee4ee71a17539e72ee7fc8ba42708dcf8a06ff403c98e89b75607373a9b9bdc31f1ee447bc8f392460c7624cff7f2bb73933b499564fe34587a2ca9937466029c7a9b0213cf58490fc67b4ef7f313ff71ceb9736b20f080e45fab1eaf17a594fd0c0e4b5be63fd6ef85ed7904166cef713508c298620c02819fa735f2df8cf7e809f1988a2b677a0000000049454e44ae426082</data>
+ </image>
+ <image name="image3">
+ <data format="PNG" length="400">89504e470d0a1a0a0000000d4948445200000024000000160806000000a8b23e3e00000157494441544889edd6b14a1c411807f0df49f00a899ca056575da1410842848065ea60ef9b58f8009601df207d0211f20a36819026a085852914e1e2e55039f492dc976247104960c6bb0b08f7876966e7637fecce7eb34cf2c8521ba63898c53c66d2541f9d1aedff0a4a9097788d352ce0092ef0097bf83c0cac04d30cb682c3e05710f7c6efe028d80996c78d5908b683ef7f81dc1f57c16ed01a17a61e6c06df3230b7a39b9ee6ec3840ad602f18148022f812d53ecbca548169092f947f082dac06f5518356b15888a16a09cf64beb61250a370fddd7b344a16e7a68b41318748b55929011da053cca1874355d31c29e86b4295e61807356e460d3ac17b9c17d45ce3238e0a6af2938e8ddda097d17f7e06ef82e763c1dc412d076f82937f34c9417016bc0dd673fbcf6d1e7ada37f10a1b58c1d374e952b5813f601fa7b97b67285042d555bf1d73984ed37dfc40bb1432496efe00bc33c2225c6892300000000049454e44ae426082</data>
+ </image>
+ <image name="image4">
+ <data format="PNG" length="1360">89504e470d0a1a0a0000000d494844520000001e000000160806000000d7632ccf00000517494441544889c595df4f135914c7bf7377663ad3625b185a4263429cb2ac8b499ba5b8188d4e23613711b2215af561f7c997f5c1c87fb2cf3c9b6563021add0775d7043a86170544419b9520f127050af6c7303fee4c3bdd972d01a4467cd94f729f6e723fe7dc7bceb9c0ff04536f435114b6502834542a95e8a54b97a61f3d7a8477efde616d6d0df97c1ea6690200445184244908854288442248241218191939ee38ce0b5114b5999919e7b3c48aa2b09aa6f900444f9d3a3573fffe7dacadadc1b66d54ab5594cb65504ae1ba2e18860121043ccf8365591042c0711cc2e13092c9242627278f3b8ef322140a9554552dd715777676f23e9fefeb0b172e3cbb76edda0e618d6ab5bab500806198adb51d9ee7110a8570f1e2455cbf7efd3b5dd7ff79fdfab5f591585114d6308c6f8e1c39f2eceeddbba094826118088280a6a62644a3514422110c0f0feff93c972f5fae66b3592c2e2ee2c3870fb02c0baeeb82e338f4f6f6626161a19b523a9fc9646c0020db226c3a7dfaf4b37bf7ee41d334b02c8b68348aab57afa2adaded478ee30ed59302c0f0f030c3b2ec215996fb868686d0dede0e9ee7a1eb3ac6c7c7914c26a7fd7ebf544b960180aeae2ecee7f39d58595999c8e5726059168aa2c0308c41afd73b75e3c68de57ac2bd387bf66c0ba5b4471084dbaaaac2b66d84c361b4b6b6feb0b1b1a16632199b00403018f4cbb23c512a9550ad56118bc56018c6a0cfe74bef570a00376fde5c154571dc308cc1582c068661a0691a6459fe5b96653f00104551588661da676767a1eb3a00c034cddfbd5eefd3b1b1b1e27ea535c6c6c636bd5eef53cbb24600c0300ccccdcda152a9749c3f7f9e2700044ae9f7ebebeb5b05518f542ac57f4910aeebc2344dacafafc3719c2e003c711c87715d97d45ac4711cf03cff8b6118f1542a15d82e354df3dbcf95a552a906c330e23ccfff5c2e97b75a9010c2504a1996e338d375dd694992502c16615916e6e7e7914c266fe9ba3e78eedcb9298661d601042ccbea9565f9c9d2d252ddea4ea552bcebba8da669f688a278ebe1c387a094421004844221701c37ebf178285155b5cc71dc622c1683d7eb0500148b45a4d369e472b95bddddddef0dc3485a96251b8611dbdcdc44381caed6131b8671ece8d1a32babababb7d3e9348ac522188641434303e2f138388e5b181d1db55900a8542ac5376fdefc140c06ffdcdcdc84655928140a78fefc39debe7d8be6e6e6bfdadada400881a669b06d1b92245537363676675ebd73e70e1e3f7e8c62b1b8355a6b432897cbf5572a9522f0df005155d52284cc9d3973067ebf1f8490ad82c8e57278f9f2255455c5f4f4342ccb42a552413e9f8724491f65dedada8a5c2e07d334e1ba2e0821686c6cc4c0c0003c1ecf7c4f4f8f03ec1c9982e3388983070f4ea6d369944a25d8b6fdc92a2784a0a5a505d96cb6764e351008a0542a816118783c1e040201f4f5f5a150281c17046166747474e7c85455d5a294ce2e2f2f9fb872e50a3a3a3ad0dcdc0c51144108d953bc173ccfc3e7f3211c0ee3f0e1c3181a1a423e9f3f2608c2939a7447c635128904278a629010d2198fc7d30f1e3cc0caca0a4cd304a574ebb7aa5de1ae77ae46a3510483419c3c79120b0b0b0a2124a3695ae193dfe2ee009a9a9a1a1dc7e9ecefef9f989a9a422693c1e2e2221cc7413018dc2ddd175fd5dbc866b3eed2d2922e49d2fb57af5efd66dbf61fe57299d5753d218a625de9c0c080f7c08103c866b3f58b633f241289402291f8351289d4ede1fdf0f955031894d289e5e5e52fbedeedfc0bff3e8d2e6a3a39850000000049454e44ae426082</data>
+ </image>
+ <image name="image5">
+ <data format="PNG" length="364">89504e470d0a1a0a0000000d4948445200000016000000160806000000c4b46c3b0000013349444154388dedd4bf4a034110c7f18f2141fc53092a884d6a3b5b0b7b5f208d4fa0d88a0fa058586a69616161ed1b08828501452cc43f85858546100de21f142dee8473d9e42e0a56fe60e06698f9eeeccedef2af02aa62058768e20d77b8c2054e718d06368a4267f0828f36f68e7bd48b42a77280597b6805294762f3815fc7266e3182d1f4fb0c5b45bb2539cf6c57d54e8abf548ac42a813f8ba19fc043ed8a0f6a1f4b988c2c5e4813788cc0b3d6c436a6c5e7d45255ac4aee69decdd8c740a7dd97d21d2ce3b80d7cbd5370a82ae67012801bbf056717c8829fc384d875cb535932b4ac2ef28afa308673df07f72af97d2fd3eec2335ec803d724af58acb895eda03b047505fe41daf58de475ebc7307a83bcc174076b58c4531eb886f1483c9b7f8523ecc580ed54414f27057faa4f01ea7bd4f97814ab0000000049454e44ae426082</data>
+ </image>
+ <image name="image6">
+ <data format="PNG" length="130">89504e470d0a1a0a0000000d494844520000000e00000010080600000026944e3a0000004949444154289163601868f01f8a090226120c245923561710d208d3c4488a469c9ab0091215300c0c0c8cc4060e0660c165220301a7e2b311a681ac50c5a999183f92ec54829ae90b007d450a172e17e6880000000049454e44ae426082</data>
+ </image>
+ <image name="image7">
+ <data format="PNG" length="130">89504e470d0a1a0a0000000d494844520000000e00000010080600000026944e3a00000049494441542891636018ace03f14c301130e450401368d4469c6a6919118cdb86c24a89928d3b1a8c769234500390a888a0e0624458c38e4f1c6234e4df86cc4ab099746829a06060000c15a0d0ef2df0b870000000049454e44ae426082</data>
+ </image>
+ <image name="image8">
+ <data format="XPM.GZ" length="923">789c9d924d0a83301085f739c5e0eca454ad1184d22314ba2c942e261169176e5abb28a577afd1980cfe54705cbd2fc973f2325108e7d311c2483c6baaef1af48d1e1016afaa7a5fae878f087631a4122424c146045bd080715b46a29132913a4f8d5446e6695ee8cc483252670595657b76a21639122b858e2b4efd7e6c3e5faae7481c2329c7492d72232c6ffeea8d8c58c747f6db757c64ef38bb2e2acf790acd02fecf1f87fb7b4e7c01c9f3619b1d1fb7b98a4fd8afe213f696b35bcd713b4a5d9f1c778367df8b4fa7cb9353b211cee6ffdd8b1f2c0ac43d</data>
+ </image>
+ <image name="image9">
+ <data format="XPM.GZ" length="412">789cd3d7528808f055d0d2e72a2e492cc94c5648ce482c52d04a29cdcdad8c8eb5ade652325530365130513054d2e152d253485650360003105719c435313449b630067193405c0b638b946453103711c44d364d494c4b03eb05017a3194131393c08ca4c444652a1808320fcc009947175fd45a730100351049ee</data>
+ </image>
+ <image name="image10">
+ <data format="XPM.GZ" length="923">789cd3d7528808f055d0d2e72a2e492cc94c5648ce482c52d04a29cdcdad8c8eb5ade65232325030365130513054d2e152d253485650360003105719c435313449b630067113415c0b638b946453103709c44d364d494c4b03ebc50246c5e92daeac9c0802ca68e2497080ae1ea21c5dfd80b97f8488d75a7301005e68a747</data>
+ </image>
+ <image name="image11">
+ <data format="PNG" length="83">89504e470d0a1a0a0000000d494844520000000b0000000b0806000000a9ac77260000001a49444154189563602013fc87629c624ca49836aa986cc5240100b319040db65cea050000000049454e44ae426082</data>
+ </image>
+ <image name="image12">
+ <data format="PNG" length="113">89504e470d0a1a0a0000000d494844520000000b0000000b0806000000a9ac7726000000384944415418956360a0156084d2ff8951cb82c4f1c1a3700b32c71b8fe9ffa1f228009b06ac0ab169c0ab10ae818385832885c836d011000071460bb64f8d4c3f0000000049454e44ae426082</data>
+ </image>
+ <image name="image13">
+ <data format="PNG" length="248">89504e470d0a1a0a0000000d4948445200000016000000160806000000c4b46c3b000000bf49444154388dedd3b16a42311840e18fe2e050e82c38f7110a6eb6601ddcfa767d81ce5dba74ea26143a17bab9585727471d222236f9d3e8e2e0814b86737312fecbe5c239708ba7235c353ac773a3731544ef3045afd1814e107dc74da3db91bbf1c9d15cf821d818b93fec8f628c577433eff5f15670551658179e55e05e72b1fd514cf05b38f43370d7b5f0178685c03270590e3fde0f06dbf590c855c330c33dbe1b5d354cfa5507d26c5b5c354c9aeb233e1addbfe9627484bb90d80042ac3405a2aa17210000000049454e44ae426082</data>
+ </image>
+ <image name="image14">
+ <data format="PNG" length="226">89504e470d0a1a0a0000000d4948445200000016000000160806000000c4b46c3b000000a949444154388dedd3210a02411406e00f316c50bc8547106c2aa8c1e6dd148cdec06c320b66c166d164f0046b70c3b2e830bb2a88f8c3f0c29bf9e63130fcf3ed69635ae6c00c695643e8098b32709a5b8fd2c125eb3f84eb656ecba16bb4429b6a9f40cbc2835894f8a718638524768ad8899701b4f90a3cc1f949aff10abc432f805786e1806e56df0ac3117decdf0d73ffc65d6cabc0f3422de68a113615068b4a82e1a7f01fc90dab2a1b4817a92dca0000000049454e44ae426082</data>
+ </image>
+ <image name="image15">
+ <data format="PNG" length="240">89504e470d0a1a0a0000000d4948445200000024000000140806000000e57a9f35000000b7494441544889edd4b109c2401886e107c546b4b372033b47b0710d67710d4b577009b1b2b6138434e20236b1300195e44c882629f2c20fc71d1fbcf71d1c1d61fa81b309a264bd47fc7f9d30d34422c601b36675de8562dcb1c6a040f6351705f62a09a573c4bc4d4245daaa5d289d13966d124a6783719b84625cb0a82ad40b085d4b5e608851c94c698ab6b3f36c342bf7b3272b2274c3ea4bae36a1cf561a13ca6ba511a1adfc566a153acbfe006b27ebd3eb6894072e56ba3745befe0b0000000049454e44ae426082</data>
+ </image>
+ <image name="image16">
+ <data format="PNG" length="105">89504e470d0a1a0a0000000d4948445200000024000000140806000000e57a9f3500000030494441544889edceb10d000008024175ff9d75041209dd5f4ff82ac0d3c676131ff3088922482148214821482108483b655f0118dca7db050000000049454e44ae426082</data>
+ </image>
+ <image name="image17">
+ <data format="PNG" length="249">89504e470d0a1a0a0000000d4948445200000016000000160806000000c4b46c3b000000c049444154388dedd33d6a42411440e18f68914210ecdc804b10d249305aa473776e203bb0b1b20b08a9035980a6b1b0b21253f843c03777de08761e1806e63087cbc0f0e0de3412e713ecb1297459a658a157e82e3c05ae8b4ff40b5d360c1dcc1381c865c3d00e02495727fc3ff05ae8ae98e250b17698056e9c0b7f242e9f0329f77b0ea49ea295385f6319b8f75c387571806de0be4ac33f7839edb55cb346f41b43c7df56dbe5265e9ea6a98a462e0c2f3052fda6910b79c3f30deec1357f84474343c1d303120000000049454e44ae426082</data>
+ </image>
+ <image name="image18">
+ <data format="PNG" length="243">89504e470d0a1a0a0000000d4948445200000016000000160806000000c4b46c3b000000ba49444154388dedd3316a02411487f11f89458a848087f008829d48d4c2cebb2958e61069acac7381c01e402b0b0f209b62151665dcb7d94db71f0c036f1edffb33ccd0f1df3c27ea4b9c716c7be0067b0c1ef4ac905ff65ae25c917898e8c94beb8ea78a017d6c1fc8935489e1fd2ff288b82c9fb42dbecabf306f227e4bd45ff0d944fc9aa81fb088887b91a692748c2cd21cbde30ca3a89458e21f7c287e6298aac4df8aa4b5a455e21d663825ced7377b88a9e2697534e717602c1d33e8c59cfe0000000049454e44ae426082</data>
+ </image>
+ <image name="image19">
+ <data format="PNG" length="155">89504e470d0a1a0a0000000d494844520000000b0000000b0806000000a9ac772600000062494441541895a5d0cb0980400c45d1a3b8746593b6623dd6611f8268093a6e14469951c407817c6e42127e2a24e200e50b78510c9f6091838b1bd862c58605fd7d48886cc41cef19fbd5d171163a0ca8d13ced1f3065f2c9c35385ecebb29ff8ac1d37bc1e2a162452d80000000049454e44ae426082</data>
+ </image>
+ <image name="image20">
+ <data format="PNG" length="324">89504e470d0a1a0a0000000d4948445200000016000000160806000000c4b46c3b0000010b49444154388d9d95311284200c453f4c0acbad2d3d0e67dd9a2358ec21283dc316ccb0858b136302a24d66243e7ffe4771ebbaa2756d14ca37031301bc2e88aef51c8dc0784d3994898039eb2ff04fa0bc6e144a42284df028f4348180530fba203ae47f13ab5a7fcaa154ef0fc5a301cd393a4df9c98a843d88bbd03ab606df685ff3c098d22aa2050700f7febcca2854eb979e7bcba3112870f59ab431f8d5da8260fd72bdabd882ca2f4eaefb96626d7c99bea958a65ad36e4135f845b17613d883d1c6b7fe15b24fddc7550987574fad2f4edae9ad660e9741f5e007d84a5f0634b25b1c3f41ee0476b79efec75a604febe504a99e8e4064ff9ca373bdc3d4b2473beb360aa5deff0164ca87a1385f32640000000049454e44ae426082</data>
+ </image>
+</images>
+<tabstops>
+ <tabstop>PlayButton</tabstop>
+ <tabstop>StopButton</tabstop>
+ <tabstop>MetronomeButton</tabstop>
+ <tabstop>RecordButton</tabstop>
+ <tabstop>RewindEndButton</tabstop>
+ <tabstop>RewindButton</tabstop>
+ <tabstop>FfwdButton</tabstop>
+ <tabstop>FfwdEndButton</tabstop>
+</tabstops>
+<layoutdefaults spacing="6" margin="11"/>
+</UI>
diff --git a/src/gui/ui/audiomanager.rc b/src/gui/ui/audiomanager.rc
new file mode 100644
index 0000000..65188d2
--- /dev/null
+++ b/src/gui/ui/audiomanager.rc
@@ -0,0 +1,67 @@
+<!DOCTYPE kpartgui SYSTEM "kpartgui.dtd">
+
+<kpartgui name="Rosegarden::AudioManagerDialog" version="1230">
+
+<MenuBar>
+
+<Menu name="file">
+ <Action name="add_audio" append="new_merge"/>
+ <Action name="export_audio" append="new_merge"/>
+</Menu>
+
+<Menu name="edit">
+ <Action name="remove_audio"/>
+ <Action name="remove_all_audio"/>
+ <Action name="remove_all_unused_audio"/>
+ <Separator/>
+ <Action name="delete_unused_audio"/>
+</Menu>
+
+<Menu name="action">
+<Text>&amp;Action</Text>
+ <Action name="preview_audio"/>
+ <Action name="insert_audio"/>
+ <Action name="distribute_audio"/>
+</Menu>
+</MenuBar>
+
+<ToolBar name="General Toolbar">
+ <text>General Toolbar</text>
+
+ <Action name="add_audio" />
+ <Action name="export_audio" />
+ <Action name="remove_audio" />
+ <Action name="preview_audio" />
+ <Action name="insert_audio" />
+</ToolBar>
+
+<State name="have_audio_files">
+ <enable>
+ <Action name="remove_all_audio"/>
+ <Action name="remove_all_unused_audio"/>
+ <Action name="delete_unused_audio"/>
+ </enable>
+</State>
+
+<State name="have_audio_selected">
+ <enable>
+ <Action name="export_audio"/>
+ <Action name="remove_audio"/>
+ <Action name="rename_audio"/>
+ <Action name="distribute_audio"/>
+ </enable>
+</State>
+
+<State name="have_audible_preview">
+ <enable>
+ <Action name="preview_audio"/>
+ </enable>
+</State>
+
+<State name="have_audio_insertable">
+ <enable>
+ <Action name="insert_audio"/>
+ <enable>
+</State>
+
+</kpartgui>
diff --git a/src/gui/ui/bankeditor.rc b/src/gui/ui/bankeditor.rc
new file mode 100644
index 0000000..9d85e52
--- /dev/null
+++ b/src/gui/ui/bankeditor.rc
@@ -0,0 +1,22 @@
+<!DOCTYPE kpartgui SYSTEM "kpartgui.dtd">
+
+<kpartgui name="BankEditor" version="1230">
+
+<MenuBar>
+ <Menu name="file">
+ <Action name="file_close_discard"/>
+ </Menu>
+</MenuBar>
+
+<ToolBar name="mainToolBar">
+ <Action name="file_close"/>
+</ToolBar>
+
+<State name="on_bank_item">
+ <enable>
+ <Action name="edit_copy"/>
+ <Action name="edit_paste"/>
+ </enable>
+</State>
+
+</kpartgui>
diff --git a/src/gui/ui/clefinserter.rc b/src/gui/ui/clefinserter.rc
new file mode 100644
index 0000000..ade8806
--- /dev/null
+++ b/src/gui/ui/clefinserter.rc
@@ -0,0 +1,11 @@
+<!DOCTYPE kpartgui SYSTEM "kpartgui.dtd">
+
+<kpartgui name="Rosegarden::NotationTool" version="1230">
+
+<Menu noMerge="1" name="ClefInserter">
+ <Action name="select" />
+ <Action name="notes" />
+ <Action name="erase" />
+</Menu>
+
+</kpartgui>
diff --git a/src/gui/ui/controleditor.rc b/src/gui/ui/controleditor.rc
new file mode 100644
index 0000000..37b8aa6
--- /dev/null
+++ b/src/gui/ui/controleditor.rc
@@ -0,0 +1,5 @@
+<!DOCTYPE kpartgui SYSTEM "kpartgui.dtd">
+
+<kpartgui name="ControlEditor" version="1230">
+
+</kpartgui>
diff --git a/src/gui/ui/devicemanager.rc b/src/gui/ui/devicemanager.rc
new file mode 100644
index 0000000..27fc121
--- /dev/null
+++ b/src/gui/ui/devicemanager.rc
@@ -0,0 +1,5 @@
+<!DOCTYPE kpartgui SYSTEM "kpartgui.dtd">
+
+<kpartgui name="DeviceManager" version="1230">
+
+</kpartgui>
diff --git a/src/gui/ui/eventlist.rc b/src/gui/ui/eventlist.rc
new file mode 100644
index 0000000..96f8af0
--- /dev/null
+++ b/src/gui/ui/eventlist.rc
@@ -0,0 +1,105 @@
+<!DOCTYPE kpartgui SYSTEM "kpartgui.dtd">
+
+<kpartgui name="Rosegarden::EventList" version="1230">
+
+<MenuBar>
+
+<Menu name="file" append="new_merge">
+</Menu>
+
+<Menu name="edit" append="new_merge">
+ <Action name="insert"/>
+ <Action name="delete"/>
+ <Action name="edit_simple"/>
+ <Action name="edit_advanced"/>
+ <Separator/>
+ <Action name="select_all"/>
+ <Action name="clear_selection"/>
+ <Action name="filter_selection"/>
+ <Separator/>
+ <Action name="set_segment_start"/>
+ <Action name="set_segment_duration"/>
+</Menu>
+
+<Menu name="view"><text>&amp;View</text>
+ <Action name="time_musical"/>
+ <Action name="time_real"/>
+ <Action name="time_raw"/>
+</Menu>
+
+<Menu name="segment">
+ <text>Se&amp;gment</text>
+ <Menu name="open-with">
+ <text>Edit &amp;With</text>
+ <Action name="open_in_matrix"/>
+ <Action name="open_in_notation"/>
+ </Menu>
+</Menu>
+</MenuBar>
+
+<ToolBar name="Actions Toolbar">
+ <text>Actions Toolbar</text>
+ <Action name="insert"/>
+ <Action name="delete"/>
+ <Action name="edit_simple"/>
+ <Action name="edit_advanced"/>
+ <Action name="filter_selection"/>
+</ToolBar>
+
+<ToolBar name="Time Toolbar">
+ <text>Time Toolbar</text>
+ <Action name="time_musical"/>
+ <Action name="time_real"/>
+ <Action name="time_raw"/>
+</ToolBar>
+
+</kpartgui>
+
+<ToolBar name="Transport Toolbar">
+ <text>Transport Toolbar</text>
+
+ <Action name="playback_pointer_start" />
+ <Action name="playback_pointer_back_bar" />
+ <Action name="play" />
+ <Action name="playback_pointer_forward_bar" />
+ <Action name="playback_pointer_end" />
+ <Action name="stop" />
+ <Action name="playback_pointer_to_cursor" />
+ <Action name="cursor_to_playback_pointer" />
+ <Action name="toggle_solo"/>
+</ToolBar>
+
+<ToolBar name="Actions Toolbar">
+ <text>Actions Toolbar</text>
+</ToolBar>
+
+<ToolBar name="Zoom Toolbar">
+ <text>Zoom Toolbar</text>
+</ToolBar>
+
+<State name="have_selection">
+ <enable>
+ <Action name="edit_cut"/>
+ <Action name="edit_copy"/>
+ <Action name="delete"/>
+ <Action name="edit_simple"/>
+ <Action name="edit_advanced"/>
+ <Action name="clear_selection"/>
+ <Action name="filter_selection"/>
+ </enable>
+</State>
+
+<State name="have_clipboard_single_segment">
+ <enable>
+ <Action name="edit_paste"/>
+ </enable>
+</State>
+
+<State name="have_control_ruler">
+ <enable>
+ <Action name="show_control_rulers"/>
+ </enable>
+</State>
+
+</kpartgui>
+
diff --git a/src/gui/ui/markereditor.rc b/src/gui/ui/markereditor.rc
new file mode 100644
index 0000000..fd5d157
--- /dev/null
+++ b/src/gui/ui/markereditor.rc
@@ -0,0 +1,37 @@
+<!DOCTYPE kpartgui SYSTEM "kpartgui.dtd">
+
+<kpartgui name="MarkerEditor" version="1230">
+
+<MenuBar>
+
+ <Menu name="file">
+ <Action name="file_close_discard"/>
+ </Menu>
+
+ <Menu name="view"><text>&amp;View</text>
+ <Action name="time_musical"/>
+ <Action name="time_real"/>
+ <Action name="time_raw"/>
+ </Menu>
+
+</MenuBar>
+
+<ToolBar name="mainToolBar">
+ <Action name="file_close"/>
+</ToolBar>
+
+<ToolBar name="Time Toolbar">
+ <text>Time Toolbar</text>
+ <Action name="time_musical"/>
+ <Action name="time_real"/>
+ <Action name="time_raw"/>
+</ToolBar>
+
+<State name="on_control_item">
+ <enable>
+ <Action name="edit_insert"/>
+ <Action name="edit_delete"/>
+ </enable>
+</State>
+
+</kpartgui>
diff --git a/src/gui/ui/markerruler.rc b/src/gui/ui/markerruler.rc
new file mode 100644
index 0000000..6a2b9a5
--- /dev/null
+++ b/src/gui/ui/markerruler.rc
@@ -0,0 +1,14 @@
+<!DOCTYPE kpartgui SYSTEM "kpartgui.dtd">
+
+<kpartgui name="Rosegarden::MarkerRuler" version="1230">
+
+<Menu name="marker_ruler_menu">
+ <Action name="insert_marker_here" />
+ <Action name="insert_marker_at_pointer" />
+ <Separator/>
+ <Action name="delete_marker" />
+ <Separator/>
+ <Action name="edit_marker" />
+</Menu>
+
+</kpartgui>
diff --git a/src/gui/ui/matrix.rc b/src/gui/ui/matrix.rc
new file mode 100644
index 0000000..24b6159
--- /dev/null
+++ b/src/gui/ui/matrix.rc
@@ -0,0 +1,301 @@
+<!DOCTYPE kpartgui SYSTEM "kpartgui.dtd">
+
+<kpartgui name="Rosegarden::Matrix" version="1500">
+
+<MenuBar>
+
+<Menu name="file" append="new_merge">
+</Menu>
+
+<Menu name="edit">
+ <Action name="delete"/>
+ <Action name="select_all"/>
+ <Action name="clear_selection"/>
+ <Action name="filter_selection"/>
+</Menu>
+
+
+<Menu name="View">
+ <text>&amp;View</text>
+ <Menu name="Grid">
+ <text>&amp;Grid</text>
+ <Action name="snap_none"/>
+ <Action name="snap_unit"/>
+ <Action name="snap_64"/>
+ <Action name="snap_48"/>
+ <Action name="snap_32"/>
+ <Action name="snap_24"/>
+ <Action name="snap_16"/>
+ <Action name="snap_12"/>
+ <Action name="snap_8"/>
+ <Action name="snap_6"/>
+ <Action name="snap_4"/>
+ <Action name="snap_2"/>
+ <Action name="snap_beat"/>
+ <Action name="snap_bar"/>
+ </Menu>
+ <Separator/>
+ <Action name="show_velocity_control_ruler"/>
+ <Action name="show_controller_events_ruler"/>
+<!-- <Action name="add_control_ruler"/> -->
+ <Menu name="add_control_ruler"><text>Add Event Ruler</text></Menu>
+</Menu>
+
+<Menu name="document">
+ <text>&amp;Composition</text>
+ <Action name="add_tempo"/>
+ <Action name="add_time_signature"/>
+</Menu>
+<Menu name="segments">
+ <text>&amp;Segment</text>
+ <Menu name="open-with">
+ <text>Edit &amp;With</text>
+ <Action name="open_in_event_list"/>
+ <Action name="open_in_notation"/>
+ </Menu>
+ <Separator/>
+ <Action name="set_segment_start"/>
+ <Action name="set_segment_duration"/>
+</Menu>
+<Menu name="Transforms">
+ <text>Ad&amp;just</text>
+ <Menu name="quantize_notes">
+ <text>&amp;Quantize</text>
+ <Action name="quantize"/>
+ <Action name="repeat_quantize"/>
+ <Action name="legatoize"/>
+ </Menu>
+ <Action name="collapse_notes"/>
+ <Separator/>
+ <Action name="jog_left"/>
+ <Action name="jog_right"/>
+ <Separator/>
+ <Action name="velocity_up"/>
+ <Action name="velocity_down"/>
+ <Action name="set_to_current_velocity"/>
+ <Action name="set_velocities"/>
+ <Separator/>
+ <Menu name="Rescale">
+ <text>Rescale</text>
+ <Action name="halve_durations"/>
+ <Action name="double_durations"/>
+ <Separator/>
+ <Action name="rescale"/>
+ </Menu>
+ <Menu name="Transpose">
+ <text>Trans&amp;pose</text>
+ <Action name="transpose_up"/>
+ <Action name="transpose_down"/>
+ <Separator/>
+ <Action name="transpose_up_octave"/>
+ <Action name="transpose_down_octave"/>
+ <Separator/>
+ <Action name="general_transpose"/>
+ <Action name="general_diatonic_transpose"/>
+ </Menu>
+ <Menu name="Convert">
+ <text>Convert</text>
+ <Action name="invert"/>
+ <Action name="retrograde"/>
+ <Action name="retrograde_invert"/>
+ </Menu>
+ <Separator/>
+ <Action name="trigger_segment"/>
+ <Action name="remove_trigger"/>
+</Menu>
+
+
+<Menu name="Tools">
+ <text>&amp;Tools</text>
+ <Action name="select" />
+ <Action name="draw" />
+ <Action name="erase" />
+ <Action name="move" />
+ <Action name="resize" />
+ <Separator/>
+ <Menu name="Move">
+ <text>Local &amp;Cursor</text>
+ <Action name="cursor_back"/>
+ <Action name="cursor_forward"/>
+ <Action name="cursor_back_bar"/>
+ <Action name="cursor_forward_bar"/>
+ <Action name="cursor_start"/>
+ <Action name="cursor_end"/>
+ <Separator/>
+ <Action name="extend_selection_backward"/>
+ <Action name="extend_selection_forward"/>
+ <Action name="preview_selection"/>
+ <Action name="clear_loop"/>
+ <Separator/>
+ <Action name="cursor_to_playback_pointer"/>
+ <Action name="playback_pointer_to_cursor"/>
+ </Menu>
+
+ <Menu name="Transport">
+ <text>T&amp;ransport</text>
+ <Action name="play"/>
+ <Action name="stop"/>
+ <Action name="playback_pointer_back_bar"/>
+ <Action name="playback_pointer_forward_bar"/>
+ <Action name="playback_pointer_start"/>
+ <Action name="playback_pointer_end"/>
+ <Action name="playback_pointer_to_cursor" />
+ <Action name="cursor_to_playback_pointer" />
+ <Action name="toggle_solo"/>
+ <Action name="toggle_tracking"/>
+ <Action name="panic"/>
+ </Menu>
+
+ <Separator/>
+ <Action name="insert_note_actionmenu"/>
+ <Action name="chord_mode"/>
+
+ <Separator/>
+ <Action name="toggle_step_by_step"/>
+</Menu>
+
+<Menu name="settings" append="show_merge"><text>&amp;Settings</text>
+ <Menu name="toolbars" append="show_merge"><text>&amp;Toolbars</text>
+ <Action name="options_show_toolbar" append="show_merge" />
+ <Action name="show_tools_toolbar" append="show_merge" />
+ <Action name="options_show_statusbar" append="show_merge" />
+ </Menu>
+ <Menu name="rulers" append="show_merge"><text>&amp;Rulers</text>
+ <Action name="show_chords_ruler" append="show_merge" />
+ <Action name="show_tempo_ruler" append="show_merge" />
+ </Menu>
+ <Separator append="show_merge" />
+ <Action name="show_inst_parameters" append="show_merge" />
+</Menu>
+
+
+</MenuBar>
+
+<ToolBar name="Tools Toolbar">
+ <text>Tools Toolbar</text>
+
+ <Action name="select" />
+ <Action name="draw" />
+ <Action name="erase" />
+ <Action name="move" />
+ <Action name="resize" />
+ <Separator/>
+ <Action name="toggle_step_by_step"/>
+ <Action name="filter_selection"/>
+ <Action name="quantize"/>
+</ToolBar>
+
+<ToolBar name="Transport Toolbar">
+ <text>Transport Toolbar</text>
+
+ <Action name="playback_pointer_start" />
+ <Action name="playback_pointer_back_bar" />
+ <Action name="play" />
+ <Action name="playback_pointer_forward_bar" />
+ <Action name="playback_pointer_end" />
+ <Action name="stop" />
+ <Action name="playback_pointer_to_cursor" />
+ <Action name="cursor_to_playback_pointer" />
+ <Action name="toggle_solo"/>
+ <Action name="toggle_tracking"/>
+ <Action name="panic"/>
+</ToolBar>
+
+<ToolBar name="Actions Toolbar">
+ <text>Actions Toolbar</text>
+</ToolBar>
+
+<ToolBar name="Zoom Toolbar">
+ <text>Zoom Toolbar</text>
+</ToolBar>
+
+<Menu noMerge="1" name="controller_events_ruler_menu">
+ <Text>Control Ruler actions</Text>
+ <Action name="insert_control_ruler_item"/>
+ <Action name="erase_control_ruler_item"/>
+ <Action name="clear_control_ruler_item"/>
+ <Separator/>
+ <Action name="start_control_line_item"/>
+ <Separator/>
+ <Action name="flip_control_events_forward"/>
+ <Action name="flip_control_events_back"/>
+</Menu>
+
+<Menu noMerge="1" name="property_ruler_menu">
+ <Text>Property Ruler actions</Text>
+ <Action name="draw_property_line"/>
+ <Action name="select_all_properties"/>
+ <Separator/>
+ <Action name="flip_control_events_forward"/>
+ <Action name="flip_control_events_back"/>
+</Menu>
+
+
+<State name="have_selection">
+ <enable>
+ <Action name="edit_cut"/>
+ <Action name="edit_copy"/>
+ <Action name="quantize"/>
+ <Action name="collapse_notes"/>
+ <Action name="legatoize"/>
+ <Action name="rescale"/>
+ <Action name="double_durations"/>
+ <Action name="halve_durations"/>
+ <Action name="velocity_up"/>
+ <Action name="velocity_down"/>
+ <Action name="jog_left"/>
+ <Action name="jog_right"/>
+ <Action name="set_to_current_velocity"/>
+ <Action name="set_velocities"/>
+ <Action name="trigger_segment"/>
+ <Action name="remove_trigger"/>
+ <Action name="transpose_up"/>
+ <Action name="transpose_down"/>
+ <Action name="transpose_up_octave"/>
+ <Action name="transpose_down_octave"/>
+ <Action name="general_transpose"/>
+ <Action name="general_diatonic_transpose"/>
+ <Action name="invert"/>
+ <Action name="retrograde"/>
+ <Action name="retrograde_invert"/>
+ <Action name="preview_selection"/>
+ <Action name="clear_selection"/>
+ <Action name="filter_selection"/>
+ </enable>
+</State>
+
+<State name="have_clipboard_single_segment">
+ <enable>
+ <Action name="edit_paste"/>
+ </enable>
+</State>
+
+<State name="have_note_events_in_segment">
+ <enable>
+ <Action name="draw_property_line"/>
+ </enable>
+</State>
+
+<State name="have_control_ruler">
+ <enable>
+ <Action name="show_control_rulers"/>
+ </enable>
+</State>
+
+<State name="have_controller_item_selected">
+ <enable>
+ <Action name="erase_control_ruler_item"/>
+ </enable>
+ <disable>
+ <Action name="insert_control_ruler_item"/>
+ </disable>
+</State>
+
+<State name="parametersbox_closed">
+ <enable>
+ <Action name="show_inst_parameters" />
+ </enable>
+</State>
+
+</kpartgui>
+
diff --git a/src/gui/ui/matrixeraser.rc b/src/gui/ui/matrixeraser.rc
new file mode 100644
index 0000000..20694d4
--- /dev/null
+++ b/src/gui/ui/matrixeraser.rc
@@ -0,0 +1,15 @@
+<!DOCTYPE kpartgui SYSTEM "kpartgui.dtd">
+
+<kpartgui name="Rosegarden::MatrixTool" version="1230">
+
+<Menu name="MatrixEraser">
+ <Action name="select" />
+ <Action name="move"/>
+ <Action name="draw"/>
+ <Action name="resize"/>
+<!-- <Separator/> -->
+<!-- Defunct: <Action name="halve_durations"/> -->
+<!-- Defunct: <Action name="double_durations"/> -->
+</Menu>
+
+</kpartgui>
diff --git a/src/gui/ui/matrixmover.rc b/src/gui/ui/matrixmover.rc
new file mode 100644
index 0000000..2d0d555
--- /dev/null
+++ b/src/gui/ui/matrixmover.rc
@@ -0,0 +1,15 @@
+<!DOCTYPE kpartgui SYSTEM "kpartgui.dtd">
+
+<kpartgui name="Rosegarden::MatrixTool" version="1230">
+
+<Menu name="MatrixMover">
+ <Action name="select"/>
+ <Action name="draw" />
+ <Action name="erase"/>
+ <Action name="resize"/>
+<!-- <Separator/> -->
+<!-- Defunct: <Action name="halve_durations"/> -->
+<!-- Defunct: <Action name="double_durations"/> -->
+</Menu>
+
+</kpartgui>
diff --git a/src/gui/ui/matrixpainter.rc b/src/gui/ui/matrixpainter.rc
new file mode 100644
index 0000000..9b4cbce
--- /dev/null
+++ b/src/gui/ui/matrixpainter.rc
@@ -0,0 +1,22 @@
+<!DOCTYPE kpartgui SYSTEM "kpartgui.dtd">
+
+<kpartgui name="Rosegarden::MatrixTool" version="1230">
+
+<Menu name="MatrixPainter">
+ <Action name="select" />
+ <Action name="move"/>
+ <Action name="erase"/>
+ <Action name="resize"/>
+<!-- <Separator/> -->
+<!-- Defunct: <Action name="halve_durations"/> -->
+<!-- Defunct: <Action name="double_durations"/> -->
+</Menu>
+
+<!-- Defunct: <State name="have_selection"> -->
+<!-- <enable> -->
+<!-- <Action name="halve_durations"/> -->
+<!-- <Action name="double_durations"/> -->
+<!-- </enable> -->
+<!--</State> -->
+
+</kpartgui>
diff --git a/src/gui/ui/matrixresizer.rc b/src/gui/ui/matrixresizer.rc
new file mode 100644
index 0000000..8d85634
--- /dev/null
+++ b/src/gui/ui/matrixresizer.rc
@@ -0,0 +1,15 @@
+<!DOCTYPE kpartgui SYSTEM "kpartgui.dtd">
+
+<kpartgui name="Rosegarden::MatrixTool" version="1230">
+
+<Menu name="MatrixResizer">
+ <Action name="select" />
+ <Action name="move"/>
+ <Action name="draw"/>
+ <Action name="erase"/>
+<!-- <Separator/> -->
+<!-- Defunct: <Action name="halve_durations"/> -->
+<!-- Defunct: <Action name="double_durations"/> -->
+</Menu>
+
+</kpartgui>
diff --git a/src/gui/ui/matrixselector.rc b/src/gui/ui/matrixselector.rc
new file mode 100644
index 0000000..cb8ec41
--- /dev/null
+++ b/src/gui/ui/matrixselector.rc
@@ -0,0 +1,15 @@
+<!DOCTYPE kpartgui SYSTEM "kpartgui.dtd">
+
+<kpartgui name="Rosegarden::MatrixTool" version="1230">
+
+<Menu name="MatrixSelector">
+ <Action name="draw" />
+ <Action name="move"/>
+ <Action name="erase"/>
+ <Action name="resize"/>
+<!-- <Separator/> -->
+<!-- Defunct: <Action name="halve_durations"/> -->
+<!-- Defunct: <Action name="double_durations"/> -->
+</Menu>
+
+</kpartgui>
diff --git a/src/gui/ui/midimixer.rc b/src/gui/ui/midimixer.rc
new file mode 100644
index 0000000..6ac7bb4
--- /dev/null
+++ b/src/gui/ui/midimixer.rc
@@ -0,0 +1,34 @@
+<!DOCTYPE kpartgui SYSTEM "kpartgui.dtd">
+
+<kpartgui name="Rosegarden::MidiMixer" version="1230">
+
+<MenuBar>
+
+ <Menu name="Transport">
+ <text>T&amp;ransport</text>
+ <Action name="play" />
+ <Action name="stop" />
+ <Action name="playback_pointer_back_bar" />
+ <Action name="playback_pointer_forward_bar" />
+ <Action name="playback_pointer_start" />
+ <Action name="playback_pointer_end" />
+ <Action name="record" />
+ <Action name="panic" />
+ </Menu>
+
+</MenuBar>
+
+<ToolBar name="Transport Toolbar">
+ <text>Transport Toolbar</text>
+ <Action name="playback_pointer_start" />
+ <Action name="playback_pointer_back_bar" />
+ <Action name="play" />
+ <Action name="playback_pointer_forward_bar" />
+ <Action name="playback_pointer_end" />
+ <Action name="stop" />
+ <Action name="record" />
+ <Action name="panic" />
+</ToolBar>
+
+</kpartgui>
+
diff --git a/src/gui/ui/mixer.rc b/src/gui/ui/mixer.rc
new file mode 100644
index 0000000..6dcef9f
--- /dev/null
+++ b/src/gui/ui/mixer.rc
@@ -0,0 +1,65 @@
+<!DOCTYPE kpartgui SYSTEM "kpartgui.dtd">
+
+<kpartgui name="Rosegarden::Mixer" version="1230">
+
+<MenuBar>
+
+ <Menu name="Transport">
+ <text>T&amp;ransport</text>
+ <Action name="play" />
+ <Action name="stop" />
+ <Action name="playback_pointer_back_bar" />
+ <Action name="playback_pointer_forward_bar" />
+ <Action name="playback_pointer_start" />
+ <Action name="playback_pointer_end" />
+ <Action name="record" />
+ <Action name="panic" />
+ </Menu>
+
+<Menu name="settings"><text>&amp;Settings</text>
+
+ <Action name="show_midi" append="show_merge" />
+ <Action name="show_audio_faders" append="show_merge" />
+ <Action name="show_synth_faders" append="show_merge" />
+ <Action name="show_audio_submasters" append="show_merge" />
+ <Action name="show_plugin_buttons" append="show_merge" />
+
+ <Separator append="show_merge"/>
+
+ <Action name="show_unassigned_faders" append="show_merge" />
+
+ <Menu name="Inputs">
+ <text>Number of Stereo &amp;Inputs</text>
+ <Action name="inputs_1"/>
+ <Action name="inputs_2"/>
+ <Action name="inputs_4"/>
+ <Action name="inputs_8"/>
+ <Action name="inputs_16"/>
+ </Menu>
+
+ <Menu name="Submasters">
+ <text>&amp;Number of Submasters</text>
+ <Action name="submasters_0"/>
+ <Action name="submasters_2"/>
+ <Action name="submasters_4"/>
+ <Action name="submasters_8"/>
+ </Menu>
+
+</Menu>
+
+</MenuBar>
+
+<ToolBar name="Transport Toolbar">
+ <text>Transport Toolbar</text>
+ <Action name="playback_pointer_start" />
+ <Action name="playback_pointer_back_bar" />
+ <Action name="play" />
+ <Action name="playback_pointer_forward_bar" />
+ <Action name="playback_pointer_end" />
+ <Action name="stop" />
+ <Action name="record" />
+ <Action name="panic" />
+</ToolBar>
+
+</kpartgui>
+
diff --git a/src/gui/ui/notation.rc b/src/gui/ui/notation.rc
new file mode 100644
index 0000000..be6f23b
--- /dev/null
+++ b/src/gui/ui/notation.rc
@@ -0,0 +1,853 @@
+<!DOCTYPE kpartgui SYSTEM "kpartgui.dtd">
+
+<kpartgui name="Rosegarden::Notation" version="1500">
+
+<MenuBar>
+
+<Menu name="file" append="new_merge">
+<Action name="file_print_lilypond" append="print_merge" />
+<Action name="file_preview_lilypond" append="print_merge" />
+</Menu>
+
+<Menu name="edit">
+ <Action name="cut_and_close"/>
+ <Action name="general_paste"/>
+ <Action name="delete"/>
+ <Separator/>
+ <Action name="move_events_up_staff"/>
+ <Action name="move_events_down_staff"/>
+ <Separator/>
+ <Action name="select_from_start"/>
+ <Action name="select_to_end"/>
+ <Action name="select_whole_staff"/>
+ <Action name="clear_selection"/>
+ <Action name="filter_selection"/>
+</Menu>
+
+<Menu name="View">
+ <text>&amp;View</text>
+<!--
+<Action name="debug_dump"/>
+<Separator/>
+-->
+ <Action name="note_font_actionmenu"/>
+ <Action name="note_font_size_actionmenu"/>
+ <Action name="stretch_actionmenu"/>
+ <Action name="proportion_actionmenu"/>
+<Separator/>
+ <Menu name="layout"><text>&amp;Layout Mode</text>
+ <Action name="linear_mode"/>
+ <Action name="continuous_page_mode"/>
+ <Action name="multi_page_mode"/>
+ </Menu>
+<Separator/>
+ <Action name="lyric_editor"/>
+ <Separator/>
+ <Action name="show_velocity_control_ruler"/>
+ <Action name="show_controller_events_ruler"/>
+<!-- <Action name="add_control_ruler"/> -->
+ <Menu name="add_control_ruler"><text>Add Event Ruler</text></Menu>
+ <Separator/>
+ <Action name="show_track_headers"/>
+</Menu>
+
+<Menu name="document">
+ <text>&amp;Composition</text>
+ <Action name="add_tempo"/>
+ <Action name="add_time_signature"/>
+</Menu>
+
+<Menu name="segment">
+ <text>Se&amp;gment</text>
+ <Menu name="open-with">
+ <text>Edit &amp;With</text>
+ <Action name="open_in_event_list"/>
+ <Action name="open_in_matrix"/>
+ <Action name="open_in_percussion_matrix"/>
+ </Menu>
+ <Separator/>
+ <Action name="add_clef"/>
+ <Action name="add_key_signature"/>
+ <Separator/>
+ <Action name="add_sustain_down"/>
+ <Action name="add_sustain_up"/>
+ <Separator/>
+ <Action name="set_segment_start"/>
+ <Action name="set_segment_duration"/>
+ <Separator/>
+ <Action name="transpose_segment"/>
+ <Action name="switch_preset"/>
+</Menu>
+
+
+<Menu name="Notes">
+ <text>N&amp;ote</text>
+ <Separator/>
+ <Menu name="Marks">
+ <text>Mar&amp;ks</text>
+ <Action name="add_accent"/>
+ <Action name="add_tenuto"/>
+ <Action name="add_staccato"/>
+ <Action name="add_staccatissimo"/>
+ <Action name="add_marcato"/>
+ <Action name="add_text_sf"/>
+ <Action name="add_text_rf"/>
+ <Separator/>
+ <Action name="add_trill"/>
+ <Action name="add_long-trill"/>
+ <Action name="add_trill-line"/>
+ <Action name="add_turn"/>
+ <Action name="add_mordent"/>
+ <Action name="add_mordent-inverted"/>
+ <Action name="add_mordent-long"/>
+ <Action name="add_mordent-long-inverted"/>
+ <Separator/>
+ <Action name="add_up-bow"/>
+ <Action name="add_down-bow"/>
+ <Action name="add_pause"/>
+ <Separator/>
+ <Action name="add_text_mark"/>
+ <Separator/>
+ <Action name="remove_marks"/>
+ </Menu>
+ <Menu name="ornaments">
+ <text>&amp;Ornaments</text>
+ <Action name="use_ornament"/>
+ <Action name="remove_ornament"/>
+ <Action name="make_ornament"/>
+ <Action name="ornament_actionmenu"/>
+ </Menu>
+ <Separator/>
+ <Menu name="Fingering">
+ <text>&amp;Fingerings</text>
+ <Action name="remove_fingering_marks"/>
+ <Separator/>
+ <Action name="add_fingering_0"/>
+ <Action name="add_fingering_1"/>
+ <Action name="add_fingering_2"/>
+ <Action name="add_fingering_3"/>
+ <Action name="add_fingering_4"/>
+ <Action name="add_fingering_5"/>
+ <Action name="add_fingering_plus"/>
+ <Action name="add_fingering_mark"/>
+ </Menu>
+ <Menu name="Slashes">
+ <text>S&amp;lashes</text>
+ <Action name="slashes_0"/>
+ <Action name="slashes_1"/>
+ <Action name="slashes_2"/>
+ <Action name="slashes_3"/>
+ <Action name="slashes_4"/>
+ <Action name="slashes_5"/>
+ </Menu>
+ <Action name="note_style_actionmenu"/>
+ <Separator/>
+ <Menu name="Respell">
+ <text>&amp;Accidentals</text>
+ <Action name="respell_restore"/>
+ <Separator/>
+ <Action name="respell_doubleflat"/>
+ <Action name="respell_flat"/>
+ <Action name="respell_natural"/>
+ <Action name="respell_sharp"/>
+ <Action name="respell_doublesharp"/>
+ <Separator/>
+ <Action name="show_cautionary"/>
+ <Action name="cancel_cautionary"/>
+ </Menu>
+ <Separator/>
+ <Action name="stems_up"/>
+ <Action name="stems_down"/>
+ <Action name="restore_stems"/>
+</Menu>
+
+<Menu name="Phrase">
+ <text>&amp;Phrase</text>
+ <Action name="make_chord"/>
+ <Separator/>
+ <Action name="beam"/>
+ <Action name="auto_beam"/>
+ <Action name="break_group"/>
+ <Action name="remove_indications"/>
+ <Separator/>
+ <Action name="tuplet"/>
+ <Action name="simple_tuplet"/>
+ <Action name="break_tuplets"/>
+ <Separator/>
+ <Action name="grace"/>
+ <Separator/>
+ <Action name="slur"/>
+ <Action name="phrasing_slur"/>
+ <!-- <Action name="glissando"/> -->
+ <Menu name="Slurs">
+ <text>Slur &amp;Position</text>
+ <Action name="restore_slurs"/>
+ <Separator/>
+ <Action name="slurs_above"/>
+ <Action name="slurs_below"/>
+ </Menu>
+ <Separator/>
+ <Action name="tie_notes"/>
+ <Action name="untie_notes"/>
+ <Menu name="Ties">
+ <text>Tie &amp;Position</text>
+ <Action name="restore_ties"/>
+ <Separator/>
+ <Action name="ties_above"/>
+ <Action name="ties_below"/>
+ </Menu>
+ <Separator/>
+ <Action name="crescendo"/>
+ <Action name="decrescendo"/>
+ <Separator/>
+ <Menu name="octaves">
+ <text>&amp;Octaves</text>
+ <Action name="octave_2up"/>
+ <Action name="octave_up"/>
+ <Action name="octave_down"/>
+ <Action name="octave_2down"/>
+ </Menu>
+</Menu>
+
+<Menu name="Adjust">
+ <text>Ad&amp;just</text>
+ <Menu name="rests">
+ <text>R&amp;ests</text>
+ <Action name="normalize_rests"/>
+ <Action name="collapse_rests_aggressively"/>
+ </Menu>
+ <Menu name="transform_notes">
+ <text>&amp;Notes</text>
+ <Action name="collapse_notes"/>
+ <Action name="make_notes_viable"/>
+ <Action name="de_counterpoint"/>
+ </Menu>
+ <Separator/>
+ <Menu name="quantize_notes">
+ <text>&amp;Quantize</text>
+ <Action name="quantize"/>
+ <Action name="fix_quantization"/>
+ <Action name="remove_quantization"/>
+ </Menu>
+ <Action name="interpret"/>
+ <Separator/>
+ <Menu name="Rescale">
+ <text>Rescale</text>
+ <Action name="halve_durations"/>
+ <Action name="double_durations"/>
+ <Separator/>
+ <Action name="rescale"/>
+ </Menu>
+ <Menu name="Transpose">
+ <text>Trans&amp;pose</text>
+ <Action name="transpose_up"/>
+ <Action name="transpose_down"/>
+ <Separator/>
+ <Action name="transpose_up_octave"/>
+ <Action name="transpose_down_octave"/>
+ <Separator/>
+ <Action name="general_transpose"/>
+ <Action name="general_diatonic_transpose"/>
+ </Menu>
+ <Menu name="Convert">
+ <text>Convert</text>
+ <Action name="invert"/>
+ <Action name="retrograde"/>
+ <Action name="retrograde_invert"/>
+ </Menu>
+ <Separator/>
+ <Menu name="fine_positioning">
+ <text>&amp;Fine Positioning</text>
+ <Action name="fine_position_restore"/>
+ <Separator/>
+ <Action name="fine_position_left"/>
+ <Action name="fine_position_right"/>
+ <Action name="fine_position_up"/>
+ <Action name="fine_position_down"/>
+ </Menu>
+ <Menu name="fine_timing">
+ <text>Fine Ti&amp;ming</text>
+ <Action name="jog_left"/>
+ <Action name="jog_right"/>
+ </Menu>
+ <Menu name="visibility">
+ <text>&amp;Visibility</text>
+ <Action name="make_invisible"/>
+ <Action name="make_visible"/>
+ </Menu>
+</Menu>
+
+<Menu name="Tools">
+ <text>&amp;Tools</text>
+ <Action name="select"/>
+ <Action name="erase"/>
+ <Menu noMerge="1" name="Notes">
+ <text>&amp;Notes</text>
+ <!-- Reference names, but with _ instead of - -->
+ <Action name="breve" />
+ <Action name="semibreve" />
+ <Action name="minim" />
+ <Action name="crotchet" />
+ <Action name="quaver" />
+ <Action name="semiquaver" />
+ <Action name="demisemi" />
+ <Action name="hemidemisemi" />
+ <Separator/>
+ <Action name="dotted_semibreve" />
+ <Action name="dotted_minim" />
+ <Action name="dotted_crotchet" />
+ <Action name="dotted_quaver" />
+ <Action name="dotted_semiquaver" />
+ <Action name="dotted_demisemi" />
+ <Separator/>
+ <Action name="switch_from_rest_to_note" />
+ </Menu>
+
+ <Menu noMerge="1" name="Rests">
+ <text>&amp;Rests</text>
+ <!-- Reference names, but with _ instead of - -->
+ <Action name="rest_breve" />
+ <Action name="rest_semibreve" />
+ <Action name="rest_minim" />
+ <Action name="rest_crotchet" />
+ <Action name="rest_quaver" />
+ <Action name="rest_semiquaver" />
+ <Action name="rest_demisemi" />
+ <Action name="rest_hemidemisemi" />
+ <Separator/>
+ <Action name="dotted_rest_semibreve" />
+ <Action name="dotted_rest_minim" />
+ <Action name="dotted_rest_crotchet" />
+ <Action name="dotted_rest_quaver" />
+ <Action name="dotted_rest_semiquaver" />
+ <Action name="dotted_rest_demisemi" />
+ <Separator/>
+ <Action name="switch_from_note_to_rest" />
+ </Menu>
+
+ <Menu noMerge="1" name="Accidentals">
+ <text>&amp;Accidentals</text>
+ <Action name="no_accidental" />
+ <Action name="follow_accidental" />
+ <Action name="sharp_accidental" />
+ <Action name="flat_accidental" />
+ <Action name="natural_accidental" />
+ <Action name="double_sharp_accidental" />
+ <Action name="double_flat_accidental" />
+ </Menu>
+
+ <Menu noMerge="1" name="Clefs">
+ <text>&amp;Clefs</text>
+ <Action name="treble_clef" />
+ <Action name="alto_clef" />
+ <Action name="tenor_clef" />
+ <Action name="bass_clef" />
+ </Menu>
+ <Separator/>
+ <Action name="text" />
+ <Action name="guitarchord" />
+ <Separator/>
+ <Menu name="Move">
+ <text>Local &amp;Cursor</text>
+ <Action name="cursor_back"/>
+ <Action name="cursor_forward"/>
+ <Action name="cursor_back_bar"/>
+ <Action name="cursor_forward_bar"/>
+ <Action name="cursor_start"/>
+ <Action name="cursor_end"/>
+ <Separator/>
+ <Action name="extend_selection_backward"/>
+ <Action name="extend_selection_forward"/>
+ <Action name="preview_selection"/>
+ <Action name="clear_loop"/>
+ <!-- not implemented yet
+ <Separator/>
+ <Action name="move_selection_left"/>
+ <Action name="move_selection_right"/>
+ <Action name="copy_selection_left"/>
+ <Action name="copy_selection_right"/>
+ -->
+ <Separator/>
+ <Action name="cursor_to_playback_pointer"/>
+ <Action name="playback_pointer_to_cursor"/>
+ <Separator/>
+ <Action name="cursor_up_staff"/>
+ <Action name="cursor_down_staff"/>
+ <Action name="cursor_prior_segment"/>
+ <Action name="cursor_next_segment"/>
+ </Menu>
+
+ <Menu name="Transport">
+ <text>T&amp;ransport</text>
+ <Action name="play"/>
+ <Action name="stop"/>
+ <Action name="playback_pointer_back_bar"/>
+ <Action name="playback_pointer_forward_bar"/>
+ <Action name="playback_pointer_start"/>
+ <Action name="playback_pointer_end"/>
+ <Action name="playback_pointer_to_cursor" />
+ <Action name="cursor_to_playback_pointer" />
+ <Action name="toggle_solo"/>
+ <Action name="toggle_tracking"/>
+ <Action name="panic"/>
+ </Menu>
+
+ <Separator/>
+
+ <Action name="insert_note_actionmenu"/>
+ <Action name="insert_rest"/>
+
+ <!--
+ <Action name="insert_melody_mode"/>
+ <Action name="insert_chord_mode"/>
+ -->
+
+ <Action name="chord_mode"/>
+ <Action name="triplet_mode"/>
+ <Action name="grace_mode"/>
+
+ <Separator/>
+ <Action name="toggle_step_by_step"/>
+
+</Menu>
+
+<Menu name="settings">
+ <text>&amp;Settings</text>
+ <Menu name="toolbars" append="show_merge" >
+ <text>&amp;Toolbars</text>
+ <Action name="options_show_toolbar" append="show_merge" />
+ <Action name="show_tools_toolbar" append="show_merge" />
+ <Action name="show_notes_toolbar" append="show_merge" />
+ <Action name="show_rests_toolbar" append="show_merge" />
+ <Action name="show_accidentals_toolbar" append="show_merge" />
+ <Action name="show_clefs_toolbar" append="show_merge" />
+ <Action name="show_marks_toolbar" append="show_merge" />
+ <Action name="show_group_toolbar" append="show_merge" />
+ <Action name="show_transport_toolbar" append="show_merge" />
+ <Action name="show_layout_toolbar" append="show_merge" />
+ <Action name="show_meta_toolbar" append="show_merge" />
+ <Action name="options_show_statusbar" append="show_merge" />
+ </Menu>
+ <Menu name="rulers" append="show_merge" >
+ <text>&amp;Rulers</text>
+ <Action name="show_chords_ruler" append="show_merge" />
+ <Action name="show_raw_note_ruler" append="show_merge" />
+ <Action name="show_tempo_ruler" append="show_merge" />
+ </Menu>
+ <Separator append="show_merge" />
+ <Action name="show_annotations" append="show_merge" />
+ <Action name="show_lilypond_directives" append="show_merge" />
+</Menu>
+
+</MenuBar>
+
+<ToolBar name="Tools Toolbar">
+ <text>Tools Toolbar</text>
+ <Action name="select" />
+ <Action name="erase" />
+ <Action name="text" />
+ <Action name="guitarchord" />
+ <Separator/>
+ <Action name="toggle_step_by_step"/>
+ <Action name="filter_selection"/>
+ <Action name="quantize"/>
+</ToolBar>
+
+<ToolBar name="Transport Toolbar">
+ <text>Transport Toolbar</text>
+ <Action name="playback_pointer_start" />
+ <Action name="playback_pointer_back_bar" />
+ <Action name="play" />
+ <Action name="playback_pointer_forward_bar" />
+ <Action name="playback_pointer_end" />
+ <Action name="stop" />
+ <Action name="playback_pointer_to_cursor" />
+ <Action name="cursor_to_playback_pointer" />
+ <Action name="toggle_solo"/>
+ <Action name="toggle_tracking"/>
+ <Action name="panic"/>
+</ToolBar>
+
+<ToolBar name="Notes Toolbar" newline="true" hidden="false" position="left">
+ <text>Notes Toolbar</text>
+
+ <!-- Reference names, but with _ instead of - -->
+ <Action name="breve" />
+ <Action name="semibreve" />
+ <Action name="minim" />
+ <Action name="crotchet" />
+ <Action name="quaver" />
+ <Action name="semiquaver" />
+ <Action name="demisemi" />
+ <Action name="hemidemisemi" />
+ <Separator/>
+ <Action name="dotted_semibreve" />
+ <Action name="dotted_minim" />
+ <Action name="dotted_crotchet" />
+ <Action name="dotted_quaver" />
+ <Action name="dotted_semiquaver" />
+ <Action name="dotted_demisemi" />
+
+ <Separator/>
+
+ <Action name="chord_mode"/>
+ <Action name="triplet_mode" />
+ <Action name="grace_mode" />
+</ToolBar>
+
+<ToolBar name="Rests Toolbar" hidden="false" position="left">
+ <text>Rests Toolbar</text>
+
+ <!-- Reference names, but with _ instead of - -->
+ <Action name="rest_breve" />
+ <Action name="rest_semibreve" />
+ <Action name="rest_minim" />
+ <Action name="rest_crotchet" />
+ <Action name="rest_quaver" />
+ <Action name="rest_semiquaver" />
+ <Action name="rest_demisemi" />
+ <Action name="rest_hemidemisemi" />
+ <Separator/>
+ <Action name="dotted_rest_semibreve" />
+ <Action name="dotted_rest_minim" />
+ <Action name="dotted_rest_crotchet" />
+ <Action name="dotted_rest_quaver" />
+ <Action name="dotted_rest_semiquaver" />
+ <Action name="dotted_rest_demisemi" />
+
+</ToolBar>
+
+<ToolBar name="Clefs Toolbar" hidden="false" position="left" newline="true">
+ <text>Clefs Toolbar</text>
+
+ <Action name="treble_clef" />
+ <Action name="alto_clef" />
+ <Action name="tenor_clef" />
+ <Action name="bass_clef" />
+</ToolBar>
+
+<ToolBar name="Accidentals Toolbar" hidden="false" position="left">
+ <text>Accidentals Toolbar</text>
+
+ <Action name="no_accidental" />
+ <Action name="follow_accidental" />
+ <Action name="sharp_accidental" />
+ <Action name="flat_accidental" />
+ <Action name="natural_accidental" />
+ <Action name="double_sharp_accidental" />
+ <Action name="double_flat_accidental" />
+</ToolBar>
+
+<ToolBar name="Group Toolbar" hidden="false" position="right">
+ <text>Group Toolbar</text>
+
+ <Action name="make_chord"/>
+ <Action name="beam"/>
+ <Action name="break_group"/>
+ <Separator/>
+ <Action name="tuplet"/>
+ <Action name="simple_tuplet"/>
+<!-- <Action name="grace"/> -->
+ <Separator/>
+ <Action name="slur"/>
+<!-- <Action name="glissando"/> -->
+ <Action name="tie_notes"/>
+ <Action name="de_counterpoint"/>
+ <Separator/>
+ <Action name="crescendo"/>
+ <Action name="decrescendo"/>
+ <Action name="octave_up"/>
+</ToolBar>
+
+<ToolBar name="Marks Toolbar" newline="true" hidden="false" position="right">
+ <text>Marks Toolbar</text>
+
+ <Action name="add_accent"/>
+ <Action name="add_tenuto"/>
+ <Action name="add_staccato"/>
+ <Action name="add_staccatissimo"/>
+ <Action name="add_marcato"/>
+ <Action name="add_text_sf"/>
+ <Action name="add_text_rf"/>
+ <Action name="add_trill"/>
+ <Action name="add_long-trill"/>
+ <Action name="add_turn"/>
+ <Action name="add_pause"/>
+ <Action name="add_up-bow"/>
+ <Action name="add_down-bow"/>
+ <Action name="add_mordent"/>
+ <Action name="add_mordent-inverted"/>
+ <Action name="add_mordent-long"/>
+ <Action name="add_mordent-long-inverted"/>
+ <Action name="add_text_mark"/>
+</ToolBar>
+
+<ToolBar name="Meta Toolbar" newline="true" hidden="true">
+ <text>Meta Toolbar</text>
+
+ <Action name="show_notes_toolbar" />
+ <Action name="show_rests_toolbar" />
+ <Action name="show_accidentals_toolbar" />
+ <Action name="show_clefs_toolbar" />
+ <Action name="show_marks_toolbar" />
+ <Action name="show_group_toolbar" />
+</ToolBar>
+
+<ToolBar name="Layout Toolbar" hidden="true">
+ <text>Layout Toolbar</text>
+ <Action name="linear_mode" />
+ <Action name="continuous_page_mode" />
+ <Action name="multi_page_mode" />
+<!-- the remainder filled at runtime -->
+</ToolBar>
+
+<Menu noMerge="1" name="controller_events_ruler_menu">
+ <Text>Control Ruler actions</Text>
+ <Action name="insert_control_ruler_item"/>
+ <Action name="erase_control_ruler_item"/>
+ <Action name="clear_control_ruler_item"/>
+ <Separator/>
+ <Action name="start_control_line_item"/>
+ <Separator/>
+ <Action name="flip_control_events_forward"/>
+ <Action name="flip_control_events_back"/>
+</Menu>
+
+<Menu noMerge="1" name="property_ruler_menu">
+ <Text>Property Ruler actions</Text>
+ <Action name="draw_property_line"/>
+ <Action name="select_all_properties"/>
+ <Separator/>
+ <Action name="flip_control_events_forward"/>
+ <Action name="flip_control_events_back"/>
+</Menu>
+
+
+
+<State name="note_insert_tool_current">
+ <enable>
+ <Action name="insert_rest"/>
+ <Action name="insert_note_actionmenu"/>
+ <Action name="switch_from_note_to_rest"/>
+ </enable>
+</State>
+
+<State name="rest_insert_tool_current">
+ <enable>
+ <Action name="insert_rest"/>
+ <Action name="switch_from_rest_to_note"/>
+ </enable>
+</State>
+
+<State name="have_selection">
+ <enable>
+ <Action name="edit_cut"/>
+ <Action name="edit_copy"/>
+ <Action name="delete"/>
+ <Action name="cut_and_close"/>
+ <Action name="debug_dump"/>
+ <Action name="preview_selection"/>
+ <Action name="normalize_rests"/>
+ <Action name="clear_selection"/>
+ <Action name="filter_selection"/>
+ <Action name="jog_left"/>
+ <Action name="jog_right"/>
+ <Action name="slurs_above"/>
+ <Action name="slurs_below"/>
+ <Action name="restore_slurs"/>
+ <Action name="ties_above"/>
+ <Action name="ties_below"/>
+ <Action name="restore_ties"/>
+ <Action name="fine_position_left"/>
+ <Action name="fine_position_right"/>
+ <Action name="fine_position_up"/>
+ <Action name="fine_position_down"/>
+ <Action name="fine_position_restore"/>
+ <Action name="tuplet"/>
+ <Action name="simple_tuplet"/>
+ <Action name="break_tuplets"/>
+ <Action name="make_invisible"/>
+ <Action name="make_visible"/>
+ <Action name="move_events_up_staff"/>
+ <Action name="move_events_down_staff"/>
+ </enable>
+</State>
+
+<State name="have_rests_in_selection">
+ <enable>
+ <Action name="note_style_actionmenu"/>
+ <Action name="remove_marks"/>
+ <Action name="add_pause"/>
+ <Action name="add_text_mark"/>
+<!-- <Action name="normalize_rests"/> -->
+ <Action name="collapse_rests_aggressively"/>
+ <Menu name="Adjust"/>
+ </enable>
+</State>
+
+<State name="have_notes_in_selection">
+ <enable>
+ <Action name="respell_doubleflat"/>
+ <Action name="respell_flat"/>
+ <Action name="respell_natural"/>
+ <Action name="respell_sharp"/>
+ <Action name="respell_doublesharp"/>
+ <Action name="respell_restore"/>
+ <Action name="show_cautionary"/>
+ <Action name="cancel_cautionary"/>
+ <Action name="make_chord"/>
+ <Action name="beam"/>
+ <Action name="auto_beam"/>
+ <Action name="break_group"/>
+<!-- <Action name="break_tuplets"/> -->
+<!-- <Action name="grace"/> -->
+<!-- <Action name="ungrace"/> -->
+ <Action name="slur"/>
+ <Action name="phrasing_slur"/>
+ <Action name="crescendo"/>
+ <Action name="decrescendo"/>
+<!-- <Action name="glissando"/> -->
+ <Action name="octave_2up"/>
+ <Action name="octave_up"/>
+ <Action name="octave_down"/>
+ <Action name="octave_2down"/>
+<!-- <Action name="remove_indications"/> -->
+ <Action name="stems_up"/>
+ <Action name="stems_down"/>
+ <Action name="restore_stems"/>
+ <Action name="slashes_0"/>
+ <Action name="slashes_1"/>
+ <Action name="slashes_2"/>
+ <Action name="slashes_3"/>
+ <Action name="slashes_4"/>
+ <Action name="slashes_5"/>
+ <Action name="note_style_actionmenu"/>
+ <Action name="remove_marks"/>
+ <Action name="remove_fingering_marks"/>
+ <Action name="add_fingering_0"/>
+ <Action name="add_fingering_1"/>
+ <Action name="add_fingering_2"/>
+ <Action name="add_fingering_3"/>
+ <Action name="add_fingering_4"/>
+ <Action name="add_fingering_5"/>
+ <Action name="add_fingering_plus"/>
+ <Action name="add_fingering_mark"/>
+ <Action name="add_accent"/>
+ <Action name="add_tenuto"/>
+ <Action name="add_staccato"/>
+ <Action name="add_staccatissimo"/>
+ <Action name="add_marcato"/>
+ <Action name="add_text_sf"/>
+ <Action name="add_text_rf"/>
+ <Action name="add_trill"/>
+ <Action name="add_long-trill"/>
+ <Action name="add_trill-line"/>
+ <Action name="add_turn"/>
+ <Action name="add_pause"/>
+ <Action name="add_up-bow"/>
+ <Action name="add_down-bow"/>
+ <Action name="add_mordent"/>
+ <Action name="add_mordent-inverted"/>
+ <Action name="add_mordent-long"/>
+ <Action name="add_mordent-long-inverted"/>
+ <Action name="add_text_mark"/>
+ <Action name="make_ornament"/>
+ <Action name="remove_ornament"/>
+ <Action name="use_ornament"/>
+ <Action name="collapse_notes"/>
+ <Action name="tie_notes"/>
+ <Action name="untie_notes"/>
+ <Action name="make_notes_viable"/>
+ <Action name="de_counterpoint"/>
+ <Action name="quantize"/>
+ <Action name="interpret"/>
+ <Action name="fix_quantization"/>
+ <Action name="remove_quantization"/>
+ <Action name="transpose_up"/>
+ <Action name="transpose_down"/>
+ <Action name="transpose_up_octave"/>
+ <Action name="transpose_down_octave"/>
+ <Action name="general_transpose"/>
+ <Action name="general_diatonic_transpose"/>
+ <Action name="invert"/>
+ <Action name="retrograde"/>
+ <Action name="retrograde_invert"/>
+ <Action name="halve_durations"/>
+ <Action name="double_durations"/>
+ <Action name="rescale"/>
+ <Menu name="Stems"/>
+ <Menu name="Slashes"/>
+ <Menu name="Transpose"/>
+ <Menu name="Convert"/>
+<Menu name="Notes"/>
+<Menu name="beams"/>
+<Menu name="octaves"/>
+<Menu name="indications"/>
+<Menu name="Slurs"/>
+<Menu name="Ties"/>
+<Menu name="Marks"/>
+<Menu name="ornaments"/>
+<Menu name="Fingering"/>
+<Menu name="Slashes"/>
+<Menu name="Adjust"/>
+<Menu name="Respell"/>
+<Menu name="triplets"/>
+<Menu name="Stems"/>
+<Menu name="rests"/>
+<Menu name="transform_notes"/>
+<Menu name="quantize_notes"/>
+ </enable>
+</State>
+
+<State name="have_clipboard_single_segment">
+ <enable>
+ <Action name="edit_paste"/>
+ <Action name="general_paste"/>
+ </enable>
+</State>
+
+<State name="have_multiple_staffs">
+ <enable>
+ <Action name="cursor_up_staff"/>
+ <Action name="cursor_down_staff"/>
+ <Action name="cursor_prior_segment"/>
+ <Action name="cursor_next_segment"/>
+ <Action name="move_events_up_staff"/>
+ <Action name="move_events_down_staff"/>
+ </enable>
+</State>
+
+<State name="linear_mode">
+ <enable>
+ <Action name="show_velocity_control_ruler"/>
+ <Action name="show_controller_events_ruler"/>
+ <Menu name="add_control_ruler"/>
+ <Action name="add_control_ruler"/>
+ <Action name="show_chords_ruler"/>
+ <Action name="show_raw_note_ruler"/>
+ <Action name="show_tempo_ruler"/>
+ </enable>
+</State>
+
+<State name="have_note_events_in_segment">
+ <enable>
+ <Action name="draw_property_line"/>
+ </enable>
+</State>
+
+<State name="have_control_ruler">
+ <enable>
+ <Action name="show_control_rulers"/>
+ </enable>
+</State>
+
+<State name="have_controller_item_selected">
+ <enable>
+ <Action name="erase_control_ruler_item"/>
+ </enable>
+ <disable>
+ <Action name="insert_control_ruler_item"/>
+ </disable>
+</State>
+
+</kpartgui>
diff --git a/src/gui/ui/notationeraser.rc b/src/gui/ui/notationeraser.rc
new file mode 100644
index 0000000..e3e3289
--- /dev/null
+++ b/src/gui/ui/notationeraser.rc
@@ -0,0 +1,12 @@
+<!DOCTYPE kpartgui SYSTEM "kpartgui.dtd">
+
+<kpartgui name="Rosegarden::NotationTool" version="1230">
+
+<Menu name="NotationEraser">
+ <Action name="select" />
+ <Action name="insert" />
+ <Separator/>
+ <Action name="toggle_rest_collapse" />
+</Menu>
+
+</kpartgui>
diff --git a/src/gui/ui/notationselector.rc b/src/gui/ui/notationselector.rc
new file mode 100644
index 0000000..4893b8f
--- /dev/null
+++ b/src/gui/ui/notationselector.rc
@@ -0,0 +1,26 @@
+<!DOCTYPE kpartgui SYSTEM "kpartgui.dtd">
+
+<kpartgui name="Rosegarden::NotationTool" version="1230">
+
+<Menu noMerge="1" name="NotationSelector">
+ <Action name="insert" />
+ <Action name="erase" />
+ <Separator />
+<!-- <Action name="collapse_rests" /> -->
+ <Action name="collapse_rests_aggressively" />
+ <Separator />
+ <Action name="respell_flat" />
+ <Action name="respell_sharp" />
+ <Action name="respell_natural" />
+ <Separator />
+ <Action name="collapse_notes" />
+ <Action name="interpret" />
+ <Separator />
+ <Action name="move_events_up_staff" />
+ <Action name="move_events_down_staff" />
+ <Separator />
+ <Action name="make_invisible" />
+ <Action name="make_visible" />
+</Menu>
+
+</kpartgui>
diff --git a/src/gui/ui/noteinserter.rc b/src/gui/ui/noteinserter.rc
new file mode 100644
index 0000000..507857f
--- /dev/null
+++ b/src/gui/ui/noteinserter.rc
@@ -0,0 +1,23 @@
+<!DOCTYPE kpartgui SYSTEM "kpartgui.dtd">
+
+<kpartgui name="Rosegarden::NotationTool" version="1230">
+
+<Menu noMerge="1" name="NoteInserter">
+ <Action name="select" />
+ <Action name="erase" />
+ <Action name="rests" />
+ <Separator/>
+ <Action name="toggle_auto_beam" />
+ <Separator/>
+ <Action name="toggle_dot" />
+ <Separator/>
+ <Action name="no_accidental" />
+ <Action name="follow_accidental" />
+ <Action name="sharp_accidental" />
+ <Action name="flat_accidental" />
+ <Action name="natural_accidental" />
+ <Action name="double_sharp_accidental" />
+ <Action name="double_flat_accidental" />
+</Menu>
+
+</kpartgui>
diff --git a/src/gui/ui/restinserter.rc b/src/gui/ui/restinserter.rc
new file mode 100644
index 0000000..37636c8
--- /dev/null
+++ b/src/gui/ui/restinserter.rc
@@ -0,0 +1,13 @@
+<!DOCTYPE kpartgui SYSTEM "kpartgui.dtd">
+
+<kpartgui name="Rosegarden::NotationTool" version="1230">
+
+<Menu noMerge="1" name="RestInserter">
+ <Action name="notes" />
+ <Action name="erase" />
+ <Action name="select" />
+ <Separator/>
+ <Action name="toggle_dot" />
+</Menu>
+
+</kpartgui>
diff --git a/src/gui/ui/rosegardenui.rc b/src/gui/ui/rosegardenui.rc
new file mode 100644
index 0000000..ba6e735
--- /dev/null
+++ b/src/gui/ui/rosegardenui.rc
@@ -0,0 +1,440 @@
+<!DOCTYPE kpartgui SYSTEM "kpartgui.dtd">
+
+<kpartgui name="Rosegarden" version="1230">
+
+<MenuBar>
+ <Menu name="file">
+ <!-- these two would like to be open_merge, but that sticks them at
+ the very bottom in older KDE3 versions: -->
+ <Menu name="import" append="new_merge"><text>&amp;Import</text>
+ <Action name="file_import_project" />
+ <Separator/>
+ <Action name="file_import_midi" />
+ <Action name="file_import_rg21" />
+ <Action name="file_import_hydrogen" />
+ </Menu>
+ <Menu name="merge" append="new_merge"><text>&amp;Merge</text>
+ <Action name="file_merge" />
+ <Action name="file_merge_midi" />
+ <Action name="file_merge_rg21" />
+ <Action name="file_merge_hydrogen" />
+ </Menu>
+ <!-- this one would like to be save_merge, but that sticks it at
+ the very bottom in older KDE3 versions: -->
+ <Menu name="export" append="new_merge"><text>&amp;Export</text>
+ <Action name="file_export_project" />
+ <Separator/>
+ <Action name="file_export_lilypond" />
+ <Separator/>
+ <Action name="file_export_midi" />
+ <Separator/>
+ <Action name="file_export_csound" />
+ <Action name="file_export_mup" />
+ <Action name="file_export_musicxml" />
+ </Menu>
+ <Action name="file_print_lilypond" append="print_merge" />
+ <Action name="file_preview_lilypond" append="print_merge" />
+ <Separator/>
+ <Action name="audio_manager"/>
+ <Separator/>
+ <Action name="file_show_playlist" />
+ </Menu>
+ <Menu name="edit">
+ <Action name="delete"/>
+ <Separator/>
+ <Action name="cut_range"/>
+ <Action name="copy_range"/>
+ <Action name="paste_range"/>
+ <Action name="insert_range"/>
+ <Separator/>
+ <Action name="select_all"/>
+ </Menu>
+ <Menu name="document">
+ <text>&amp;Composition</text>
+ <Action name="add_tempo"/>
+ <Action name="add_time_signature"/>
+ <Action name="edit_tempos"/>
+ <Separator/>
+ <Action name="set_tempo_to_segment_length"/>
+ <Action name="groove_quantize"/>
+ <Separator/>
+ <Action name="change_composition_length"/>
+ <Separator/>
+ <Action name="edit_markers"/>
+ <Action name="edit_doc_properties"/>
+ <Separator/>
+ <Action name="set_quick_marker"/>
+ <Action name="jump_to_quick_marker"/>
+ </Menu>
+ <Menu name="Studio">
+ <text>&amp;Studio</text>
+ <Action name="audio_mixer"/>
+ <Action name="midi_mixer"/>
+ <Separator/>
+ <Action name="manage_devices"/>
+ <Action name="manage_synths"/>
+ <Action name="manage_metronome"/>
+ <Separator/>
+ <Action name="modify_midi_filters"/>
+ <Action name="enable_midi_routing"/>
+ <Separator/>
+ <Action name="reset_midi_network"/>
+ <Separator/>
+ <Action name="load_studio"/>
+ <Action name="load_default_studio"/>
+ <Action name="save_default_studio"/>
+ </Menu>
+ <Menu name="Segments">
+ <text>Se&amp;gments</text>
+ <Menu name="edit_with">
+ <text>Edit &amp;With</text>
+ <Action name="edit_default"/>
+ <Separator/>
+ <Action name="edit_matrix"/>
+ <Action name="edit_percussion_matrix"/>
+ <Action name="edit_notation"/>
+ <Action name="edit_event_list"/>
+ </Menu>
+ <Separator/>
+ <Action name="relabel_segment"/>
+ <Action name="quantize_selection"/>
+ <Action name="repeat_quantize"/>
+ <Action name="transpose"/>
+ <Separator/>
+ <Action name="rescale"/>
+ <Action name="set_segment_start"/>
+ <Action name="set_segment_duration"/>
+ <Separator/>
+ <Action name="jog_left"/>
+ <Action name="jog_right"/>
+ <Separator/>
+ <Action name="join_segments"/>
+ <Menu name="split">
+ <text>&amp;Split</text>
+ <Action name="auto_split"/>
+ <Action name="split_by_pitch"/>
+ <Action name="split_by_recording"/>
+ <Action name="split_at_time"/>
+ </Menu>
+ <Action name="repeats_to_real_copies"/>
+ <Separator/>
+ <Action name="manage_trigger_segments"/>
+ </Menu>
+ <Menu name="Tracks">
+ <text>Trac&amp;ks</text>
+ <Action name="add_track"/>
+ <Action name="add_tracks"/>
+ <Action name="delete_track"/>
+ <Separator/>
+ <Action name="move_track_down"/>
+ <Action name="move_track_up"/>
+ <Separator/>
+ <Action name="select_next_track"/>
+ <Action name="select_previous_track"/>
+ <Separator/>
+ <Action name="toggle_mute_track"/>
+ <Action name="toggle_arm_track"/>
+ <Separator/>
+ <Action name="unmute_all_tracks"/>
+ <Action name="mute_all_tracks"/>
+ <Separator/>
+ <Menu name="set_track_instrument"><text>Set &amp;Instrument</text></Menu>
+ <Action name="remap_instruments"/>
+ </Menu>
+ <Menu name="Tools">
+ <text>&amp;Tools</text>
+ <Action name="select"/>
+ <Action name="draw"/>
+ <Action name="erase"/>
+ <Action name="move"/>
+ <Action name="resize"/>
+ <Action name="split"/>
+ <Separator/>
+ <Menu name="Transport">
+ <text>T&amp;ransport</text>
+ <Action name="play"/>
+ <Action name="stop"/>
+ <Action name="fast_forward"/>
+ <Action name="rewind"/>
+ <Action name="record"/>
+ <Action name="recordtoggle"/>
+ <Action name="toggle_tracking"/>
+ <Action name="panic"/>
+ </Menu>
+ </Menu>
+ <Menu name="settings">
+ <Menu name="toolbars" append="show_merge"><text>&amp;Toolbars</text>
+ <Action name="show_stock_toolbar" append="show_merge" />
+ <Action name="show_tools_toolbar" append="show_merge" />
+ <Action name="show_tracks_toolbar" append="show_merge" />
+ <Action name="show_editors_toolbar" append="show_merge" />
+ <Action name="show_transport_toolbar" append="show_merge" />
+ <Action name="show_zoom_toolbar" append="show_merge" />
+ <Action name="show_status_bar" append="show_merge" />
+ </Menu>
+ <Menu name="rulers" append="show_merge"><text>&amp;Rulers</text>
+ <Action name="show_rulers" append="show_merge" />
+ <Action name="show_chord_name_ruler" append="show_merge" />
+ <Action name="show_tempo_ruler" append="show_merge" />
+ </Menu>
+ <Separator append="show_merge" />
+ <Action name="show_transport" append="show_merge" />
+ <Action name="show_inst_segment_parameters" append="show_merge" />
+ <Separator append="show_merge" />
+ <Action name="show_previews" append="show_merge" />
+ <Action name="show_segment_labels" append="show_merge" />
+ <Action name="show_tracklabels" append="show_merge" />
+ <!-- Action name="toggle_all" append="show_merge" /> -->
+ </Menu>
+ <Menu name="help" append="about_merge"><text>&amp;Help</text>
+ <Action name="tutorial"/>
+ <Action name="guidelines"/>
+ </Menu>
+
+</MenuBar>
+
+<ToolBar name="Tools Toolbar">
+ <text>Tools Toolbar</text>
+ <Action name="select"/>
+ <Action name="draw"/>
+ <Action name="erase"/>
+ <Action name="move"/>
+ <Action name="resize"/>
+ <Action name="split"/>
+ <!-- <Action name="join"/> -->
+</ToolBar>
+
+<ToolBar name="Tracks Toolbar">
+ <text>Tracks Toolbar</text>
+ <Action name="add_track"/>
+ <Action name="delete_track"/>
+ <Action name="move_track_up"/>
+ <Action name="move_track_down"/>
+ <Separator/>
+ <Action name="mute_all_tracks"/>
+ <Action name="unmute_all_tracks"/>
+<!-- <Separator/>
+ <Action name="show_previews"/>
+ <Action name="show_segment_labels"/>
+ <Action name="show_tracklabels"/> -->
+</ToolBar>
+
+<ToolBar name="Editors Toolbar">
+ <text>Editors Toolbar</text>
+ <Action name="edit_matrix"/>
+ <Action name="edit_percussion_matrix"/>
+ <Action name="edit_notation"/>
+ <Action name="edit_event_list"/>
+ <Action name="audio_manager"/>
+ <Separator/>
+ <Action name="quantize_selection"/>
+ <Separator/>
+ <Action name="manage_devices"/>
+ <Action name="manage_synths"/>
+ <Action name="midi_mixer"/>
+ <Action name="audio_mixer"/>
+</ToolBar>
+
+<ToolBar name="Transport Toolbar">
+ <text>Transport Toolbar</text>
+ <Action name="rewindtobeginning" />
+ <Action name="rewind" />
+ <Action name="play" />
+ <Action name="fast_forward" />
+ <Action name="fastforwardtoend" />
+ <Action name="stop" />
+ <Action name="record" />
+ <Action name="toggle_tracking"/>
+ <Action name="panic"/>
+</ToolBar>
+
+<ToolBar name="Zoom Toolbar">
+ <text>Zoom Toolbar</text>
+</ToolBar>
+
+<Menu noMerge="1" name="segment_tool_menu">
+ <Action name="edit_default"/>
+ <Separator/>
+ <Action name="edit_matrix"/>
+ <Action name="edit_percussion_matrix"/>
+ <Action name="edit_notation"/>
+ <Action name="edit_event_list"/>
+ <Separator/>
+ <Action name="edit_undo"/>
+ <Action name="edit_redo"/>
+ <Separator/>
+ <Action name="edit_cut"/>
+ <Action name="edit_copy"/>
+ <Action name="edit_paste"/>
+ <Separator/>
+ <Action name="delete"/>
+ <Action name="join_segments"/>
+ <Separator/>
+ <Action name="quantize_selection"/>
+ <Action name="repeat_quantize"/>
+ <Action name="relabel_segment"/>
+ <Action name="transpose"/>
+ <Separator/>
+ <Action name="select"/>
+ <Action name="move"/>
+ <Action name="draw"/>
+ <Action name="erase"/>
+ <Action name="resize"/>
+ <Action name="split"/>
+ <!-- <Action name="join"/> -->
+</Menu>
+
+<State name="new_file">
+ <disable>
+ <Action name="file_revert"/>
+ </disable>
+ <enable>
+ <Action name="file_close"/>
+ <Action name="file_print"/>
+ <Action name="file_print_preview"/>
+ </enable>
+</State>
+
+<State name="new_file_modified">
+ <enable>
+ </enable>
+</State>
+
+<State name="saved_file_modified">
+ <enable>
+ <Action name="file_revert"/>
+ </enable>
+</State>
+
+<State name="have_project_packager">
+ <enable>
+ <Action name="file_import_project"/>
+ <Action name="file_export_project"/>
+ </enable>
+</State>
+
+<State name="have_lilypondview">
+ <enable>
+ <Action name="file_preview_lilypond"/>
+ </enable>
+</State>
+
+<State name="have_segments">
+ <enable>
+ <Action name="move"/>
+ <Action name="erase"/>
+ <Action name="resize"/>
+ <Action name="split"/>
+ <!-- <Action name="join"/> -->
+ <Action name="file_print"/>
+ <Action name="file_print_preview"/>
+ </enable>
+</State>
+
+<State name="have_selection">
+ <enable>
+ <Action name="edit_cut"/>
+ <Action name="edit_copy"/>
+ <Action name="delete"/>
+ <Action name="edit_default"/>
+ <Action name="edit_matrix"/>
+ <Action name="edit_percussion_matrix"/>
+ <Action name="edit_notation"/>
+ <Action name="edit_event_list"/>
+ <Action name="quantize_selection"/>
+ <Action name="repeat_quantize"/>
+<!-- <Action name="harmonize_selection"/> -->
+ <Action name="rescale"/>
+ <Action name="jog_left"/>
+ <Action name="jog_right"/>
+ <Action name="auto_split"/>
+ <Action name="split_by_pitch"/>
+ <Action name="split_by_recording"/>
+ <Action name="split_at_time"/>
+ <Action name="join_segments"/>
+ <Action name="relabel_segment"/>
+ <Action name="transpose"/>
+ <Action name="set_segment_start"/>
+ <Action name="set_segment_duration"/>
+ <Action name="collapse"/>
+ <Action name="set_tempo_to_segment_length"/>
+ <Action name="repeats_to_real_copies"/>
+ <Action name="groove_quantize"/>
+ </enable>
+</State>
+
+<State name="have_clipboard">
+ <enable>
+ <Action name="edit_paste"/>
+ <Action name="paste_range"/>
+ </enable>
+</State>
+
+<State name="audio_segment_selected">
+ <enable>
+ <Action name="set_tempo_to_segment_length"/>
+ </enable>
+ <disable>
+ <Action name="edit_notation"/>
+ <Action name="edit_matrix"/>
+ <Action name="edit_percussion_matrix"/>
+ <Action name="edit_event_list"/>
+ <Action name="quantize_selection"/>
+ <Action name="repeat_quantize"/>
+<!-- <Action name="rescale"/> -->
+ <Action name="split_by_pitch"/>
+ <Action name="split_by_recording"/>
+ <Action name="join_segments"/>
+ <Action name="collapse"/>
+ <Action name="repeats_to_real_copies"/>
+ <Action name="groove_quantize"/>
+ </disable>
+</State>
+
+<State name="got_midi_devices">
+ <enable>
+ <Action name="modify_banks"/>
+ <Action name="remap_instruments"/>
+ <Action name="midi_mixer"/>
+ </enable>
+</State>
+
+<State name="got_audio">
+ <enable>
+ <Action name="audio_manager"/>
+ <Action name="audio_mixer"/>
+ </enable>
+</State>
+
+<State name="sequencer_running">
+ <enable>
+ <Action name="rewindtobeginning" />
+ <Action name="rewind" />
+ <Action name="play" />
+ <Action name="fast_forward" />
+ <Action name="fastforwardtoend" />
+ <Action name="stop" />
+ <Action name="record" />
+ <Action name="recordtoggle" />
+ </enable>
+</State>
+
+<State name="not_playing">
+ <enable>
+ </enable>
+</State>
+
+<State name="parametersbox_closed">
+ <enable>
+ <Action name="show_inst_segment_parameters" />
+ </enable>
+</State>
+
+<State name="have_range">
+ <enable>
+ <Action name="cut_range"/>
+ <Action name="copy_range"/>
+ </enable>
+</State>
+
+</kpartgui>
diff --git a/src/gui/ui/temporuler.rc b/src/gui/ui/temporuler.rc
new file mode 100644
index 0000000..1db6b62
--- /dev/null
+++ b/src/gui/ui/temporuler.rc
@@ -0,0 +1,19 @@
+<!DOCTYPE kpartgui SYSTEM "kpartgui.dtd">
+
+<kpartgui name="Rosegarden::TempoRuler" version="1230">
+
+<Menu name="tempo_ruler_menu">
+ <Action name="insert_tempo_here" />
+ <Action name="insert_tempo_at_pointer" />
+ <Separator/>
+ <Action name="delete_tempo" />
+ <Separator/>
+ <Action name="ramp_to_next" />
+ <Action name="unramp" />
+ <Separator/>
+ <Action name="edit_tempo" />
+ <Action name="edit_time_signature" />
+ <Action name="edit_tempos" />
+</Menu>
+
+</kpartgui>
diff --git a/src/gui/ui/tempoview.rc b/src/gui/ui/tempoview.rc
new file mode 100644
index 0000000..b48443f
--- /dev/null
+++ b/src/gui/ui/tempoview.rc
@@ -0,0 +1,96 @@
+<!DOCTYPE kpartgui SYSTEM "kpartgui.dtd">
+
+<kpartgui name="Rosegarden::TempoView" version="1500">
+
+<MenuBar>
+
+<Menu name="edit" append="new_merge">
+ <Action name="insert_tempo"/>
+ <Action name="insert_timesig"/>
+ <Action name="delete"/>
+ <Action name="edit"/>
+ <Separator/>
+ <Action name="select_all"/>
+ <Action name="clear_selection"/>
+</Menu>
+
+<Menu name="view"><text>&amp;View</text>
+ <Action name="time_musical"/>
+ <Action name="time_real"/>
+ <Action name="time_raw"/>
+</Menu>
+
+</MenuBar>
+
+<ToolBar name="Actions Toolbar">
+ <text>Actions Toolbar</text>
+ <Action name="insert_tempo"/>
+ <Action name="insert_timesig"/>
+ <Action name="delete"/>
+ <Action name="edit"/>
+</ToolBar>
+
+<ToolBar name="Time Toolbar">
+ <text>Time Toolbar</text>
+ <Action name="time_musical"/>
+ <Action name="time_real"/>
+ <Action name="time_raw"/>
+</ToolBar>
+
+</kpartgui>
+
+<ToolBar name="Transport Toolbar">
+ <text>Transport Toolbar</text>
+
+ <Action name="playback_pointer_start" />
+ <Action name="playback_pointer_back_bar" />
+ <Action name="play" />
+ <Action name="playback_pointer_forward_bar" />
+ <Action name="playback_pointer_end" />
+ <Action name="stop" />
+ <Action name="playback_pointer_to_cursor" />
+ <Action name="cursor_to_playback_pointer" />
+ <Action name="toggle_solo"/>
+ <Action name="panic"/>
+</ToolBar>
+
+<ToolBar name="Actions Toolbar">
+ <text>Actions Toolbar</text>
+</ToolBar>
+
+<ToolBar name="Zoom Toolbar">
+ <text>Zoom Toolbar</text>
+</ToolBar>
+
+<State name="have_selection">
+ <enable>
+ <Action name="edit_cut"/>
+ <Action name="edit_copy"/>
+ <Action name="edit"/>
+ <Action name="quantize"/>
+ <Action name="velocity_up"/>
+ <Action name="velocity_down"/>
+ <Action name="set_velocities"/>
+ <Action name="transpose_up"/>
+ <Action name="transpose_down"/>
+ <Action name="transpose_up_octave"/>
+ <Action name="transpose_down_octave"/>
+ <Action name="general_transpose"/>
+ <Action name="general_diatonic_transpose"/>
+ <Action name="invert"/>
+ <Action name="retrograde"/>
+ <Action name="retrograde_invert"/>
+ <Action name="preview_selection"/>
+ <Action name="clear_selection"/>
+ <Action name="filter_selection"/>
+ </enable>
+</State>
+
+<State name="have_control_ruler">
+ <enable>
+ <Action name="show_control_rulers"/>
+ </enable>
+</State>
+
+</kpartgui>
+
diff --git a/src/gui/ui/textinserter.rc b/src/gui/ui/textinserter.rc
new file mode 100644
index 0000000..2d445a6
--- /dev/null
+++ b/src/gui/ui/textinserter.rc
@@ -0,0 +1,11 @@
+<!DOCTYPE kpartgui SYSTEM "kpartgui.dtd">
+
+<kpartgui name="Rosegarden::NotationTool" version="1230">
+
+<Menu noMerge="1" name="TextInserter">
+ <Action name="select" />
+ <Action name="notes" />
+ <Action name="erase" />
+</Menu>
+
+</kpartgui>
diff --git a/src/gui/ui/triggermanager.rc b/src/gui/ui/triggermanager.rc
new file mode 100644
index 0000000..56efa52
--- /dev/null
+++ b/src/gui/ui/triggermanager.rc
@@ -0,0 +1,40 @@
+<!DOCTYPE kpartgui SYSTEM "kpartgui.dtd">
+
+<kpartgui name="TriggerManager" version="1230">
+
+<MenuBar>
+
+ <Menu name="file">
+ <Action name="file_close_discard"/>
+ </Menu>
+
+ <Menu name="edit">
+ <Action name="paste_to_trigger_segment"/>
+ </Menu>
+
+ <Menu name="view"><text>&amp;View</text>
+ <Action name="time_musical"/>
+ <Action name="time_real"/>
+ <Action name="time_raw"/>
+ /Menu>
+
+</MenuBar>
+
+<ToolBar name="mainToolBar">
+ <Action name="file_close"/>
+</ToolBar>
+
+<ToolBar name="Time Toolbar">
+ <text>Time Toolbar</text>
+ <Action name="time_musical"/>
+ <Action name="time_real"/>
+ <Action name="time_raw"/>
+</ToolBar>
+
+<State name="have_clipboard_single_segment">
+ <enable>
+ <Action name="paste_to_trigger_segment"/>
+ </enable>
+</State>
+
+</kpartgui>
diff --git a/src/gui/widgets/AudioFaderBox.cpp b/src/gui/widgets/AudioFaderBox.cpp
new file mode 100644
index 0000000..05789ff
--- /dev/null
+++ b/src/gui/widgets/AudioFaderBox.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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "AudioFaderBox.h"
+#include <qlayout.h>
+
+#include <klocale.h>
+#include <kstddirs.h>
+#include "misc/Debug.h"
+#include "AudioRouteMenu.h"
+#include "AudioVUMeter.h"
+#include "base/AudioLevel.h"
+#include "base/Instrument.h"
+#include "base/Studio.h"
+#include "Fader.h"
+#include "gui/general/GUIPalette.h"
+#include "gui/application/RosegardenGUIApp.h"
+#include "gui/studio/AudioPluginOSCGUIManager.h"
+#include "Rotary.h"
+#include <kglobal.h>
+#include <qframe.h>
+#include <qhbox.h>
+#include <qlabel.h>
+#include <qobject.h>
+#include <qpixmap.h>
+#include <qpushbutton.h>
+#include <qsignalmapper.h>
+#include <qstring.h>
+#include <qtooltip.h>
+#include <qvbox.h>
+#include <qwidget.h>
+#include "VUMeter.h"
+
+
+namespace Rosegarden
+{
+
+AudioFaderBox::AudioFaderBox(QWidget *parent,
+ QString id,
+ bool haveInOut,
+ const char *name):
+ QFrame(parent, name),
+ m_signalMapper(new QSignalMapper(this)),
+ m_id(id),
+ m_isStereo(false)
+{
+ // Plugin box
+ //
+ QPushButton *plugin;
+ QVBox *pluginVbox = 0;
+
+ pluginVbox = new QVBox(this);
+ pluginVbox->setSpacing(2);
+
+ for (int i = 0; i < 5; i++) {
+ plugin = new QPushButton(pluginVbox);
+ plugin->setText(i18n("<no plugin>"));
+
+ QToolTip::add
+ (plugin, i18n("Audio plugin button"));
+
+ m_plugins.push_back(plugin);
+ m_signalMapper->setMapping(plugin, i);
+ connect(plugin, SIGNAL(clicked()),
+ m_signalMapper, SLOT(map()));
+ }
+
+ m_synthButton = new QPushButton(this);
+ m_synthButton->setText(i18n("<no synth>"));
+ QToolTip::add
+ (m_synthButton, i18n("Synth plugin button"));
+
+ // VU meter and fader
+ //
+ QHBox *faderHbox = new QHBox(this);
+
+ m_vuMeter = new AudioVUMeter(faderHbox, VUMeter::AudioPeakHoldShort,
+ true, true);
+
+ m_recordFader = new Fader(AudioLevel::ShortFader,
+ 20, m_vuMeter->height(), faderHbox);
+
+ m_recordFader->setOutlineColour(GUIPalette::getColour(GUIPalette::RecordFaderOutline));
+
+ delete m_vuMeter; // only used the first one to establish height,
+ // actually want it after the record fader in
+ // hbox
+ m_vuMeter = new AudioVUMeter(faderHbox, VUMeter::AudioPeakHoldShort,
+ true, true);
+
+ m_fader = new Fader(AudioLevel::ShortFader,
+ 20, m_vuMeter->height(), faderHbox);
+
+ m_fader->setOutlineColour(GUIPalette::getColour(GUIPalette::PlaybackFaderOutline));
+
+ QString pixmapDir = KGlobal::dirs()->findResource("appdata", "pixmaps/");
+ m_monoPixmap.load(QString("%1/misc/mono.xpm").arg(pixmapDir));
+ m_stereoPixmap.load(QString("%1/misc/stereo.xpm").arg(pixmapDir));
+
+ m_pan = new Rotary(this, -100.0, 100.0, 1.0, 5.0, 0.0, 22,
+ Rotary::NoTicks, false, true);
+
+ // same as the knob colour on the MIDI pan
+ m_pan->setKnobColour(GUIPalette::getColour(GUIPalette::RotaryPastelGreen));
+
+ m_stereoButton = new QPushButton(this);
+ m_stereoButton->setPixmap(m_monoPixmap); // default is mono
+ m_stereoButton->setFixedSize(24, 24);
+
+ connect(m_stereoButton, SIGNAL(clicked()),
+ this, SLOT(slotChannelStateChanged()));
+
+ m_synthGUIButton = new QPushButton(this);
+ m_synthGUIButton->setText(i18n("Editor"));
+
+ if (haveInOut) {
+ m_audioInput = new AudioRouteMenu(this,
+ AudioRouteMenu::In,
+ AudioRouteMenu::Regular);
+ m_audioOutput = new AudioRouteMenu(this,
+ AudioRouteMenu::Out,
+ AudioRouteMenu::Regular);
+ } else {
+ m_pan->setKnobColour(GUIPalette::getColour(GUIPalette::RotaryPastelOrange));
+
+ m_audioInput = 0;
+ m_audioOutput = 0;
+ }
+
+ QToolTip::add
+ (m_pan, i18n("Set the audio pan position in the stereo field"));
+ QToolTip::add
+ (m_synthGUIButton, i18n("Open synth plugin's native editor"));
+ QToolTip::add
+ (m_stereoButton, i18n("Mono or Stereo Instrument"));
+ QToolTip::add
+ (m_recordFader, i18n("Record level"));
+ QToolTip::add
+ (m_fader, i18n("Playback level"));
+ QToolTip::add
+ (m_vuMeter, i18n("Audio level"));
+
+ QGridLayout *grid = new QGridLayout(this, 3, 6, 4, 4);
+
+ grid->addMultiCellWidget(m_synthButton, 0, 0, 0, 2);
+
+ if (haveInOut) {
+ m_inputLabel = new QLabel(i18n("In:"), this);
+ grid->addWidget(m_inputLabel, 0, 0, AlignRight);
+ grid->addMultiCellWidget(m_audioInput->getWidget(), 0, 0, 1, 2);
+ m_outputLabel = new QLabel(i18n("Out:"), this);
+ grid->addWidget(m_outputLabel, 0, 3, AlignRight);
+ grid->addMultiCellWidget(m_audioOutput->getWidget(), 0, 0, 4, 5);
+ }
+
+ grid->addMultiCellWidget(pluginVbox, 2, 2, 0, 2);
+ grid->addMultiCellWidget(faderHbox, 1, 2, 3, 5);
+
+ grid->addWidget(m_synthGUIButton, 1, 0);
+ grid->addWidget(m_pan, 1, 2);
+ grid->addWidget(m_stereoButton, 1, 1);
+
+ for (int i = 0; i < 5; ++i) {
+ // Force width
+ m_plugins[i]->setFixedWidth(m_plugins[i]->width());
+ }
+ m_synthButton->setFixedWidth(m_plugins[0]->width());
+
+ m_synthButton->hide();
+ m_synthGUIButton->hide();
+}
+
+void
+AudioFaderBox::setIsSynth(bool isSynth)
+{
+ if (isSynth) {
+ m_inputLabel->hide();
+ m_synthButton->show();
+ m_synthGUIButton->show();
+ m_audioInput->getWidget()->hide();
+ m_recordFader->hide();
+ } else {
+ m_synthButton->hide();
+ m_synthGUIButton->hide();
+ m_inputLabel->show();
+ m_audioInput->getWidget()->show();
+ m_recordFader->show();
+ }
+}
+
+void
+AudioFaderBox::slotSetInstrument(Studio *studio,
+ Instrument *instrument)
+{
+ if (m_audioInput)
+ m_audioInput->slotSetInstrument(studio, instrument);
+ if (m_audioOutput)
+ m_audioOutput->slotSetInstrument(studio, instrument);
+ if (instrument)
+ setAudioChannels(instrument->getAudioChannels());
+ if (instrument) {
+
+ RG_DEBUG << "AudioFaderBox::slotSetInstrument(" << instrument->getId() << ")" << endl;
+
+ setIsSynth(instrument->getType() == Instrument::SoftSynth);
+ if (instrument->getType() == Instrument::SoftSynth) {
+ bool gui = false;
+ RG_DEBUG << "AudioFaderBox::slotSetInstrument(" << instrument->getId() << "): is soft synth" << endl;
+#ifdef HAVE_LIBLO
+
+ gui = RosegardenGUIApp::self()->getPluginGUIManager()->hasGUI
+ (instrument->getId(), Instrument::SYNTH_PLUGIN_POSITION);
+ RG_DEBUG << "AudioFaderBox::slotSetInstrument(" << instrument->getId() << "): has gui = " << gui << endl;
+#endif
+
+ m_synthGUIButton->setEnabled(gui);
+ }
+ }
+}
+
+bool
+AudioFaderBox::owns(const QObject *object)
+{
+ return (object &&
+ ((object->parent() == this) ||
+ (object->parent() && (object->parent()->parent() == this))));
+}
+
+void
+AudioFaderBox::setAudioChannels(int channels)
+{
+ m_isStereo = (channels > 1);
+
+ switch (channels) {
+ case 1:
+ if (m_stereoButton)
+ m_stereoButton->setPixmap(m_monoPixmap);
+ m_isStereo = false;
+ break;
+
+ case 2:
+ if (m_stereoButton)
+ m_stereoButton->setPixmap(m_stereoPixmap);
+ m_isStereo = true;
+ break;
+ default:
+ RG_DEBUG << "AudioFaderBox::setAudioChannels - "
+ << "unsupported channel numbers (" << channels
+ << ")" << endl;
+ return ;
+ }
+
+ if (m_audioInput)
+ m_audioInput->slotRepopulate();
+ if (m_audioOutput)
+ m_audioOutput->slotRepopulate();
+}
+
+void
+AudioFaderBox::slotChannelStateChanged()
+{
+ if (m_isStereo) {
+ setAudioChannels(1);
+ emit audioChannelsChanged(1);
+ } else {
+ setAudioChannels(2);
+ emit audioChannelsChanged(2);
+ }
+}
+
+}
+#include "AudioFaderBox.moc"
diff --git a/src/gui/widgets/AudioFaderBox.h b/src/gui/widgets/AudioFaderBox.h
new file mode 100644
index 0000000..060fc9c
--- /dev/null
+++ b/src/gui/widgets/AudioFaderBox.h
@@ -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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_AUDIOFADERBOX_H_
+#define _RG_AUDIOFADERBOX_H_
+
+#include <qframe.h>
+#include <qpixmap.h>
+#include <qstring.h>
+#include <vector>
+
+
+class QWidget;
+class QSignalMapper;
+class QPushButton;
+class QObject;
+class QLabel;
+
+
+namespace Rosegarden
+{
+
+class Studio;
+class Rotary;
+class Instrument;
+class Fader;
+class AudioVUMeter;
+class AudioRouteMenu;
+
+
+class AudioFaderBox : public QFrame
+{
+ Q_OBJECT
+
+public:
+ AudioFaderBox(QWidget *parent,
+ QString id = "",
+ bool haveInOut = true,
+ const char *name = 0);
+
+ void setAudioChannels(int);
+
+ void setIsSynth(bool);
+
+ bool owns(const QObject *object);
+
+ QPushButton *m_synthButton;
+ std::vector<QPushButton*> m_plugins;
+
+ AudioVUMeter *m_vuMeter;
+
+ Fader *m_fader;
+ Fader *m_recordFader;
+ Rotary *m_pan;
+
+ QPixmap m_monoPixmap;
+ QPixmap m_stereoPixmap;
+
+ QSignalMapper *m_signalMapper;
+
+ QLabel *m_inputLabel;
+ QLabel *m_outputLabel;
+
+ AudioRouteMenu *m_audioInput;
+ AudioRouteMenu *m_audioOutput;
+
+ QPushButton *m_synthGUIButton;
+
+ QString m_id;
+
+ bool isStereo() const { return m_isStereo; }
+
+signals:
+ void audioChannelsChanged(int);
+
+public slots:
+ void slotSetInstrument(Studio *studio,
+ Instrument *instrument);
+
+protected slots:
+ void slotChannelStateChanged();
+
+protected:
+ QPushButton *m_stereoButton;
+ bool m_isStereo;
+};
+
+
+
+}
+
+#endif
diff --git a/src/gui/widgets/AudioListItem.h b/src/gui/widgets/AudioListItem.h
new file mode 100644
index 0000000..bbd8fc2
--- /dev/null
+++ b/src/gui/widgets/AudioListItem.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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_AUDIOLISTITEM_H_
+#define _RG_AUDIOLISTITEM_H_
+
+#include <sound/AudioFile.h>
+#include <klistview.h>
+
+namespace Rosegarden
+{
+
+class Segment;
+
+// Add an Id to a QListViewItem
+//
+class AudioListItem : public KListViewItem
+{
+
+public:
+
+ AudioListItem(KListView *parent):KListViewItem(parent),
+ m_segment(0) {;}
+
+ AudioListItem(KListViewItem *parent):KListViewItem(parent),
+ m_segment(0) {;}
+
+ AudioListItem(KListView *parent,
+ QString label,
+ AudioFileId id):
+ KListViewItem(parent,
+ label,
+ "", "", "", "", "", "", ""),
+ m_id(id),
+ m_segment(0) {;}
+
+ AudioListItem(KListViewItem *parent,
+ QString label,
+ AudioFileId id):
+ KListViewItem(parent,
+ label,
+ "", "", "", "", "", "", ""),
+ m_id(id),
+ m_segment(0) {;}
+
+
+ AudioFileId getId() { return m_id; }
+
+ void setStartTime(const RealTime &time)
+ { m_startTime = time; }
+ RealTime getStartTime() { return m_startTime; }
+
+ void setDuration(const RealTime &time)
+ { m_duration = time; }
+ RealTime getDuration() { return m_duration; }
+
+ void setSegment(Segment *segment)
+ { m_segment = segment; }
+ Segment *getSegment() { return m_segment; }
+
+protected:
+ AudioFileId m_id;
+
+ // for audio segments
+ RealTime m_startTime;
+ RealTime m_duration;
+
+ // pointer to a segment
+ Segment *m_segment;
+
+};
+
+}
+
+
+#endif /*AUDIOLISTITEM_H_*/
diff --git a/src/gui/widgets/AudioListView.cpp b/src/gui/widgets/AudioListView.cpp
new file mode 100644
index 0000000..45193a6
--- /dev/null
+++ b/src/gui/widgets/AudioListView.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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "AudioListView.h"
+
+#include "misc/Debug.h"
+#include "gui/widgets/AudioListItem.h"
+#include "qdragobject.h"
+
+namespace Rosegarden {
+
+AudioListView::AudioListView(QWidget *parent, const char *name)
+ : KListView(parent, name)
+{
+ setDragEnabled(true);
+ setAcceptDrops(true);
+ setDropVisualizer(false);
+}
+
+bool AudioListView::acceptDrag(QDropEvent* e) const
+{
+ return QUriDrag::canDecode(e) || KListView::acceptDrag(e);
+}
+
+QDragObject* AudioListView::dragObject()
+{
+ AudioListItem* item = dynamic_cast<AudioListItem*>(currentItem());
+
+ QString audioData;
+ QTextOStream ts(&audioData);
+ ts << "AudioFileManager\n"
+ << item->getId() << '\n'
+ << item->getStartTime().sec << '\n'
+ << item->getStartTime().nsec << '\n'
+ << item->getDuration().sec << '\n'
+ << item->getDuration().nsec << '\n';
+
+ RG_DEBUG << "AudioListView::dragObject - "
+ << "file id = " << item->getId()
+ << ", start time = " << item->getStartTime() << endl;
+
+ return new QTextDrag(audioData, this);
+}
+
+}
diff --git a/src/gui/widgets/AudioListView.h b/src/gui/widgets/AudioListView.h
new file mode 100644
index 0000000..477054a
--- /dev/null
+++ b/src/gui/widgets/AudioListView.h
@@ -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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_AUDIOLISTVIEW_H_
+#define _RG_AUDIOLISTVIEW_H_
+
+#include <klistview.h>
+
+namespace Rosegarden {
+
+class AudioListView : public KListView
+{
+public:
+ AudioListView(QWidget *parent = 0, const char *name = 0);
+
+protected:
+ bool acceptDrag(QDropEvent* e) const;
+ virtual QDragObject* dragObject();
+};
+
+}
+
+#endif /*AUDIOLISTVIEW_H_*/
diff --git a/src/gui/widgets/AudioRouteMenu.cpp b/src/gui/widgets/AudioRouteMenu.cpp
new file mode 100644
index 0000000..6f4c93f
--- /dev/null
+++ b/src/gui/widgets/AudioRouteMenu.cpp
@@ -0,0 +1,381 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
+
+/*
+ Rosegarden
+ A MIDI and audio sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "AudioRouteMenu.h"
+#include "WheelyButton.h"
+
+#include "base/Instrument.h"
+#include "base/Studio.h"
+#include "gui/studio/StudioControl.h"
+#include "gui/widgets/RosegardenPopupMenu.h"
+#include "sound/MappedCommon.h"
+#include "sound/MappedStudio.h"
+#include <kcombobox.h>
+#include <klocale.h>
+#include <qcursor.h>
+#include <qobject.h>
+#include <qpoint.h>
+#include <qstring.h>
+#include <qwidget.h>
+
+
+namespace Rosegarden
+{
+
+AudioRouteMenu::AudioRouteMenu(QWidget *par,
+ Direction direction,
+ Format format,
+ Studio *studio,
+ Instrument *instrument) :
+ QObject(par),
+ m_studio(studio),
+ m_instrument(instrument),
+ m_direction(direction),
+ m_format(format)
+{
+ switch (format) {
+
+ case Compact: {
+ m_combo = 0;
+ m_button = new WheelyButton(par);
+ connect(m_button, SIGNAL(wheel(bool)), this, SLOT(slotWheel(bool)));
+ connect(m_button, SIGNAL(clicked()), this, SLOT(slotShowMenu()));
+ break;
+ }
+
+ case Regular: {
+ m_button = 0;
+ m_combo = new KComboBox(par);
+ connect(m_combo, SIGNAL(activated(int)), this, SLOT(slotEntrySelected(int)));
+ break;
+ }
+
+ }
+
+ slotRepopulate();
+}
+
+QWidget *
+AudioRouteMenu::getWidget()
+{
+ if (m_button)
+ return m_button;
+ else
+ return m_combo;
+}
+
+void
+AudioRouteMenu::slotRepopulate()
+{
+ switch (m_format) {
+
+ case Compact:
+ m_button->setText(getEntryText(getCurrentEntry()));
+ break;
+
+ case Regular:
+ m_combo->clear();
+ for (int i = 0; i < getNumEntries(); ++i) {
+ m_combo->insertItem(getEntryText(i));
+ }
+ m_combo->setCurrentItem(getCurrentEntry());
+ break;
+ }
+}
+
+void
+AudioRouteMenu::slotSetInstrument(Studio *studio,
+ Instrument *instrument)
+{
+ m_studio = studio;
+ m_instrument = instrument;
+ slotRepopulate();
+}
+
+void
+AudioRouteMenu::slotWheel(bool up)
+{
+ int current = getCurrentEntry();
+ if (up) { // actually moves down the list
+ if (current > 0)
+ slotEntrySelected(current - 1);
+ } else {
+ if (current < getNumEntries() - 1)
+ slotEntrySelected(current + 1);
+ }
+}
+
+void
+AudioRouteMenu::slotShowMenu()
+{
+ if (getNumEntries() == 0)
+ return ;
+
+ RosegardenPopupMenu *menu = new RosegardenPopupMenu((QWidget *)parent());
+
+ for (int i = 0; i < getNumEntries(); ++i) {
+
+ menu->insertItem(getEntryText(i), this, SLOT(slotEntrySelected(int)),
+ 0, i);
+ menu->setItemParameter(i, i);
+ }
+
+ int itemHeight = menu->itemHeight(0) + 2;
+ QPoint pos = QCursor::pos();
+
+ pos.rx() -= 10;
+ pos.ry() -= (itemHeight / 2 + getCurrentEntry() * itemHeight);
+
+ menu->popup(pos);
+}
+
+int
+AudioRouteMenu::getNumEntries()
+{
+ if (!m_instrument)
+ return 0;
+
+ switch (m_direction) {
+
+ case In: {
+ int stereoIns =
+ m_studio->getRecordIns().size() +
+ m_studio->getBusses().size();
+
+ if (m_instrument->getAudioChannels() > 1) {
+ return stereoIns;
+ } else {
+ return stereoIns * 2;
+ }
+
+ break;
+ }
+
+ case Out:
+ return m_studio->getBusses().size();
+ }
+
+ return 0;
+}
+
+int
+AudioRouteMenu::getCurrentEntry()
+{
+ if (!m_instrument)
+ return 0;
+
+ switch (m_direction) {
+
+ case In: {
+ bool stereo = (m_instrument->getAudioChannels() > 1);
+
+ bool isBuss;
+ int channel;
+ int input = m_instrument->getAudioInput(isBuss, channel);
+
+ if (isBuss) {
+ int recordIns = m_studio->getRecordIns().size();
+ if (stereo) {
+ return recordIns + input;
+ } else {
+ return recordIns * 2 + input * 2 + channel;
+ }
+ } else {
+ if (stereo) {
+ return input;
+ } else {
+ return input * 2 + channel;
+ }
+ }
+
+ break;
+ }
+
+ case Out:
+ return m_instrument->getAudioOutput();
+ }
+
+ return 0;
+}
+
+QString
+AudioRouteMenu::getEntryText(int entry)
+{
+ switch (m_direction) {
+
+ case In: {
+ bool stereo = (m_instrument->getAudioChannels() > 1);
+ int recordIns = m_studio->getRecordIns().size();
+
+ if (stereo) {
+ if (entry < recordIns) {
+ return i18n("In %1").arg(entry + 1);
+ } else if (entry == recordIns) {
+ return i18n("Master");
+ } else {
+ return i18n("Sub %1").arg(entry - recordIns);
+ }
+ } else {
+ int channel = entry % 2;
+ entry /= 2;
+ if (entry < recordIns) {
+ return (channel ? i18n("In %1 R") :
+ i18n("In %1 L")).arg(entry + 1);
+ } else if (entry == recordIns) {
+ return (channel ? i18n("Master R") :
+ i18n("Master L"));
+ } else {
+ return (channel ? i18n("Sub %1 R") :
+ i18n("Sub %1 L")).arg(entry - recordIns);
+ }
+ }
+ break;
+ }
+
+ case Out:
+ if (entry == 0)
+ return i18n("Master");
+ else
+ return i18n("Sub %1").arg(entry);
+ }
+
+ return QString();
+}
+
+void
+AudioRouteMenu::slotEntrySelected(int i)
+{
+ switch (m_direction) {
+
+ case In: {
+ bool stereo = (m_instrument->getAudioChannels() > 1);
+
+ bool oldIsBuss;
+ int oldChannel;
+ int oldInput = m_instrument->getAudioInput(oldIsBuss, oldChannel);
+
+ bool newIsBuss;
+ int newChannel = 0;
+ int newInput;
+
+ int recordIns = m_studio->getRecordIns().size();
+
+ if (stereo) {
+ newIsBuss = (i >= recordIns);
+ if (newIsBuss) {
+ newInput = i - recordIns;
+ } else {
+ newInput = i;
+ }
+ } else {
+ newIsBuss = (i >= recordIns * 2);
+ newChannel = i % 2;
+ if (newIsBuss) {
+ newInput = i / 2 - recordIns;
+ } else {
+ newInput = i / 2;
+ }
+ }
+
+ MappedObjectId oldMappedId = 0, newMappedId = 0;
+
+ if (oldIsBuss) {
+ Buss *buss = m_studio->getBussById(oldInput);
+ if (buss)
+ oldMappedId = buss->getMappedId();
+ } else {
+ RecordIn *in = m_studio->getRecordIn(oldInput);
+ if (in)
+ oldMappedId = in->getMappedId();
+ }
+
+ if (newIsBuss) {
+ Buss *buss = m_studio->getBussById(newInput);
+ if (!buss)
+ return ;
+ newMappedId = buss->getMappedId();
+ } else {
+ RecordIn *in = m_studio->getRecordIn(newInput);
+ if (!in)
+ return ;
+ newMappedId = in->getMappedId();
+ }
+
+ if (oldMappedId != 0) {
+ StudioControl::disconnectStudioObjects
+ (oldMappedId, m_instrument->getMappedId());
+ } else {
+ StudioControl::disconnectStudioObject
+ (m_instrument->getMappedId());
+ }
+
+ StudioControl::setStudioObjectProperty
+ (m_instrument->getMappedId(),
+ MappedAudioFader::InputChannel,
+ MappedObjectValue(newChannel));
+
+ if (newMappedId != 0) {
+ StudioControl::connectStudioObjects
+ (newMappedId, m_instrument->getMappedId());
+ }
+
+ if (newIsBuss) {
+ m_instrument->setAudioInputToBuss(newInput, newChannel);
+ } else {
+ m_instrument->setAudioInputToRecord(newInput, newChannel);
+ }
+
+ break;
+ }
+
+ case Out: {
+ BussId bussId = m_instrument->getAudioOutput();
+ Buss *oldBuss = m_studio->getBussById(bussId);
+ Buss *newBuss = m_studio->getBussById(i);
+ if (!newBuss)
+ return ;
+
+ if (oldBuss) {
+ StudioControl::disconnectStudioObjects
+ (m_instrument->getMappedId(), oldBuss->getMappedId());
+ } else {
+ StudioControl::disconnectStudioObject
+ (m_instrument->getMappedId());
+ }
+
+ StudioControl::connectStudioObjects
+ (m_instrument->getMappedId(), newBuss->getMappedId());
+
+ m_instrument->setAudioOutput(i);
+ break;
+ }
+ }
+
+ slotRepopulate();
+ emit changed();
+}
+
+}
+#include "AudioRouteMenu.moc"
diff --git a/src/gui/widgets/AudioRouteMenu.h b/src/gui/widgets/AudioRouteMenu.h
new file mode 100644
index 0000000..259da68
--- /dev/null
+++ b/src/gui/widgets/AudioRouteMenu.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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_AUDIOROUTEMENU_H_
+#define _RG_AUDIOROUTEMENU_H_
+
+#include <qobject.h>
+#include <qstring.h>
+
+
+class QWidget;
+class KComboBox;
+
+
+namespace Rosegarden
+{
+
+class WheelyButton;
+class Studio;
+class Instrument;
+
+
+class AudioRouteMenu : public QObject
+{
+ Q_OBJECT
+
+public:
+ enum Direction { In, Out };
+ enum Format { Compact, Regular };
+
+ AudioRouteMenu(QWidget *parent,
+ Direction direction,
+ Format format,
+ Studio *studio = 0,
+ Instrument *instrument = 0);
+
+ QWidget *getWidget();
+
+public slots:
+ void slotRepopulate();
+ void slotSetInstrument(Studio *, Instrument *);
+
+protected slots:
+ void slotWheel(bool up);
+ void slotShowMenu();
+ void slotEntrySelected(int);
+
+signals:
+ // The menu writes changes directly to the instrument, but it
+ // also emits this to let you know something has changed
+ void changed();
+
+private:
+ Studio *m_studio;
+ Instrument *m_instrument;
+ Direction m_direction;
+ Format m_format;
+
+ WheelyButton *m_button;
+ KComboBox *m_combo;
+
+ int getNumEntries();
+ int getCurrentEntry(); // for instrument
+ QString getEntryText(int n);
+};
+
+
+
+
+}
+
+#endif
diff --git a/src/gui/widgets/AudioVUMeter.cpp b/src/gui/widgets/AudioVUMeter.cpp
new file mode 100644
index 0000000..4c15a07
--- /dev/null
+++ b/src/gui/widgets/AudioVUMeter.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-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "AudioVUMeter.h"
+
+#include "gui/rulers/VelocityColour.h"
+#include <qcolor.h>
+#include <qlabel.h>
+#include <qpainter.h>
+#include <qtimer.h>
+#include <qwidget.h>
+#include "VUMeter.h"
+
+
+namespace Rosegarden
+{
+
+AudioVUMeter::AudioVUMeter(QWidget *parent,
+ VUMeter::VUMeterType type,
+ bool stereo,
+ bool hasRecord,
+ int width,
+ int height,
+ const char *name) :
+ QWidget(parent, name),
+ m_stereo(stereo)
+{
+ setBackgroundMode(Qt::NoBackground);
+ setFixedSize(width, height);
+
+ // This offset is intended to match that for the height of the
+ // button pixmap in Fader (in studiowidgets.cpp, which
+ // is probably where this class should be too)
+
+ m_yoff = height / 7;
+ m_yoff /= 10;
+ ++m_yoff;
+ m_yoff *= 10;
+ ++m_yoff;
+
+ // This one is _not_ intended to match that for the button width
+
+ m_xoff = width / 4;
+ if (m_xoff % 2 == 1)
+ ++m_xoff;
+
+ m_meter = new AudioVUMeterImpl(this, type, stereo, hasRecord,
+ width - m_xoff, height - m_yoff, name);
+
+ m_meter->move(m_xoff / 2, m_yoff / 2);
+}
+
+void
+AudioVUMeter::paintEvent(QPaintEvent *e)
+{
+ QPainter paint(this);
+ paint.setPen(colorGroup().mid());
+ paint.drawRect(0, 0, width(), height());
+
+ paint.setPen(colorGroup().background());
+ paint.setBrush(colorGroup().background());
+ paint.drawRect(1, 1, width() - 2, m_yoff / 2 - 1);
+ paint.drawRect(1, 1, m_xoff / 2 - 1, height() - 2);
+ paint.drawRect(width() - m_xoff / 2 - 1, 1, m_xoff / 2, height() - 2);
+ paint.drawRect(1, height() - m_yoff / 2 - 1, width() - 2, m_yoff / 2);
+ paint.end();
+
+ m_meter->paintEvent(e);
+}
+
+AudioVUMeter::AudioVUMeterImpl::AudioVUMeterImpl(QWidget *parent,
+ VUMeterType type,
+ bool stereo,
+ bool hasRecord,
+ int width,
+ int height,
+ const char *name) :
+ VUMeter(parent, type, stereo, hasRecord, width, height, VUMeter::Vertical, name)
+{}
+
+}
diff --git a/src/gui/widgets/AudioVUMeter.h b/src/gui/widgets/AudioVUMeter.h
new file mode 100644
index 0000000..cff7c27
--- /dev/null
+++ b/src/gui/widgets/AudioVUMeter.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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_AUDIOVUMETER_H_
+#define _RG_AUDIOVUMETER_H_
+
+#include <qwidget.h>
+#include "VUMeter.h"
+
+
+class QPaintEvent;
+
+
+namespace Rosegarden
+{
+
+
+
+class AudioVUMeter : public QWidget
+{
+public:
+ AudioVUMeter(QWidget *parent = 0,
+ VUMeter::VUMeterType type = VUMeter::AudioPeakHoldShort,
+ bool stereo = true,
+ bool hasRecord = false,
+ int width = 12,
+ int height = 140,
+ const char *name = 0);
+
+ void setLevel(double dB) {
+ m_meter->setLevel(dB);
+ }
+ void setLevel(double dBleft, double dBright) {
+ m_meter->setLevel(dBleft, dBright);
+ }
+
+ void setRecordLevel(double dB) {
+ m_meter->setRecordLevel(dB);
+ }
+ void setRecordLevel(double dBleft, double dBright) {
+ m_meter->setRecordLevel(dBleft, dBright);
+ }
+
+ virtual void paintEvent(QPaintEvent*);
+
+protected:
+ class AudioVUMeterImpl : public VUMeter
+ {
+ public:
+ AudioVUMeterImpl(QWidget *parent,
+ VUMeterType type,
+ bool stereo,
+ bool hasRecord,
+ int width,
+ int height,
+ const char *name);
+ protected:
+ virtual void meterStart() { }
+ virtual void meterStop() { }
+ };
+
+ AudioVUMeterImpl *m_meter;
+ bool m_stereo;
+ int m_yoff;
+ int m_xoff;
+};
+
+
+// A push button that emits wheel events. Used by AudioRouteMenu.
+//
+
+}
+
+#endif
diff --git a/src/gui/widgets/BigArrowButton.h b/src/gui/widgets/BigArrowButton.h
new file mode 100644
index 0000000..505e374
--- /dev/null
+++ b/src/gui/widgets/BigArrowButton.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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_BIGARROWBUTTON_H_
+#define _RG_BIGARROWBUTTON_H_
+
+#include <karrowbutton.h>
+
+namespace Rosegarden {
+
+class BigArrowButton : public KArrowButton
+{
+public:
+ BigArrowButton(QWidget *parent = 0, Qt::ArrowType arrow = Qt::UpArrow,
+ const char *name = 0) :
+ KArrowButton(parent, arrow, name) { }
+ virtual ~BigArrowButton() { }
+
+ virtual QSize sizeHint() const {
+ return QSize(20, 20);
+ }
+};
+
+}
+
+#endif /*BIGARROWBUTTON_H_*/
diff --git a/src/gui/widgets/CollapsingFrame.cpp b/src/gui/widgets/CollapsingFrame.cpp
new file mode 100644
index 0000000..1f71ebf
--- /dev/null
+++ b/src/gui/widgets/CollapsingFrame.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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "CollapsingFrame.h"
+#include <kapplication.h>
+#include <kstddirs.h>
+#include <kconfig.h>
+#include <kglobal.h>
+#include <qfont.h>
+#include <qframe.h>
+#include <qlayout.h>
+#include <qpixmap.h>
+#include <qstring.h>
+#include <qtoolbutton.h>
+#include <qwidget.h>
+#include <cassert>
+
+
+namespace Rosegarden
+{
+
+CollapsingFrame::CollapsingFrame(QString label, QWidget *parent, const char *n) :
+ QFrame(parent, n),
+ m_widget(0),
+ m_fill(false),
+ m_collapsed(false)
+{
+ m_layout = new QGridLayout(this, 3, 3, 0, 0);
+
+ m_toggleButton = new QToolButton(this);
+ m_toggleButton->setTextLabel(label);
+ m_toggleButton->setUsesTextLabel(true);
+ m_toggleButton->setUsesBigPixmap(false);
+ m_toggleButton->setTextPosition(QToolButton::BesideIcon);
+ m_toggleButton->setAutoRaise(true);
+
+ QFont font(m_toggleButton->font());
+ font.setBold(true);
+ m_toggleButton->setFont(font);
+
+ QString pixmapDir = KGlobal::dirs()->findResource("appdata", "pixmaps/");
+ QPixmap pixmap(pixmapDir + "/misc/arrow-expanded.png");
+ m_toggleButton->setIconSet(pixmap);
+
+ connect(m_toggleButton, SIGNAL(clicked()), this, SLOT(toggle()));
+
+ m_layout->addMultiCellWidget(m_toggleButton, 0, 0, 0, 2);
+}
+
+CollapsingFrame::~CollapsingFrame()
+{}
+
+void
+CollapsingFrame::setWidgetFill(bool fill)
+{
+ m_fill = fill;
+}
+
+QFont
+CollapsingFrame::font() const
+{
+ return m_toggleButton->font();
+}
+
+void
+CollapsingFrame::setFont(QFont font)
+{
+ m_toggleButton->setFont(font);
+}
+
+void
+CollapsingFrame::setWidget(QWidget *widget)
+{
+ assert(!m_widget);
+ m_widget = widget;
+ if (m_fill) {
+ m_layout->addMultiCellWidget(widget, 1, 1, 0, 2);
+ } else {
+ m_layout->addWidget(widget, 1, 1);
+ }
+
+ bool expanded = true;
+ if (name(0)) {
+ KConfig *config = kapp->config();
+ QString groupTemp = config->group();
+ config->setGroup("CollapsingFrame");
+ expanded = config->readBoolEntry(name(), true);
+ config->setGroup(groupTemp);
+ }
+ if (expanded != !m_collapsed)
+ toggle();
+}
+
+void
+CollapsingFrame::toggle()
+{
+ int h = m_toggleButton->height();
+
+ m_collapsed = !m_collapsed;
+
+ m_widget->setShown(!m_collapsed);
+
+ QString pixmapDir = KGlobal::dirs()->findResource("appdata", "pixmaps/");
+ QPixmap pixmap;
+
+ if (m_collapsed) {
+ pixmap = QPixmap(pixmapDir + "/misc/arrow-contracted.png");
+ } else {
+ pixmap = QPixmap(pixmapDir + "/misc/arrow-expanded.png");
+ }
+
+ if (name(0)) {
+ KConfig *config = kapp->config();
+ QString groupTemp = config->group();
+ config->setGroup("CollapsingFrame");
+ config->writeEntry(name(), !m_collapsed);
+ config->setGroup(groupTemp);
+ }
+
+ m_toggleButton->setIconSet(pixmap);
+
+ m_toggleButton->setMaximumHeight(h);
+}
+
+}
+#include "CollapsingFrame.moc"
diff --git a/src/gui/widgets/CollapsingFrame.h b/src/gui/widgets/CollapsingFrame.h
new file mode 100644
index 0000000..780a8b1
--- /dev/null
+++ b/src/gui/widgets/CollapsingFrame.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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_COLLAPSINGFRAME_H_
+#define _RG_COLLAPSINGFRAME_H_
+
+#include <qframe.h>
+#include <qstring.h>
+
+
+class QWidget;
+class QToolButton;
+class QGridLayout;
+
+
+namespace Rosegarden
+{
+
+
+
+class CollapsingFrame : public QFrame
+{
+ Q_OBJECT
+
+public:
+ CollapsingFrame(QString label, QWidget *parent = 0, const char *name = 0);
+ virtual ~CollapsingFrame();
+
+ QFont font() const;
+ void setFont(QFont font);
+
+ /// If true, the widget fills the available space. Call before setWidget
+ void setWidgetFill(bool fill);
+
+ /// This frame contains a single other widget. Set it here.
+ void setWidget(QWidget *w);
+
+public slots:
+ void toggle();
+
+protected:
+ QGridLayout *m_layout;
+ QToolButton *m_toggleButton;
+ QWidget *m_widget;
+ bool m_fill;
+ bool m_collapsed;
+};
+
+
+
+}
+
+#endif
diff --git a/src/gui/widgets/ColourTable.cpp b/src/gui/widgets/ColourTable.cpp
new file mode 100644
index 0000000..c5fcfc6
--- /dev/null
+++ b/src/gui/widgets/ColourTable.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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "ColourTable.h"
+
+#include <klocale.h>
+#include "misc/Strings.h"
+#include "base/ColourMap.h"
+#include "ColourTableItem.h"
+#include "gui/general/GUIPalette.h"
+#include <kcolordialog.h>
+#include <klineeditdlg.h>
+#include <qcolor.h>
+#include <qpoint.h>
+#include <qstring.h>
+#include <qtable.h>
+#include <qwidget.h>
+
+
+namespace Rosegarden
+{
+
+ColourTable::ColourTable
+(QWidget *parent, ColourMap &input, ColourList &list)
+ : QTable(1, 2, parent, "RColourTable")
+{
+ setSorting(FALSE);
+ setSelectionMode(QTable::SingleRow);
+ horizontalHeader()->setLabel(0, i18n("Name"));
+ horizontalHeader()->setLabel(1, i18n("Color"));
+ populate_table(input, list);
+ connect(this, SIGNAL(doubleClicked(int, int, int, const QPoint&)),
+ SLOT(slotEditEntry(int, int)));
+
+}
+
+void
+ColourTable::slotEditEntry(int row, int col)
+{
+
+ switch (col) {
+ case 0: {
+ if (row == 0)
+ return ;
+ bool ok = false;
+ QString newName = KLineEditDlg::getText(i18n("Modify Color Name"), i18n("Enter new name"),
+ item(row, col)->text(), &ok);
+
+ if ((ok == true) && (!newName.isEmpty())) {
+ emit entryTextChanged(row, newName);
+ return ;
+ }
+ }
+ break;
+ case 1: {
+ QColor temp = m_colours[row];
+ KColorDialog box(this, "", true);
+
+ int result = box.getColor(temp);
+
+ if (result == KColorDialog::Accepted) {
+ emit entryColourChanged(row, temp);
+ return ;
+ }
+ }
+ break;
+ default: // Should never happen
+ break;
+ }
+
+}
+
+void
+ColourTable::populate_table(ColourMap &input, ColourList &list)
+{
+ m_colours.reserve(input.size());
+ setNumRows(input.size());
+
+ QString name;
+
+ unsigned int i = 0;
+
+ for (RCMap::const_iterator it = input.begin(); it != input.end(); ++it) {
+ if (it->second.second == std::string(""))
+ name = i18n("Default Color");
+ else
+ name = strtoqstr(it->second.second);
+
+ QTableItem *text = new QTableItem(
+ dynamic_cast<QTable*>(this),
+ QTableItem::Never, name);
+
+ setItem(i, 0, text);
+
+ list[i] = it->first;
+ m_colours[i] = GUIPalette::convertColour(it->second.first);
+
+ ColourTableItem *temp = new ColourTableItem(this, m_colours[i]);
+ setItem(i, 1, temp);
+
+ verticalHeader()->setLabel(i, QString::number(it->first));
+
+ ++i;
+ }
+
+}
+
+}
+#include "ColourTable.moc"
diff --git a/src/gui/widgets/ColourTable.h b/src/gui/widgets/ColourTable.h
new file mode 100644
index 0000000..48e2309
--- /dev/null
+++ b/src/gui/widgets/ColourTable.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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ Portions of this file Copyright 2003
+ Mark Hymers <markh@linuxfromscratch.org>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_ROSEGARDENCOLOURTABLE_H_
+#define _RG_ROSEGARDENCOLOURTABLE_H_
+
+#include <map>
+#include <qtable.h>
+#include <vector>
+
+
+class QWidget;
+class ColourList;
+
+
+namespace Rosegarden
+{
+
+class ColourMap;
+
+
+class ColourTable : public QTable
+{
+ Q_OBJECT
+
+public:
+ typedef std::map<unsigned int, unsigned int, std::less<unsigned int> > ColourList;
+ ColourTable(QWidget *parent, ColourMap &input, ColourList &list);
+ void populate_table(ColourMap &input, ColourList &list);
+
+
+signals:
+ void entryTextChanged(unsigned int, QString);
+ void entryColourChanged(unsigned int, QColor);
+
+public slots:
+ void slotEditEntry (int, int);
+
+protected:
+ std::vector<QColor> m_colours;
+
+};
+
+
+}
+
+#endif
diff --git a/src/gui/widgets/ColourTableItem.cpp b/src/gui/widgets/ColourTableItem.cpp
new file mode 100644
index 0000000..3dfbd87
--- /dev/null
+++ b/src/gui/widgets/ColourTableItem.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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "ColourTableItem.h"
+
+#include <qcolor.h>
+#include <qpainter.h>
+#include <qrect.h>
+#include <qtable.h>
+
+
+namespace Rosegarden
+{
+
+void
+ColourTableItem::setColor(QColor &input)
+{
+ currentColour = input;
+}
+
+void
+ColourTableItem::paint(QPainter *p, const QColorGroup &cg, const QRect &cr, bool selected)
+{
+ QColorGroup g(cg);
+ g.setColor(QColorGroup::Base, currentColour);
+ selected = false;
+ QTableItem::paint(p, g, cr, selected);
+}
+
+}
diff --git a/src/gui/widgets/ColourTableItem.h b/src/gui/widgets/ColourTableItem.h
new file mode 100644
index 0000000..a8c906e
--- /dev/null
+++ b/src/gui/widgets/ColourTableItem.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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_ROSEGARDENCOLOURTABLEITEM_H_
+#define _RG_ROSEGARDENCOLOURTABLEITEM_H_
+
+#include <qcolor.h>
+#include <qtable.h>
+
+class QTable;
+class QRect;
+class QPainter;
+class QColorGroup;
+
+
+namespace Rosegarden
+{
+
+
+
+class ColourTableItem : public QTableItem
+{
+public:
+ ColourTableItem(QTable *t, const QColor &input)
+ : QTableItem(t, QTableItem::Never, ""),
+ currentColour(input) {}
+ void setColor(QColor &input);
+ void paint(QPainter *p, const QColorGroup &cg, const QRect &cr, bool selected);
+
+protected:
+ QColor currentColour;
+};
+
+
+
+}
+
+#endif
diff --git a/src/gui/widgets/CurrentProgressDialog.cpp b/src/gui/widgets/CurrentProgressDialog.cpp
new file mode 100644
index 0000000..2e3735f
--- /dev/null
+++ b/src/gui/widgets/CurrentProgressDialog.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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "CurrentProgressDialog.h"
+
+#include "ProgressDialog.h"
+#include <qobject.h>
+
+
+namespace Rosegarden
+{
+
+CurrentProgressDialog* CurrentProgressDialog::getInstance()
+{
+ if (!m_instance)
+ m_instance = new CurrentProgressDialog(0);
+
+ return m_instance;
+}
+
+
+ProgressDialog*
+CurrentProgressDialog::get()
+{
+ return m_currentProgressDialog;
+}
+
+void
+CurrentProgressDialog::set(ProgressDialog* d)
+{
+ if (m_currentProgressDialog)
+ m_currentProgressDialog->disconnect(getInstance());
+
+ m_currentProgressDialog = d;
+
+ // this lets the progress dialog deregister itself when it's deleted
+ connect(d, SIGNAL(destroyed()),
+ getInstance(), SLOT(slotCurrentProgressDialogDestroyed()));
+}
+
+void CurrentProgressDialog::freeze()
+{
+ if (m_currentProgressDialog)
+ m_currentProgressDialog->slotFreeze();
+}
+
+void CurrentProgressDialog::thaw()
+{
+ if (m_currentProgressDialog)
+ m_currentProgressDialog->slotThaw();
+}
+
+void CurrentProgressDialog::slotCurrentProgressDialogDestroyed()
+{
+ m_currentProgressDialog = 0;
+}
+
+CurrentProgressDialog* CurrentProgressDialog::m_instance = 0;
+ProgressDialog* CurrentProgressDialog::m_currentProgressDialog = 0;
+
+}
+#include "CurrentProgressDialog.moc"
diff --git a/src/gui/widgets/CurrentProgressDialog.h b/src/gui/widgets/CurrentProgressDialog.h
new file mode 100644
index 0000000..d0eea2e
--- /dev/null
+++ b/src/gui/widgets/CurrentProgressDialog.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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_CURRENTPROGRESSDIALOG_H_
+#define _RG_CURRENTPROGRESSDIALOG_H_
+
+#include <qobject.h>
+
+
+
+
+namespace Rosegarden
+{
+
+class ProgressDialog;
+
+
+class CurrentProgressDialog : public QObject
+{
+ Q_OBJECT
+public:
+ static CurrentProgressDialog* getInstance();
+
+ static ProgressDialog* get();
+ static void set(ProgressDialog*);
+
+ /**
+ * Block the current progress so that it won't appear
+ * regardless of passing time and occurring events.
+ * This is useful when you want to show another dialog
+ * and you want to make sure the progress dialog is out of the way
+ */
+ static void freeze();
+
+ /**
+ * Restores the progress dialog to its normal state atfer a freeze()
+ */
+ static void thaw();
+
+public slots:
+ /// Called then the current progress dialog is being destroyed
+ void slotCurrentProgressDialogDestroyed();
+
+protected:
+ CurrentProgressDialog(QObject* parent, const char* name = 0)
+ : QObject(parent, name) {}
+
+ //--------------- Data members ---------------------------------
+ static CurrentProgressDialog* m_instance;
+ static ProgressDialog* m_currentProgressDialog;
+};
+
+
+// A Text popup - a tooltip we can control.
+//
+
+}
+
+#endif
diff --git a/src/gui/widgets/DiatonicPitchChooser.cpp b/src/gui/widgets/DiatonicPitchChooser.cpp
new file mode 100644
index 0000000..95b8b3a
--- /dev/null
+++ b/src/gui/widgets/DiatonicPitchChooser.cpp
@@ -0,0 +1,244 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
+
+/*
+ Rosegarden
+ A MIDI and audio sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "DiatonicPitchChooser.h"
+
+#include <iostream>
+#include <klocale.h>
+#include "base/NotationRules.h"
+#include "base/NotationTypes.h"
+#include "gui/general/MidiPitchLabel.h"
+#include "PitchDragLabel.h"
+#include <kcombobox.h>
+#include <qgroupbox.h>
+#include <qhbox.h>
+#include <qlabel.h>
+#include <qspinbox.h>
+#include <qstring.h>
+#include <qwidget.h>
+
+namespace Rosegarden
+{
+
+DiatonicPitchChooser::DiatonicPitchChooser(QString title,
+ QWidget *parent,
+ int defaultNote,
+ int defaultPitch,
+ int defaultOctave) :
+ QGroupBox(1, Horizontal, title, parent),
+ m_defaultPitch(defaultPitch)
+{
+ m_pitchDragLabel = new PitchDragLabel(this, defaultPitch);
+
+ QHBox *hbox = new QHBox(this);
+ hbox->setSpacing(6);
+
+ m_step = new KComboBox( hbox );
+ m_step->setSizeLimit( 7 );
+ m_step->insertItem(i18n("C"));
+ m_step->insertItem(i18n("D"));
+ m_step->insertItem(i18n("E"));
+ m_step->insertItem(i18n("F"));
+ m_step->insertItem(i18n("G"));
+ m_step->insertItem(i18n("A"));
+ m_step->insertItem(i18n("B"));
+ m_step->setCurrentItem(defaultNote);
+
+ m_octave = new KComboBox( hbox );
+ m_octave->insertItem(i18n("-2"));
+ m_octave->insertItem(i18n("-1"));
+ m_octave->insertItem(i18n("0"));
+ m_octave->insertItem(i18n("1"));
+ m_octave->insertItem(i18n("2"));
+ m_octave->insertItem(i18n("3"));
+ m_octave->insertItem(i18n("4"));
+ m_octave->insertItem(i18n("5"));
+ m_octave->insertItem(i18n("6"));
+ m_octave->insertItem(i18n("7"));
+ m_octave->setCurrentItem(defaultOctave);
+
+ m_accidental = new KComboBox( hbox );
+ m_accidental->insertItem(i18n("double flat"));
+ m_accidental->insertItem(i18n("flat"));
+ m_accidental->insertItem(i18n("natural"));
+ m_accidental->insertItem(i18n("sharp"));
+ m_accidental->insertItem(i18n("double sharp"));
+ m_accidental->setCurrentItem(2); // default: natural
+
+ m_pitchLabel = new QLabel(QString("%1").arg(getPitch()), hbox);
+
+ m_pitchLabel->setMinimumWidth(40);
+
+ connect(m_accidental, SIGNAL(activated(int)),
+ this, SLOT(slotSetAccidental(int)));
+
+ connect(m_octave, SIGNAL(activated(int)),
+ this, SLOT(slotSetOctave(int)));
+
+ connect(m_step, SIGNAL(activated(int)),
+ this, SLOT(slotSetStep(int)));
+
+ //connect(m_pitch, SIGNAL(valueChanged(int)),
+ // this, SIGNAL(pitchChanged(int)));
+
+ //connect(m_pitch, SIGNAL(valueChanged(int)),
+ // this, SIGNAL(preview(int)));
+
+ connect(m_pitchDragLabel, SIGNAL(pitchDragged(int,int,int)),
+ this, SLOT(slotSetNote(int,int,int)));
+
+ //connect(m_pitchDragLabel, SIGNAL(pitchChanged(int)),
+ // this, SLOT(slotSetPitch(int)));
+
+ connect(m_pitchDragLabel, SIGNAL(pitchChanged(int,int,int)),
+ this, SLOT(slotSetNote(int,int,int)));
+
+ //connect(m_pitchDragLabel, SIGNAL(pitchChanged(int)),
+ // this, SIGNAL(pitchChanged(int)));
+
+ connect(m_pitchDragLabel, SIGNAL(pitchDragged(int,int,int)),
+ this, SIGNAL(noteChanged(int,int,int)));
+
+ connect(m_pitchDragLabel, SIGNAL(pitchChanged(int,int,int)),
+ this, SIGNAL(noteChanged(int,int,int)));
+
+ connect(m_pitchDragLabel, SIGNAL(preview(int)),
+ this, SIGNAL(preview(int)));
+
+}
+
+int
+DiatonicPitchChooser::getPitch() const
+{
+ return 12 * m_octave->currentItem() + scale_Cmajor[m_step->currentItem()] +
+ (m_accidental->currentItem() - 2);
+}
+
+int
+DiatonicPitchChooser::getAccidental()
+{
+ return m_accidental->currentItem() - 2;
+}
+
+void
+DiatonicPitchChooser::slotSetPitch(int pitch)
+{
+ if (m_pitchDragLabel->getPitch() != pitch)
+ m_pitchDragLabel->slotSetPitch(pitch);
+
+ m_octave->setCurrentItem((int)(((long) pitch) / 12));
+
+ int step = steps_Cmajor[pitch % 12];
+ m_step->setCurrentItem(step);
+
+ int pitchChange = (pitch % 12) - scale_Cmajor[step];
+
+ m_accidental->setCurrentItem(pitchChange + 2);
+
+ m_pitchLabel->setText(QString("%1").arg(pitch));
+
+ update();
+}
+
+void
+DiatonicPitchChooser::slotSetStep(int step)
+{
+ if (m_step->currentItem() != step)
+ m_step->setCurrentItem(step);
+ std::cout << "slot_step called" << std::endl;
+ setLabelsIfNeeded();
+ update();
+}
+
+void
+DiatonicPitchChooser::slotSetOctave(int octave)
+{
+ if (m_octave->currentItem() != octave)
+ m_octave->setCurrentItem(octave);
+ setLabelsIfNeeded();
+ update();
+}
+
+/** input 0..4: doubleflat .. doublesharp */
+void
+DiatonicPitchChooser::slotSetAccidental(int accidental)
+{
+ if (m_accidental->currentItem() != accidental)
+ m_accidental->setCurrentItem(accidental);
+ setLabelsIfNeeded();
+ update();
+}
+
+/** sets the m_pitchDragLabel and m_pitchLabel if needed */
+void
+DiatonicPitchChooser::setLabelsIfNeeded()
+{
+ //if (m_pitchDragLabel->)
+ //{
+ m_pitchDragLabel->slotSetPitch(getPitch(), m_octave->currentItem(), m_step->currentItem());
+ //}
+ m_pitchLabel->setText(QString("%1").arg(getPitch()));
+}
+
+void
+DiatonicPitchChooser::slotSetNote(int pitch, int octave, int step)
+{
+ //if (m_pitch->value() != p)
+ // m_pitch->setValue(p);
+ if (m_pitchDragLabel->getPitch() != pitch)
+ m_pitchDragLabel->slotSetPitch(pitch, octave, step);
+
+ m_octave->setCurrentItem(octave);
+ m_step->setCurrentItem(step);
+
+ int pitchOffset = pitch - (octave * 12 + scale_Cmajor[step]);
+ m_accidental->setCurrentItem(pitchOffset + 2);
+
+ //MidiPitchLabel pl(p);
+ m_pitchLabel->setText(QString("%1").arg(pitch));
+ update();
+}
+
+void
+DiatonicPitchChooser::slotResetToDefault()
+{
+ slotSetPitch(m_defaultPitch);
+}
+
+int
+DiatonicPitchChooser::getOctave() const
+{
+ return m_octave->currentItem();
+}
+
+
+int
+DiatonicPitchChooser::getStep() const
+{
+ return m_step->currentItem();
+}
+
+}
+#include "DiatonicPitchChooser.moc"
diff --git a/src/gui/widgets/DiatonicPitchChooser.h b/src/gui/widgets/DiatonicPitchChooser.h
new file mode 100644
index 0000000..8e5b20d
--- /dev/null
+++ b/src/gui/widgets/DiatonicPitchChooser.h
@@ -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-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_ROSEGARDENDIATONICPITCHCHOOSER_H_
+#define _RG_ROSEGARDENDIATONICPITCHCHOOSER_H_
+
+#include <qgroupbox.h>
+#include <qstring.h>
+
+
+class QWidget;
+class QSpinBox;
+class QComboBox;
+class QLabel;
+
+
+namespace Rosegarden
+{
+
+class PitchDragLabel;
+
+
+class DiatonicPitchChooser : public QGroupBox
+{
+ Q_OBJECT
+public:
+ DiatonicPitchChooser(QString title,
+ QWidget *parent,
+ int defaultNote = 0,
+ int defaultPitch = 60,
+ int defaultOctave = 5);
+
+ // C0=0, D0=1, C1=12, etc.
+ int getPitch() const;
+
+ // C=0, D=1, E=2, F=3, etc.
+ int getStep() const;
+
+ // pitch 0 is the first C of octave 0.
+ int getOctave() const;
+
+ // 0 = none,
+ // -x = x flats
+ // x = x sharps
+ int getAccidental();
+
+signals:
+ void pitchChanged(int);
+ //pitch, octave, step
+ void noteChanged(int,int,int);
+ void preview(int);
+
+public slots:
+ void slotSetPitch(int);
+ //pitch, octave, step
+ void slotSetNote(int,int,int);
+ void slotSetStep(int);
+ void slotSetOctave(int);
+ void slotSetAccidental(int);
+ void slotResetToDefault();
+
+protected:
+ int m_defaultPitch;
+
+ PitchDragLabel *m_pitchDragLabel;
+
+ QComboBox *m_step;
+ QComboBox *m_accidental;
+ QComboBox *m_octave;
+
+ QLabel *m_pitchLabel;
+
+private:
+ void setLabelsIfNeeded();
+};
+
+
+
+}
+
+#endif
diff --git a/src/gui/widgets/Fader.cpp b/src/gui/widgets/Fader.cpp
new file mode 100644
index 0000000..2413323
--- /dev/null
+++ b/src/gui/widgets/Fader.cpp
@@ -0,0 +1,567 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
+
+/*
+ Rosegarden
+ A MIDI and audio sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "Fader.h"
+#include "TextFloat.h"
+
+#include "misc/Debug.h"
+#include "base/AudioLevel.h"
+#include <qcolor.h>
+#include <qevent.h>
+#include <qpainter.h>
+#include <qpixmap.h>
+#include <qpoint.h>
+#include <qstring.h>
+#include <qtimer.h>
+#include <qwidget.h>
+#include <cmath>
+
+namespace Rosegarden
+{
+
+Fader::PixmapCache Fader::m_pixmapCache;
+
+
+Fader::Fader(AudioLevel::FaderType type,
+ int w, int h, QWidget *parent) :
+ QWidget(parent),
+ m_integral(false),
+ m_vertical(h > w),
+ m_min(0),
+ m_max(0),
+ m_type(type),
+ m_clickMousePos( -1),
+ m_float(new TextFloat(this)),
+ m_floatTimer(new QTimer())
+{
+ setBackgroundMode(Qt::NoBackground);
+ setFixedSize(w, h); // provisional
+ calculateButtonPixmap();
+ // if (m_vertical) {
+ // setFixedSize(w, h + m_buttonPixmap->height() + 4);
+ // } else {
+ // setFixedSize(w + m_buttonPixmap->width() + 4, h);
+ // }
+
+ if (m_vertical) {
+ m_sliderMin = buttonPixmap()->height() / 2 + 2;
+ m_sliderMax = height() - m_sliderMin;
+ } else {
+ m_sliderMin = buttonPixmap()->width() / 2 + 2;
+ m_sliderMax = width() - m_sliderMin;
+ }
+
+ m_outlineColour = colorGroup().mid();
+
+ calculateGroovePixmap();
+ setFader(0.0);
+
+ connect(m_floatTimer, SIGNAL(timeout()), this, SLOT(slotFloatTimeout()));
+ m_float->hide();
+}
+
+Fader::Fader(int min, int max, int deflt,
+ int w, int h, QWidget *parent) :
+ QWidget(parent),
+ m_integral(true),
+ m_vertical(h > w),
+ m_min(min),
+ m_max(max),
+ m_clickMousePos( -1),
+ m_float(new TextFloat(this)),
+ m_floatTimer(new QTimer())
+{
+ setBackgroundMode(Qt::NoBackground);
+ setFixedSize(w, h); // provisional
+ calculateButtonPixmap();
+ // if (m_vertical) {
+ // setFixedSize(w, h + m_buttonPixmap->height() + 4);
+ // } else {
+ // setFixedSize(w + m_buttonPixmap->width() + 4, h);
+ // }
+
+ if (m_vertical) {
+ m_sliderMin = buttonPixmap()->height() / 2 + 2;
+ m_sliderMax = height() - m_sliderMin;
+ } else {
+ m_sliderMin = buttonPixmap()->width() / 2 + 2;
+ m_sliderMax = width() - m_sliderMin;
+ }
+
+ m_outlineColour = colorGroup().mid();
+
+ calculateGroovePixmap();
+ setFader(deflt);
+
+ connect(m_floatTimer, SIGNAL(timeout()), this, SLOT(slotFloatTimeout()));
+ m_float->hide();
+}
+
+Fader::Fader(int min, int max, int deflt,
+ bool vertical, QWidget *parent) :
+ QWidget(parent),
+ m_integral(true),
+ m_vertical(vertical),
+ m_min(min),
+ m_max(max),
+ m_clickMousePos( -1),
+ m_float(new TextFloat(this)),
+ m_floatTimer(new QTimer())
+{
+ setBackgroundMode(Qt::NoBackground);
+ calculateButtonPixmap();
+
+ if (m_vertical) {
+ m_sliderMin = buttonPixmap()->height() / 2 + 2;
+ m_sliderMax = height() - m_sliderMin;
+ } else {
+ m_sliderMin = buttonPixmap()->width() / 2 + 2;
+ m_sliderMax = width() - m_sliderMin;
+ }
+
+ m_outlineColour = colorGroup().mid();
+
+ calculateGroovePixmap();
+ setFader(deflt);
+
+ connect(m_floatTimer, SIGNAL(timeout()), this, SLOT(slotFloatTimeout()));
+ m_float->hide();
+}
+
+Fader::~Fader()
+{}
+
+void
+Fader::setOutlineColour(QColor c)
+{
+ m_outlineColour = c;
+ calculateGroovePixmap();
+}
+
+QPixmap *
+Fader::groovePixmap()
+{
+ PixmapCache::iterator i = m_pixmapCache.find(SizeRec(width(), height()));
+ if (i != m_pixmapCache.end()) {
+ ColourPixmapRec::iterator j = i->second.first.find(m_outlineColour.pixel());
+ if (j != i->second.first.end()) {
+ return j->second;
+ }
+ }
+ return 0;
+}
+
+QPixmap *
+Fader::buttonPixmap()
+{
+ PixmapCache::iterator i = m_pixmapCache.find(SizeRec(width(), height()));
+ if (i != m_pixmapCache.end()) {
+ return i->second.second;
+ } else
+ return 0;
+}
+
+float
+Fader::getFaderLevel() const
+{
+ return m_value;
+}
+
+void
+Fader::setFader(float value)
+{
+ m_value = value;
+ emit faderChanged(value);
+ paintEvent(0);
+}
+
+float
+Fader::position_to_value(int position)
+{
+ float value;
+
+ if (m_integral) {
+ float sliderLength = float(m_sliderMax) - float(m_sliderMin);
+ value = float(position)
+ * float(m_max - m_min) / sliderLength - float(m_min);
+ if (value > m_max)
+ value = float(m_max);
+ if (value < m_min)
+ value = float(m_min);
+ } else {
+ value = AudioLevel::fader_to_dB
+ (position, m_sliderMax - m_sliderMin, m_type);
+ }
+ /*
+ RG_DEBUG << "Fader::position_to_value - position = " << position
+ << ", new value = " << value << endl;
+
+ if (m_min != m_max) // works for integral case
+ {
+ if (value > m_max) value = float(m_max);
+ if (value < m_min) value = float(m_min);
+ }
+
+ RG_DEBUG << "Fader::position_to_value - limited value = " << value << endl;
+ */
+ return value;
+}
+
+int
+Fader::value_to_position(float value)
+{
+ int position;
+
+ if (m_integral) {
+ float sliderLength = float(m_sliderMax) - float(m_sliderMin);
+ position =
+ int(sliderLength * (value - float(m_min)) / float(m_max - m_min) + 0.1);
+ } else {
+ position =
+ AudioLevel::dB_to_fader
+ (value, m_sliderMax - m_sliderMin, m_type);
+ }
+
+ return position;
+}
+
+void
+Fader::paintEvent(QPaintEvent *)
+{
+ QPainter paint(this);
+ int position = value_to_position(m_value);
+
+ if (m_vertical) {
+
+ int aboveButton = height() - position - m_sliderMin - buttonPixmap()->height() / 2;
+ int belowButton = position + m_sliderMin - buttonPixmap()->height() / 2;
+
+ if (aboveButton > 0) {
+ paint.drawPixmap(0, 0,
+ *groovePixmap(),
+ 0, 0,
+ groovePixmap()->width(), aboveButton);
+ }
+
+ if (belowButton > 0) {
+ paint.drawPixmap(0, aboveButton + buttonPixmap()->height(),
+ *groovePixmap(),
+ 0, aboveButton + buttonPixmap()->height(),
+ groovePixmap()->width(), belowButton);
+ }
+
+ int buttonMargin = (width() - buttonPixmap()->width()) / 2;
+
+ paint.drawPixmap(buttonMargin, aboveButton, *buttonPixmap());
+
+ paint.drawPixmap(0, aboveButton,
+ *groovePixmap(),
+ 0, aboveButton,
+ buttonMargin, buttonPixmap()->height());
+
+ paint.drawPixmap(buttonMargin + buttonPixmap()->width(), aboveButton,
+ *groovePixmap(),
+ buttonMargin + buttonPixmap()->width(), aboveButton,
+ width() - buttonMargin - buttonPixmap()->width(),
+ buttonPixmap()->height());
+
+ } else {
+ //... update
+ int leftOfButton =
+ (m_sliderMax - m_sliderMin) - position - buttonPixmap()->width() / 2;
+
+ int rightOfButton =
+ position - buttonPixmap()->width() / 2;
+
+ if (leftOfButton > 0) {
+ paint.drawPixmap(0, 0,
+ *groovePixmap(),
+ 0, 0,
+ leftOfButton, groovePixmap()->height());
+ }
+
+ if (rightOfButton > 0) {
+ paint.drawPixmap(rightOfButton + buttonPixmap()->width(), 0,
+ *groovePixmap(),
+ groovePixmap()->width() - rightOfButton, 0,
+ rightOfButton, groovePixmap()->height());
+ }
+
+ paint.drawPixmap(leftOfButton, 0, *buttonPixmap());
+ }
+
+ paint.end();
+}
+
+void
+Fader::mousePressEvent(QMouseEvent *e)
+{
+ m_clickMousePos = -1;
+
+ if (e->button() == LeftButton) {
+
+ if (e->type() == QEvent::MouseButtonDblClick) {
+ setFader(0);
+ return ;
+ }
+
+ if (m_vertical) {
+ int buttonPosition = value_to_position(m_value);
+ int clickPosition = height() - e->y() - m_sliderMin;
+
+ if (clickPosition < buttonPosition + buttonPixmap()->height() / 2 &&
+ clickPosition > buttonPosition - buttonPixmap()->height() / 2) {
+ m_clickMousePos = clickPosition;
+ m_clickButtonPos = value_to_position(m_value);
+ showFloatText();
+ }
+ }
+ }
+}
+
+void
+Fader::mouseReleaseEvent(QMouseEvent *e)
+{
+ mouseMoveEvent(e);
+ m_clickMousePos = -1;
+}
+
+void
+Fader::mouseMoveEvent(QMouseEvent *e)
+{
+ if (m_clickMousePos >= 0) {
+ if (m_vertical) {
+ int mousePosition = height() - e->y() - m_sliderMin;
+ int delta = mousePosition - m_clickMousePos;
+ int buttonPosition = m_clickButtonPos + delta;
+ if (buttonPosition < 0)
+ buttonPosition = 0;
+ if (buttonPosition > m_sliderMax - m_sliderMin) {
+ buttonPosition = m_sliderMax - m_sliderMin;
+ }
+ setFader(position_to_value(buttonPosition));
+ showFloatText();
+ }
+ }
+}
+
+void
+Fader::wheelEvent(QWheelEvent *e)
+{
+ int buttonPosition = value_to_position(m_value);
+ if (e->state() & ShiftButton) {
+ if (e->delta() > 0)
+ buttonPosition += 10;
+ else
+ buttonPosition -= 10;
+ } else {
+ if (e->delta() > 0)
+ buttonPosition += 1;
+ else
+ buttonPosition -= 1;
+ }
+ RG_DEBUG << "Fader::wheelEvent - button position = " << buttonPosition << endl;
+ setFader(position_to_value(buttonPosition));
+ RG_DEBUG << "Fader::wheelEvent - value = " << m_value << endl;
+
+ showFloatText();
+}
+
+void
+Fader::showFloatText()
+{
+ // draw on the float text
+
+ QString text;
+
+ if (m_integral) {
+ text = QString("%1").arg(int(m_value));
+ } else if (m_value == AudioLevel::DB_FLOOR) {
+ text = "Off";
+ } else {
+ float v = fabs(m_value);
+ text = QString("%1%2.%3%4%5 dB")
+ .arg(m_value < 0 ? '-' : '+')
+ .arg(int(v))
+ .arg(int(v * 10) % 10)
+ .arg(int(v * 100) % 10)
+ .arg(int(v * 1000) % 10);
+ }
+
+ m_float->setText(text);
+
+ // Reposition - we need to sum the relative positions up to the
+ // topLevel or dialog to please move().
+ //
+ QWidget *par = parentWidget();
+ QPoint totalPos = this->pos();
+
+ while (par->parentWidget() && !par->isTopLevel() && !par->isDialog()) {
+ totalPos += par->pos();
+ par = par->parentWidget();
+ }
+
+ // Move just top/right
+ //
+ m_float->move(totalPos + QPoint(width() + 2, 0));
+
+ // Show
+ m_float->show();
+
+ // one shot, 500ms
+ m_floatTimer->start(500, true);
+}
+
+void
+Fader::slotFloatTimeout()
+{
+ m_float->hide();
+}
+
+void
+Fader::calculateGroovePixmap()
+{
+ QPixmap *& map = m_pixmapCache[SizeRec(width(), height())].first[m_outlineColour.pixel()];
+
+ delete map;
+ map = new QPixmap(width(), height());
+ map->fill(colorGroup().background());
+ QPainter paint(map);
+ paint.setBrush(colorGroup().background());
+
+ if (m_vertical) {
+
+ paint.setPen(m_outlineColour);
+ paint.drawRect(0, 0, width(), height());
+
+ if (m_integral) {
+ //...
+ } else {
+ for (int dB = -70; dB <= 10; ) {
+ int position = value_to_position(float(dB));
+ if (position >= 0 &&
+ position < m_sliderMax - m_sliderMin) {
+ if (dB == 0)
+ paint.setPen(colorGroup().dark());
+ else
+ paint.setPen(colorGroup().midlight());
+ paint.drawLine(1, (m_sliderMax - position),
+ width() - 2, (m_sliderMax - position));
+ }
+ if (dB < -10)
+ dB += 10;
+ else
+ dB += 2;
+ }
+ }
+
+ paint.setPen(colorGroup().dark());
+ paint.setBrush(colorGroup().mid());
+ paint.drawRect(width() / 2 - 3, height() - m_sliderMax,
+ 6, m_sliderMax - m_sliderMin);
+ paint.end();
+ } else {
+ //...
+ }
+}
+
+void
+Fader::calculateButtonPixmap()
+{
+ PixmapCache::iterator i = m_pixmapCache.find(SizeRec(width(), height()));
+ if (i != m_pixmapCache.end() && i->second.second)
+ return ;
+
+ QPixmap *& map = m_pixmapCache[SizeRec(width(), height())].second;
+
+ if (m_vertical) {
+
+ int buttonHeight = height() / 7;
+ buttonHeight /= 10;
+ ++buttonHeight;
+ buttonHeight *= 10;
+ ++buttonHeight;
+ int buttonWidth = width() * 2 / 3;
+ buttonWidth /= 5;
+ ++buttonWidth;
+ buttonWidth *= 5;
+ if (buttonWidth > width() - 2)
+ buttonWidth = width() - 2;
+
+ map = new QPixmap(buttonWidth, buttonHeight);
+ map->fill(colorGroup().background());
+
+ int x = 0;
+ int y = 0;
+
+ QPainter paint(map);
+
+ paint.setPen(colorGroup().light());
+ paint.drawLine(x + 1, y, x + buttonWidth - 2, y);
+ paint.drawLine(x, y + 1, x, y + buttonHeight - 2);
+
+ paint.setPen(colorGroup().midlight());
+ paint.drawLine(x + 1, y + 1, x + buttonWidth - 2, y + 1);
+ paint.drawLine(x + 1, y + 1, x + 1, y + buttonHeight - 2);
+
+ paint.setPen(colorGroup().mid());
+ paint.drawLine(x + 2, y + buttonHeight - 2, x + buttonWidth - 2,
+ y + buttonHeight - 2);
+ paint.drawLine(x + buttonWidth - 2, y + 2, x + buttonWidth - 2,
+ y + buttonHeight - 2);
+
+ paint.setPen(colorGroup().dark());
+ paint.drawLine(x + 1, y + buttonHeight - 1, x + buttonWidth - 2,
+ y + buttonHeight - 1);
+ paint.drawLine(x + buttonWidth - 1, y + 1, x + buttonWidth - 1,
+ y + buttonHeight - 2);
+
+ paint.setPen(colorGroup().shadow());
+ paint.drawLine(x + 1, y + buttonHeight / 2, x + buttonWidth - 2,
+ y + buttonHeight / 2);
+
+ paint.setPen(colorGroup().mid());
+ paint.drawLine(x + 1, y + buttonHeight / 2 - 1, x + buttonWidth - 2,
+ y + buttonHeight / 2 - 1);
+ paint.drawPoint(x, y + buttonHeight / 2);
+
+ paint.setPen(colorGroup().light());
+ paint.drawLine(x + 1, y + buttonHeight / 2 + 1, x + buttonWidth - 2,
+ y + buttonHeight / 2 + 1);
+
+ paint.setPen(colorGroup().button());
+ paint.setBrush(colorGroup().button());
+ paint.drawRect(x + 2, y + 2, buttonWidth - 4, buttonHeight / 2 - 4);
+ paint.drawRect(x + 2, y + buttonHeight / 2 + 2,
+ buttonWidth - 4, buttonHeight / 2 - 4);
+
+ paint.end();
+ } else {
+ //...
+ }
+}
+
+}
+#include "Fader.moc"
diff --git a/src/gui/widgets/Fader.h b/src/gui/widgets/Fader.h
new file mode 100644
index 0000000..f4afb24
--- /dev/null
+++ b/src/gui/widgets/Fader.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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_ROSEGARDENFADER_H_
+#define _RG_ROSEGARDENFADER_H_
+
+#include "base/AudioLevel.h"
+#include <map>
+#include <qcolor.h>
+#include <qwidget.h>
+#include <utility>
+
+
+class QWheelEvent;
+class QTimer;
+class QPixmap;
+class QPaintEvent;
+class QMouseEvent;
+
+
+namespace Rosegarden
+{
+
+class TextFloat;
+
+
+class Fader : public QWidget
+{
+ Q_OBJECT
+
+public:
+ /**
+ * Construct a dB fader. The fader calculates its orientation
+ * based on the given dimensions.
+ */
+ Fader(AudioLevel::FaderType,
+ int width, int height, QWidget *parent);
+
+ /**
+ * Construct a fader on an integral scale. The fader calculates
+ * its orientation based on the given dimensions.
+ */
+ Fader(int min, int max, int deflt,
+ int width, int height, QWidget *parent);
+
+ /**
+ * Construct a fader on an integral scale, with a 1:1 ratio of
+ * pixel positions and values.
+ */
+ Fader(int min, int max, int deflt,
+ bool vertical, QWidget *parent);
+
+ virtual ~Fader();
+
+ void setOutlineColour(QColor);
+
+ float getFaderLevel() const;
+
+public slots:
+ void setFader(float value);
+ void slotFloatTimeout();
+
+signals:
+ void faderChanged(float);
+
+protected:
+ virtual void paintEvent(QPaintEvent *);
+ virtual void mousePressEvent(QMouseEvent *);
+ virtual void mouseReleaseEvent(QMouseEvent *);
+ virtual void mouseMoveEvent(QMouseEvent *);
+ virtual void wheelEvent(QWheelEvent *);
+
+ float position_to_value(int);
+ int value_to_position(float);
+
+ void calculateGroovePixmap();
+ void calculateButtonPixmap();
+ void showFloatText();
+
+ bool m_integral;
+ bool m_vertical;
+
+ int m_sliderMin;
+ int m_sliderMax;
+ float m_value;
+
+ int m_min;
+ int m_max;
+ AudioLevel::FaderType m_type;
+
+ int m_clickMousePos;
+ int m_clickButtonPos;
+
+ TextFloat *m_float;
+ QTimer *m_floatTimer;
+
+ QPixmap *groovePixmap();
+ QPixmap *buttonPixmap();
+
+ QColor m_outlineColour;
+
+ typedef std::pair<int, int> SizeRec;
+ typedef std::map<unsigned int, QPixmap *> ColourPixmapRec; // key is QColor::pixel()
+ typedef std::pair<ColourPixmapRec, QPixmap *> PixmapRec;
+ typedef std::map<SizeRec, PixmapRec> PixmapCache;
+ static PixmapCache m_pixmapCache;
+};
+
+
+// AudioVUMeter - a vertical audio meter. Default is stereo.
+//
+
+}
+
+#endif
diff --git a/src/gui/widgets/HSpinBox.cpp b/src/gui/widgets/HSpinBox.cpp
new file mode 100644
index 0000000..efdb9d1
--- /dev/null
+++ b/src/gui/widgets/HSpinBox.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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "HSpinBox.h"
+
+#include <qstring.h>
+#include <cmath>
+#include <algorithm>
+
+namespace Rosegarden
+{
+
+QString HSpinBox::mapValueToText(int j)
+{
+ QString str;
+ str.sprintf(m_format, float(j) / m_scaleFactor);
+ return str;
+}
+
+int HSpinBox::mapTextToValue( bool* ok )
+{
+ *ok = true;
+ float f = atof(text());
+ return int(f * m_scaleFactor);
+}
+
+HSpinBox::HSpinBox( int minV, int maxV, int step, QWidget* parent,
+ double bottom, double top, int decimals, float initialValue)
+ : QSpinBox(minV,maxV,step,parent)
+{
+ setValidator(new QDoubleValidator(bottom,top,decimals,this));
+ initialize(decimals);
+ setValuef(initialValue);
+}
+
+ //constructor with default settings
+HSpinBox::HSpinBox( QWidget* parent, float initialValue, int step,
+ double bottom, double top, int decimals,
+ const QObject* recv, const char* mem)
+ : QSpinBox((int)(bottom*pow(10.0, decimals)),
+ (int)(top*pow(10.0, decimals)), step, parent)
+{
+ setValidator(new QDoubleValidator(bottom,top,decimals,this));
+ initialize(decimals);
+ setValuef(initialValue);
+ if (recv != NULL && mem != NULL)
+ QObject::connect(this, SIGNAL(valueChanged(int)), recv, mem);
+}
+
+float HSpinBox::valuef() { return float(value()) / m_scaleFactor; }
+void HSpinBox::setValuef(float v) { setValue(static_cast<int>(v * m_scaleFactor)); }
+
+void HSpinBox::initialize(int digits) {
+ m_scaleFactor = pow(10.0, digits);
+ sprintf(m_format, "%c%1i.%1if", '%', digits+3, digits);
+}
+
+
+}
diff --git a/src/gui/widgets/HSpinBox.h b/src/gui/widgets/HSpinBox.h
new file mode 100644
index 0000000..aa60b65
--- /dev/null
+++ b/src/gui/widgets/HSpinBox.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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_HSPINBOX_H_
+#define _RG_HSPINBOX_H_
+
+#include <qobject.h>
+#include <qspinbox.h>
+#include <qstring.h>
+#include <qvalidator.h>
+
+
+namespace Rosegarden
+{
+
+class HSpinBox : public QSpinBox
+{
+ QString mapValueToText(int j);
+
+ int mapTextToValue( bool* ok );
+
+public:
+ HSpinBox( int minV, int maxV, int step, QWidget* parent,
+ double bottom, double top, int decimals, float initialValue);
+
+ //constructor with default settings
+ HSpinBox( QWidget* parent, float initialValue = 0.2, int step=1,
+ double bottom=-25.0, double top=25.0, int decimals=3,
+ const QObject* recv=NULL, const char* mem=NULL);
+
+ float valuef();
+ void setValuef(float v);
+ void initialize(int digits);
+
+private:
+
+ float m_scaleFactor; //scale of the value
+ char m_format[3]; //text format
+
+};
+
+
+}
+
+#endif
diff --git a/src/gui/widgets/Label.cpp b/src/gui/widgets/Label.cpp
new file mode 100644
index 0000000..69f504b
--- /dev/null
+++ b/src/gui/widgets/Label.cpp
@@ -0,0 +1,2 @@
+#include "Label.h"
+#include "Label.moc"
diff --git a/src/gui/widgets/Label.h b/src/gui/widgets/Label.h
new file mode 100644
index 0000000..e704c35
--- /dev/null
+++ b/src/gui/widgets/Label.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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_ROSEGARDENLABEL_H_
+#define _RG_ROSEGARDENLABEL_H_
+
+#include <qlabel.h>
+
+
+class QWidget;
+class QWheelEvent;
+class QMouseEvent;
+
+
+namespace Rosegarden
+{
+
+class Label : public QLabel
+{
+ Q_OBJECT
+public:
+ Label(QWidget *parent = 0, const char *name=0):
+ QLabel(parent, name) {;}
+
+protected:
+ virtual void mouseDoubleClickEvent(QMouseEvent * /*e*/)
+ { emit doubleClicked(); }
+
+ virtual void wheelEvent(QWheelEvent * e)
+ { emit scrollWheel(e->delta()); }
+
+signals:
+ void doubleClicked();
+ void scrollWheel(int);
+
+};
+
+
+}
+
+#endif
diff --git a/src/gui/widgets/MidiFaderWidget.cpp b/src/gui/widgets/MidiFaderWidget.cpp
new file mode 100644
index 0000000..a2ef7fc
--- /dev/null
+++ b/src/gui/widgets/MidiFaderWidget.cpp
@@ -0,0 +1,41 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
+
+/*
+ Rosegarden
+ A MIDI and audio sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "MidiFaderWidget.h"
+
+#include "AudioVUMeter.h"
+#include "Fader.h"
+#include "Rotary.h"
+#include <kcombobox.h>
+#include <qframe.h>
+#include <qpushbutton.h>
+#include <qstring.h>
+#include <qwidget.h>
+
+
+namespace Rosegarden
+{
+}
+#include "MidiFaderWidget.moc"
diff --git a/src/gui/widgets/MidiFaderWidget.h b/src/gui/widgets/MidiFaderWidget.h
new file mode 100644
index 0000000..7bdf520
--- /dev/null
+++ b/src/gui/widgets/MidiFaderWidget.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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_MIDIFADERWIDGET_H_
+#define _RG_MIDIFADERWIDGET_H_
+
+#include <qframe.h>
+#include <qstring.h>
+
+
+class QWidget;
+class QPushButton;
+class KComboBox;
+
+
+namespace Rosegarden
+{
+
+class Rotary;
+class Fader;
+class AudioVUMeter;
+
+
+class MidiFaderWidget : public QFrame
+{
+ Q_OBJECT
+
+public:
+ MidiFaderWidget(QWidget *parent,
+ QString id = "");
+
+ AudioVUMeter *m_vuMeter;
+
+ Fader *m_fader;
+
+ QPushButton *m_muteButton;
+ QPushButton *m_soloButton;
+ QPushButton *m_recordButton;
+ Rotary *m_pan;
+
+ KComboBox *m_output;
+
+ QString m_id;
+};
+
+
+
+}
+
+#endif
diff --git a/src/gui/widgets/PitchChooser.cpp b/src/gui/widgets/PitchChooser.cpp
new file mode 100644
index 0000000..e20647d
--- /dev/null
+++ b/src/gui/widgets/PitchChooser.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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "PitchChooser.h"
+
+#include <klocale.h>
+#include "gui/general/MidiPitchLabel.h"
+#include "PitchDragLabel.h"
+#include <qgroupbox.h>
+#include <qhbox.h>
+#include <qlabel.h>
+#include <qspinbox.h>
+#include <qstring.h>
+#include <qwidget.h>
+
+
+namespace Rosegarden
+{
+
+PitchChooser::PitchChooser(QString title,
+ QWidget *parent,
+ int defaultPitch) :
+ QGroupBox(1, Horizontal, title, parent),
+ m_defaultPitch(defaultPitch)
+{
+ m_pitchDragLabel = new PitchDragLabel(this, defaultPitch);
+
+ QHBox *hbox = new QHBox(this);
+ hbox->setSpacing(6);
+
+ new QLabel(i18n("Pitch:"), hbox);
+
+ m_pitch = new QSpinBox(hbox);
+ m_pitch->setMinValue(0);
+ m_pitch->setMaxValue(127);
+ m_pitch->setValue(defaultPitch);
+
+ MidiPitchLabel pl(defaultPitch);
+ m_pitchLabel = new QLabel(pl.getQString(), hbox);
+ m_pitchLabel->setMinimumWidth(40);
+
+ connect(m_pitch, SIGNAL(valueChanged(int)),
+ this, SLOT(slotSetPitch(int)));
+
+ connect(m_pitch, SIGNAL(valueChanged(int)),
+ this, SIGNAL(pitchChanged(int)));
+
+ connect(m_pitch, SIGNAL(valueChanged(int)),
+ this, SIGNAL(preview(int)));
+
+ connect(m_pitchDragLabel, SIGNAL(pitchDragged(int)),
+ this, SLOT(slotSetPitch(int)));
+
+ connect(m_pitchDragLabel, SIGNAL(pitchChanged(int)),
+ this, SLOT(slotSetPitch(int)));
+
+ connect(m_pitchDragLabel, SIGNAL(pitchChanged(int)),
+ this, SIGNAL(pitchChanged(int)));
+
+ connect(m_pitchDragLabel, SIGNAL(preview(int)),
+ this, SIGNAL(preview(int)));
+
+}
+
+int
+PitchChooser::getPitch() const
+{
+ return m_pitch->value();
+}
+
+void
+PitchChooser::slotSetPitch(int p)
+{
+ if (m_pitch->value() != p)
+ m_pitch->setValue(p);
+ if (m_pitchDragLabel->getPitch() != p)
+ m_pitchDragLabel->slotSetPitch(p);
+
+ MidiPitchLabel pl(p);
+ m_pitchLabel->setText(pl.getQString());
+ update();
+}
+
+void
+PitchChooser::slotResetToDefault()
+{
+ slotSetPitch(m_defaultPitch);
+}
+
+}
+#include "PitchChooser.moc"
diff --git a/src/gui/widgets/PitchChooser.h b/src/gui/widgets/PitchChooser.h
new file mode 100644
index 0000000..df3b8ef
--- /dev/null
+++ b/src/gui/widgets/PitchChooser.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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_ROSEGARDENPITCHCHOOSER_H_
+#define _RG_ROSEGARDENPITCHCHOOSER_H_
+
+#include <qgroupbox.h>
+#include <qstring.h>
+
+
+class QWidget;
+class QSpinBox;
+class QLabel;
+
+
+namespace Rosegarden
+{
+
+class PitchDragLabel;
+
+
+class PitchChooser : public QGroupBox
+{
+ Q_OBJECT
+public:
+ PitchChooser(QString title,
+ QWidget *parent,
+ int defaultPitch = 60);
+
+ int getPitch() const;
+
+signals:
+ void pitchChanged(int);
+ void preview(int);
+
+public slots:
+ void slotSetPitch(int);
+ void slotResetToDefault();
+
+protected:
+ int m_defaultPitch;
+ PitchDragLabel *m_pitchDragLabel;
+ QSpinBox *m_pitch;
+ QLabel *m_pitchLabel;
+};
+
+
+
+}
+
+#endif
diff --git a/src/gui/widgets/PitchDragLabel.cpp b/src/gui/widgets/PitchDragLabel.cpp
new file mode 100644
index 0000000..1d22a77
--- /dev/null
+++ b/src/gui/widgets/PitchDragLabel.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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "PitchDragLabel.h"
+
+#include "base/NotationRules.h"
+#include "base/NotationTypes.h"
+#include "gui/editors/notation/NotePixmapFactory.h"
+#include <qcanvas.h>
+#include <qpainter.h>
+#include <qpixmap.h>
+#include <qsize.h>
+#include <qwidget.h>
+
+
+namespace Rosegarden
+{
+
+PitchDragLabel::PitchDragLabel(QWidget *parent,
+ int defaultPitch,
+ bool defaultSharps) :
+ QWidget(parent),
+ m_pitch(defaultPitch),
+ m_usingSharps(defaultSharps),
+ m_clickedY(0),
+ m_clicked(false),
+ m_npf(new NotePixmapFactory())
+{
+ calculatePixmap();
+}
+
+PitchDragLabel::~PitchDragLabel()
+{
+ delete m_npf;
+}
+
+void
+PitchDragLabel::slotSetPitch(int p)
+{
+ bool up = (p > m_pitch);
+ m_usingSharps = up;
+ if (m_pitch == p)
+ return ;
+ m_pitch = p;
+ calculatePixmap();
+ emitPitchChange();
+ paintEvent(0);
+}
+
+void
+PitchDragLabel::slotSetPitch(int pitch, int octave, int step)
+{
+ if (m_pitch == pitch)
+ return ;
+ m_pitch = pitch;
+ calculatePixmap(pitch, octave, step);
+ emit pitchChanged(pitch);
+ emit pitchChanged(pitch, octave, step);
+ paintEvent(0);
+}
+
+void
+PitchDragLabel::mousePressEvent(QMouseEvent *e)
+{
+ if (e->button() == LeftButton) {
+ m_clickedY = e->y();
+ m_clickedPitch = m_pitch;
+ m_clicked = true;
+ emit preview(m_pitch);
+ }
+}
+
+void
+PitchDragLabel::mouseMoveEvent(QMouseEvent *e)
+{
+ if (m_clicked) {
+
+ int y = e->y();
+ int diff = y - m_clickedY;
+ int pitchDiff = diff * 4 / m_npf->getLineSpacing();
+
+ int newPitch = m_clickedPitch - pitchDiff;
+ if (newPitch < 0)
+ newPitch = 0;
+ if (newPitch > 127)
+ newPitch = 127;
+
+ if (m_pitch != newPitch) {
+ bool up = (newPitch > m_pitch);
+ m_pitch = newPitch;
+ m_usingSharps = up;
+ calculatePixmap();
+ emit pitchDragged(m_pitch);
+ if (up)
+ {
+ // use sharps
+ emit pitchDragged(m_pitch, (int)(((long)m_pitch) / 12),
+ steps_Cmajor_with_sharps[m_pitch % 12]);
+ }
+ else
+ {
+ // use flats
+ emit pitchDragged(m_pitch, (int)(((long)m_pitch) / 12),
+ steps_Cmajor_with_flats[m_pitch % 12]);
+ }
+ emit preview(m_pitch);
+ paintEvent(0);
+ }
+ }
+}
+
+void
+PitchDragLabel::mouseReleaseEvent(QMouseEvent *e)
+{
+ mouseMoveEvent(e);
+ emitPitchChange();
+ m_clicked = false;
+}
+
+void
+PitchDragLabel::emitPitchChange()
+{
+ emit pitchChanged(m_pitch);
+
+ Pitch newPitch(m_pitch);
+
+ if (m_usingSharps)
+ {
+ Rosegarden::Key key = Rosegarden::Key("C major");
+ emit pitchDragged(m_pitch, newPitch.getOctave(0), newPitch.getNoteInScale(key));
+ }
+ else
+ {
+ Rosegarden::Key key = Rosegarden::Key("A minor");
+ emit pitchDragged(m_pitch, newPitch.getOctave(0), (newPitch.getNoteInScale(key) + 5) % 7);
+ }
+}
+
+void
+PitchDragLabel::wheelEvent(QWheelEvent *e)
+{
+ if (e->delta() > 0) {
+ if (m_pitch < 127) {
+ ++m_pitch;
+ m_usingSharps = true;
+ calculatePixmap();
+ emitPitchChange();
+ emit preview(m_pitch);
+ paintEvent(0);
+ }
+ } else {
+ if (m_pitch > 0) {
+ --m_pitch;
+ m_usingSharps = false;
+ calculatePixmap();
+ emitPitchChange();
+ emit preview(m_pitch);
+ paintEvent(0);
+ }
+ }
+}
+
+void
+PitchDragLabel::paintEvent(QPaintEvent *)
+{
+ QPainter paint(this);
+ paint.fillRect(0, 0, width(), height(), paint.backgroundColor());
+
+ int x = width() / 2 - m_pixmap.width() / 2;
+ if (x < 0)
+ x = 0;
+
+ int y = height() / 2 - m_pixmap.height() / 2;
+ if (y < 0)
+ y = 0;
+
+ paint.drawPixmap(x, y, m_pixmap);
+
+
+}
+
+QSize
+PitchDragLabel::sizeHint() const
+{
+ return QSize(150, 135);
+}
+
+void
+PitchDragLabel::calculatePixmap(int pitch, int octave, int step) const
+{
+ std::string clefType = Clef::Treble;
+ int octaveOffset = 0;
+
+ if (m_pitch > 94) {
+ octaveOffset = 2;
+ } else if (m_pitch > 82) {
+ octaveOffset = 1;
+ } else if (m_pitch < 60) {
+ clefType = Clef::Bass;
+ if (m_pitch < 24) {
+ octaveOffset = -2;
+ } else if (m_pitch < 36) {
+ octaveOffset = -1;
+ }
+ }
+
+ QCanvasPixmap *pmap = m_npf->makePitchDisplayPixmap
+ (m_pitch,
+ Clef(clefType, octaveOffset),
+ octave, step);
+
+ m_pixmap = *pmap;
+
+ delete pmap;
+}
+
+void
+PitchDragLabel::calculatePixmap() const
+{
+ std::string clefType = Clef::Treble;
+ int octaveOffset = 0;
+
+ if (m_pitch > 94) {
+ octaveOffset = 2;
+ } else if (m_pitch > 82) {
+ octaveOffset = 1;
+ } else if (m_pitch < 60) {
+ clefType = Clef::Bass;
+ if (m_pitch < 24) {
+ octaveOffset = -2;
+ } else if (m_pitch < 36) {
+ octaveOffset = -1;
+ }
+ }
+
+ QCanvasPixmap *pmap = m_npf->makePitchDisplayPixmap
+ (m_pitch,
+ Clef(clefType, octaveOffset),
+ m_usingSharps);
+
+ m_pixmap = *pmap;
+
+ delete pmap;
+}
+
+}
+#include "PitchDragLabel.moc"
diff --git a/src/gui/widgets/PitchDragLabel.h b/src/gui/widgets/PitchDragLabel.h
new file mode 100644
index 0000000..7114611
--- /dev/null
+++ b/src/gui/widgets/PitchDragLabel.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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_ROSEGARDENPITCHDRAGLABEL_H_
+#define _RG_ROSEGARDENPITCHDRAGLABEL_H_
+
+#include <qpixmap.h>
+#include <qsize.h>
+#include <qwidget.h>
+
+
+class QWheelEvent;
+class QPaintEvent;
+class QMouseEvent;
+
+
+namespace Rosegarden
+{
+
+class NotePixmapFactory;
+
+
+class PitchDragLabel : public QWidget
+{
+ Q_OBJECT
+public:
+ PitchDragLabel(QWidget *parent,
+ int defaultPitch = 60, bool defaultSharps = true);
+ ~PitchDragLabel();
+
+ int getPitch() const { return m_pitch; }
+
+ virtual QSize sizeHint() const;
+
+signals:
+ void pitchDragged(int);
+ // pitch, octave, step
+ void pitchDragged(int,int,int);
+ void pitchChanged(int); // mouse release
+ // pitch, octave, step
+ void pitchChanged(int,int,int); // mouse release
+ void preview(int);
+
+public slots:
+ void slotSetPitch(int);
+ void slotSetPitch(int,int,int);
+
+protected:
+ virtual void paintEvent(QPaintEvent *);
+ virtual void mousePressEvent(QMouseEvent *e);
+ virtual void mouseReleaseEvent(QMouseEvent *e);
+ virtual void mouseMoveEvent(QMouseEvent *e);
+ virtual void wheelEvent(QWheelEvent *e);
+
+ void calculatePixmap() const;
+ void calculatePixmap(int pitch, int octave, int step) const;
+
+ /** emits 'pitchChanged' events, both diatonic and chromatic */
+ void emitPitchChange();
+
+ mutable QPixmap m_pixmap;
+
+ int m_pitch;
+ int m_clickedY;
+ int m_clickedPitch;
+ bool m_clicked;
+
+ bool m_usingSharps;
+
+ NotePixmapFactory *m_npf;
+};
+
+
+
+}
+
+#endif
diff --git a/src/gui/widgets/PluginControl.cpp b/src/gui/widgets/PluginControl.cpp
new file mode 100644
index 0000000..acf33ea
--- /dev/null
+++ b/src/gui/widgets/PluginControl.cpp
@@ -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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "PluginControl.h"
+#include "Rotary.h"
+#include "misc/Strings.h"
+#include "base/AudioPluginInstance.h"
+#include "gui/general/GUIPalette.h"
+#include "gui/studio/AudioPluginManager.h"
+#include "gui/widgets/Rotary.h"
+#include <qfont.h>
+#include <qhbox.h>
+#include <qlabel.h>
+#include <qlayout.h>
+#include <qobject.h>
+#include <qstring.h>
+#include <qwidget.h>
+#include <cmath>
+
+
+namespace Rosegarden
+{
+
+PluginControl::PluginControl(QWidget *parent,
+ QGridLayout *layout,
+ ControlType type,
+ PluginPort *port,
+ AudioPluginManager *aPM,
+ int index,
+ float initialValue,
+ bool showBounds,
+ bool hidden):
+ QObject(parent),
+ m_layout(layout),
+ m_type(type),
+ m_port(port),
+ m_pluginManager(aPM),
+ m_index(index)
+{
+ QFont plainFont;
+ plainFont.setPointSize((plainFont.pointSize() * 9 ) / 10);
+
+ QLabel *controlTitle =
+ new QLabel(QString("%1 ").arg(strtoqstr(port->getName())), parent);
+ controlTitle->setFont(plainFont);
+
+ if (type == Rotary) {
+ float lowerBound = port->getLowerBound();
+ float upperBound = port->getUpperBound();
+ // Default value was already handled when calling this constructor
+
+ if (lowerBound > upperBound) {
+ float swap = upperBound;
+ upperBound = lowerBound;
+ lowerBound = swap;
+ }
+
+ float step = (upperBound - lowerBound) / 100.0;
+ float pageStep = step * 10.f;
+ Rotary::TickMode ticks = Rotary::PageStepTicks;
+ bool snapToTicks = false;
+
+ if (port->getDisplayHint() & PluginPort::Integer) {
+ step = 1.0;
+ ticks = Rotary::StepTicks;
+ if (upperBound - lowerBound > 30.0)
+ pageStep = 10.0;
+ snapToTicks = true;
+ }
+ if (port->getDisplayHint() & PluginPort::Toggled) {
+ lowerBound = -0.0001;
+ upperBound = 1.0001;
+ step = 1.0;
+ pageStep = 1.0;
+ ticks = Rotary::StepTicks;
+ snapToTicks = true;
+ }
+
+ float displayLower = lowerBound, displayUpper = upperBound;
+
+ bool logarithmic = (port->getDisplayHint() & PluginPort::Logarithmic);
+
+ if (logarithmic) {
+ float logthresh = -10;
+ float thresh = powf(10, logthresh);
+ if (lowerBound > thresh) lowerBound = log10f(lowerBound);
+ else {
+ if (upperBound > 1) lowerBound = 0;
+ else lowerBound = logthresh;
+ }
+ if (upperBound > thresh) upperBound = log10f(upperBound);
+ else upperBound = logthresh;
+
+ step = (upperBound - lowerBound) / 100.0;
+ pageStep = step * 10.f;
+ initialValue = log10f(initialValue);
+ }
+
+ QLabel *low;
+ if (port->getDisplayHint() &
+ (PluginPort::Integer | PluginPort::Toggled)) {
+ low = new QLabel(QString("%1").arg(int(displayLower)), parent);
+ } else {
+ low = new QLabel(QString("%1").arg(displayLower), parent);
+ }
+ low->setFont(plainFont);
+
+// std::cerr << "port " << port->getName() << ": lower bound "
+// << displayLower << ", upper bound " << displayUpper
+// << ", logarithmic " << logarithmic << ", default "
+// << initialValue << ", actual lower " << lowerBound
+// << ", actual upper " << upperBound << ", step "
+// << step << std::endl;
+
+ m_dial = new ::Rosegarden::Rotary(parent,
+ lowerBound, // min
+ upperBound, // max
+ step, // step
+ pageStep, // page step
+ initialValue, // initial
+ 30, // size
+ ticks,
+ snapToTicks,
+ false, // centred
+ logarithmic);
+
+ m_dial->setKnobColour(GUIPalette::getColour(GUIPalette::RotaryPlugin));
+
+ connect(m_dial, SIGNAL(valueChanged(float)),
+ this, SLOT(slotValueChanged(float)));
+
+ QLabel *upp;
+ if (port->getDisplayHint() &
+ (PluginPort::Integer | PluginPort::Toggled)) {
+ upp = new QLabel(QString("%1").arg(int(displayUpper)), parent);
+ } else {
+ upp = new QLabel(QString("%1").arg(displayUpper), parent);
+ }
+ upp->setFont(plainFont);
+
+ QWidgetItem *item;
+
+ if (!hidden) {
+ controlTitle->show();
+ item = new QWidgetItem(controlTitle);
+ item->setAlignment(Qt::AlignRight | Qt::AlignBottom);
+ m_layout->addItem(item);
+ } else {
+ controlTitle->hide();
+ }
+
+ if (showBounds && !hidden) {
+ low->show();
+ item = new QWidgetItem(low);
+ item->setAlignment(Qt::AlignRight | Qt::AlignBottom);
+ m_layout->addItem(item);
+ } else {
+ low->hide();
+ }
+
+ if (!hidden) {
+ m_dial->show();
+ item = new QWidgetItem(m_dial);
+ item->setAlignment(Qt::AlignCenter);
+ m_layout->addItem(item);
+ } else {
+ m_dial->hide();
+ }
+
+ if (showBounds && !hidden) {
+ upp->show();
+ item = new QWidgetItem(upp);
+ item->setAlignment(Qt::AlignLeft | Qt::AlignBottom);
+ m_layout->addItem(item);
+ } else {
+ upp->hide();
+ }
+ }
+}
+
+void
+PluginControl::setValue(float value, bool emitSignals)
+{
+ if (!emitSignals)
+ m_dial->blockSignals(true);
+ m_dial->setPosition(value);
+ if (!emitSignals)
+ m_dial->blockSignals(false);
+ else
+ emit valueChanged(value);
+}
+
+float
+PluginControl::getValue() const
+{
+ return m_dial == 0 ? 0 : m_dial->getPosition();
+}
+
+void
+PluginControl::slotValueChanged(float value)
+{
+ emit valueChanged(value);
+}
+
+}
+#include "PluginControl.moc"
diff --git a/src/gui/widgets/PluginControl.h b/src/gui/widgets/PluginControl.h
new file mode 100644
index 0000000..83d2d33
--- /dev/null
+++ b/src/gui/widgets/PluginControl.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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_PLUGINCONTROL_H_
+#define _RG_PLUGINCONTROL_H_
+
+#include <qobject.h>
+#include <vector>
+
+
+class QWidget;
+class QHBox;
+class QGridLayout;
+
+
+namespace Rosegarden
+{
+
+class Rotary;
+class PluginPort;
+class AudioPluginManager;
+class Studio;
+
+class PluginControl : public QObject
+{
+ Q_OBJECT
+public:
+
+ typedef enum
+ {
+ Rotary,
+ Slider,
+ NumericSlider
+ } ControlType;
+
+ PluginControl(QWidget *parent,
+ QGridLayout *layout,
+ ControlType type,
+ PluginPort *port,
+ AudioPluginManager *pluginManager,
+ int index,
+ float initialValue,
+ bool showBounds,
+ bool hidden);
+
+ void setValue(float value, bool emitSignals = true);
+ float getValue() const;
+
+ int getIndex() const { return m_index; }
+
+ void show();
+ void hide();
+
+public slots:
+ void slotValueChanged(float value);
+
+signals:
+ void valueChanged(float value);
+
+protected:
+
+ //--------------- Data members ---------------------------------
+
+ QGridLayout *m_layout;
+
+ ControlType m_type;
+ PluginPort *m_port;
+
+ ::Rosegarden::Rotary *m_dial; // we have to specify the namespace here otherwise gcc 4.1 thinks it's the enum value above
+ AudioPluginManager *m_pluginManager;
+
+ int m_index;
+
+};
+
+typedef std::vector<PluginControl*>::iterator ControlIterator;
+typedef std::vector<QHBox*>::iterator ControlLineIterator;
+
+
+}
+
+#endif
diff --git a/src/gui/widgets/ProgressBar.cpp b/src/gui/widgets/ProgressBar.cpp
new file mode 100644
index 0000000..c4cb88e
--- /dev/null
+++ b/src/gui/widgets/ProgressBar.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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "ProgressBar.h"
+
+#include <kprogress.h>
+#include <qwidget.h>
+
+
+namespace Rosegarden
+{
+
+ProgressBar::ProgressBar(int totalSteps,
+ bool /*useDelay*/,
+ QWidget *creator,
+ const char *name,
+ WFlags f) :
+ KProgress(totalSteps, creator, name, f)
+{}
+
+}
+#include "ProgressBar.moc"
diff --git a/src/gui/widgets/ProgressBar.h b/src/gui/widgets/ProgressBar.h
new file mode 100644
index 0000000..3ce93e1
--- /dev/null
+++ b/src/gui/widgets/ProgressBar.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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_ROSEGARDENPROGRESSBAR_H_
+#define _RG_ROSEGARDENPROGRESSBAR_H_
+
+#include <kprogress.h>
+
+
+class QWidget;
+
+
+namespace Rosegarden
+{
+
+
+
+class ProgressBar : public KProgress
+{
+ Q_OBJECT
+
+public:
+ ProgressBar(int totalSteps,
+ bool useDelay,
+ QWidget *creator = 0,
+ const char *name = 0,
+ WFlags f = 0);
+
+};
+
+
+}
+
+#endif
diff --git a/src/gui/widgets/ProgressDialog.cpp b/src/gui/widgets/ProgressDialog.cpp
new file mode 100644
index 0000000..1f6811f
--- /dev/null
+++ b/src/gui/widgets/ProgressDialog.cpp
@@ -0,0 +1,209 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
+
+/*
+ Rosegarden
+ A MIDI and audio sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "ProgressDialog.h"
+#include "CurrentProgressDialog.h"
+#include "misc/Debug.h"
+#include "gui/application/RosegardenApplication.h"
+#include <klocale.h>
+#include <qcursor.h>
+#include <qprogressdialog.h>
+#include <qstring.h>
+#include <qtimer.h>
+#include <qwidget.h>
+
+
+namespace Rosegarden
+{
+
+bool ProgressDialog::m_modalVisible = false;
+
+
+ProgressDialog::ProgressDialog(QWidget *creator,
+ const char *name,
+ bool modal):
+ KProgressDialog(creator, name,
+ i18n("Processing..."), QString::null, modal),
+ m_wasVisible(false),
+ m_frozen(false),
+ m_modal(modal)
+{
+ setCaption(i18n("Processing..."));
+ RG_DEBUG << "ProgressDialog::ProgressDialog type 1 - "
+ << labelText() << " - modal : " << modal << endl;
+
+ connect(progressBar(), SIGNAL(percentageChanged (int)),
+ this, SLOT(slotCheckShow(int)));
+
+ m_chrono.start();
+
+ CurrentProgressDialog::set
+ (this);
+
+ setMinimumDuration(500); // set a default value for this
+}
+
+ProgressDialog::ProgressDialog(
+ const QString &labelText,
+ int totalSteps,
+ QWidget *creator,
+ const char *name,
+ bool modal) :
+ KProgressDialog(creator,
+ name,
+ i18n("Processing..."),
+ labelText,
+ modal),
+ m_wasVisible(false),
+ m_frozen(false),
+ m_modal(modal)
+{
+ progressBar()->setTotalSteps(totalSteps);
+
+ RG_DEBUG << "ProgressDialog::ProgressDialog type 2 - "
+ << labelText << " - modal : " << modal << endl;
+
+ connect(progressBar(), SIGNAL(percentageChanged (int)),
+ this, SLOT(slotCheckShow(int)));
+
+ m_chrono.start();
+
+ CurrentProgressDialog::set
+ (this);
+
+ setMinimumDuration(500); // set a default value for this
+}
+
+ProgressDialog::~ProgressDialog()
+{
+ m_modalVisible = false;
+}
+
+void
+ProgressDialog::polish()
+{
+ KProgressDialog::polish();
+
+ if (allowCancel())
+ setCursor(Qt::ArrowCursor);
+ else
+ QApplication::setOverrideCursor(QCursor(Qt::waitCursor));
+}
+
+void ProgressDialog::hideEvent(QHideEvent* e)
+{
+ if (!allowCancel())
+ QApplication::restoreOverrideCursor();
+
+ KProgressDialog::hideEvent(e);
+ m_modalVisible = false;
+}
+
+void
+ProgressDialog::slotSetOperationName(QString name)
+{
+ // RG_DEBUG << "ProgressDialog::slotSetOperationName("
+ // << name << ") visible : " << isVisible() << endl;
+
+ setLabel(name);
+ // Little trick stolen from QProgressDialog
+ // increase resize only, never shrink
+ int w = QMAX( isVisible() ? width() : 0, sizeHint().width() );
+ int h = QMAX( isVisible() ? height() : 0, sizeHint().height() );
+ resize( w, h );
+}
+
+void ProgressDialog::slotCancel()
+{
+ RG_DEBUG << "ProgressDialog::slotCancel()\n";
+ KProgressDialog::slotCancel();
+ slotFreeze();
+}
+
+void ProgressDialog::slotCheckShow(int)
+{
+ // RG_DEBUG << "ProgressDialog::slotCheckShow() : "
+ // << m_chrono.elapsed() << " - " << minimumDuration()
+ // << endl;
+
+ if (!isVisible() &&
+ !m_frozen &&
+ m_chrono.elapsed() > minimumDuration()) {
+ RG_DEBUG << "ProgressDialog::slotCheckShow() : showing dialog\n";
+ show();
+ if (m_modal)
+ m_modalVisible = true;
+ processEvents();
+ }
+}
+
+void ProgressDialog::slotFreeze()
+{
+ RG_DEBUG << "ProgressDialog::slotFreeze()\n";
+
+ m_wasVisible = isVisible();
+ if (isVisible()) {
+ m_modalVisible = false;
+ hide();
+ }
+
+ // This is also a convenient place to ensure the wait cursor (if
+ // currently shown) returns to the original cursor to ensure that
+ // the user can respond to whatever's freezing the progress dialog
+ QApplication::restoreOverrideCursor();
+
+ mShowTimer->stop();
+ m_frozen = true;
+}
+
+void ProgressDialog::slotThaw()
+{
+ RG_DEBUG << "ProgressDialog::slotThaw()\n";
+
+ if (m_wasVisible) {
+ if (m_modal)
+ m_modalVisible = true;
+ show();
+ }
+
+ // Restart timer
+ mShowTimer->start(minimumDuration());
+ m_frozen = false;
+ m_chrono.restart();
+}
+
+void ProgressDialog::processEvents()
+{
+ // RG_DEBUG << "ProgressDialog::processEvents: modalVisible is "
+ // << m_modalVisible << endl;
+ if (m_modalVisible) {
+ kapp->processEvents(50);
+ } else {
+ rgapp->refreshGUI(50);
+ }
+}
+
+}
+#include "ProgressDialog.moc"
diff --git a/src/gui/widgets/ProgressDialog.h b/src/gui/widgets/ProgressDialog.h
new file mode 100644
index 0000000..b753493
--- /dev/null
+++ b/src/gui/widgets/ProgressDialog.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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_ROSEGARDENPROGRESSDIALOG_H_
+#define _RG_ROSEGARDENPROGRESSDIALOG_H_
+
+#define private protected // fugly
+#include <kprogress.h>
+#undef private
+#include <qdatetime.h>
+
+class QWidget;
+class QString;
+class QHideEvent;
+
+
+namespace Rosegarden
+{
+
+
+
+class ProgressDialog : public KProgressDialog
+{
+ Q_OBJECT
+public:
+ ProgressDialog(QWidget * creator = 0,
+ const char * name = 0,
+ bool modal = true);
+
+ ProgressDialog(const QString &labelText,
+ int totalSteps,
+ QWidget *creator = 0,
+ const char *name = 0,
+ bool modal = true);
+
+ ~ProgressDialog();
+
+ /**
+ * A "safe" way to process events without worrying about user
+ * input during the process. If there is a modal progress dialog
+ * visible, then this will permit user input so as to allow the
+ * user to hit Cancel; otherwise it will prevent all user input
+ */
+ static void processEvents();
+
+ virtual void polish();
+
+public slots:
+ void slotSetOperationName(QString);
+ void slotCancel();
+
+ /// Stop and hide (if it's shown) the progress dialog
+ void slotFreeze();
+
+ /// Restore the dialog to its normal state
+ void slotThaw();
+
+protected slots:
+ void slotCheckShow(int);
+
+protected:
+ virtual void hideEvent(QHideEvent*);
+
+ //--------------- Data members ---------------------------------
+
+ QTime m_chrono;
+ bool m_wasVisible;
+ bool m_frozen;
+ bool m_modal;
+ static bool m_modalVisible;
+};
+
+
+}
+
+#endif
diff --git a/src/gui/widgets/QDeferScrollView.cpp b/src/gui/widgets/QDeferScrollView.cpp
new file mode 100644
index 0000000..01864a3
--- /dev/null
+++ b/src/gui/widgets/QDeferScrollView.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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "QDeferScrollView.h"
+
+#include <qscrollview.h>
+#include <qwidget.h>
+
+
+namespace Rosegarden
+{
+
+QDeferScrollView::QDeferScrollView(QWidget* parent, const char *name, WFlags f)
+ : QScrollView(parent, name, f)
+{
+ setFocusPolicy(QWidget::WheelFocus);
+}
+
+void QDeferScrollView::setBottomMargin(int m)
+{
+ setMargins(leftMargin(), topMargin(), rightMargin(), m);
+}
+
+void QDeferScrollView::contentsWheelEvent(QWheelEvent* e)
+{
+ emit gotWheelEvent(e);
+}
+
+}
+#include "QDeferScrollView.moc"
diff --git a/src/gui/widgets/QDeferScrollView.h b/src/gui/widgets/QDeferScrollView.h
new file mode 100644
index 0000000..e4b2e3d
--- /dev/null
+++ b/src/gui/widgets/QDeferScrollView.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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_QDEFERSCROLLVIEW_H_
+#define _RG_QDEFERSCROLLVIEW_H_
+
+#include <qscrollview.h>
+
+
+class QWidget;
+class QWheelEvent;
+
+
+namespace Rosegarden
+{
+
+
+
+/**
+ * A QScrollView which defers vertical scrolling (through mouse wheel)
+ * elsewhere, typically another QScrollView, so that both can be kept
+ * in sync. The master scrollview will connect its vertical scrollbar
+ * to the slave view so the scrollbar will act on both views.
+ *
+ * The slave scrollview will defer its scrolling to the master by
+ * having the gotWheelEvent() signal connected to a slot in the master
+ * scrollview, which will simply process the wheel event as if it had
+ * received it itself.
+ *
+ * @see TrackEditor
+ * @see SegmentCanvas
+ * @see TrackEditor::m_trackButtonScroll
+ */
+class QDeferScrollView : public QScrollView
+{
+ Q_OBJECT
+public:
+ QDeferScrollView(QWidget* parent=0, const char *name=0, WFlags f=0);
+
+ void setBottomMargin(int);
+
+signals:
+ void gotWheelEvent(QWheelEvent*);
+
+protected:
+ virtual void contentsWheelEvent(QWheelEvent*);
+
+};
+
+
+}
+
+#endif
diff --git a/src/gui/widgets/QuantizeParameters.cpp b/src/gui/widgets/QuantizeParameters.cpp
new file mode 100644
index 0000000..19ba96a
--- /dev/null
+++ b/src/gui/widgets/QuantizeParameters.cpp
@@ -0,0 +1,497 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
+
+/*
+ Rosegarden
+ A MIDI and audio sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "QuantizeParameters.h"
+#include <qlayout.h>
+#include <kapplication.h>
+
+#include <klocale.h>
+#include "base/NotationTypes.h"
+#include "base/Quantizer.h"
+#include "base/BasicQuantizer.h"
+#include "base/LegatoQuantizer.h"
+#include "base/NotationQuantizer.h"
+#include "gui/editors/notation/NotationStrings.h"
+#include "gui/editors/notation/NotePixmapFactory.h"
+#include <kcombobox.h>
+#include <kconfig.h>
+#include <qcheckbox.h>
+#include <qframe.h>
+#include <qgroupbox.h>
+#include <qhbox.h>
+#include <qlabel.h>
+#include <qobject.h>
+#include <qpixmap.h>
+#include <qpushbutton.h>
+#include <qstring.h>
+#include <qwidget.h>
+
+
+namespace Rosegarden
+{
+
+QuantizeParameters::QuantizeParameters(QWidget *parent,
+ QuantizerType defaultQuantizer,
+ bool showNotationOption,
+ bool showAdvancedButton,
+ QString configCategory,
+ QString preamble) :
+ QFrame(parent),
+ m_configCategory(configCategory),
+ m_standardQuantizations
+ (BasicQuantizer::getStandardQuantizations())
+{
+ m_mainLayout = new QGridLayout(this,
+ preamble ? 3 : 4, 2,
+ preamble ? 10 : 0,
+ preamble ? 5 : 4);
+
+ int zero = 0;
+ if (preamble) {
+ QLabel *label = new QLabel(preamble, this);
+ label->setAlignment(Qt::WordBreak);
+ m_mainLayout->addMultiCellWidget(label, 0, 0, 0, 1);
+ zero = 1;
+ }
+
+ QGroupBox *quantizerBox = new QGroupBox
+ (1, Horizontal, i18n("Quantizer"), this);
+
+ m_mainLayout->addWidget(quantizerBox, zero, 0);
+ QFrame *typeFrame = new QFrame(quantizerBox);
+
+ QGridLayout *layout = new QGridLayout(typeFrame, 2, 2, 5, 3);
+ layout->addWidget(new QLabel(i18n("Quantizer type:"), typeFrame), 0, 0);
+ m_typeCombo = new KComboBox(typeFrame);
+ m_typeCombo->insertItem(i18n("Grid quantizer"));
+ m_typeCombo->insertItem(i18n("Legato quantizer"));
+ m_typeCombo->insertItem(i18n("Heuristic notation quantizer"));
+ layout->addWidget(m_typeCombo, 0, 1);
+
+ m_notationTarget = new QCheckBox
+ (i18n("Quantize for notation only (leave performance unchanged)"),
+ typeFrame);
+ layout->addMultiCellWidget(m_notationTarget, 1, 1, 0, 1);
+ if (!showNotationOption)
+ m_notationTarget->hide();
+
+ QHBox *parameterBox = new QHBox(this);
+ m_mainLayout->addWidget(parameterBox, zero + 1, 0);
+
+ m_notationBox = new QGroupBox
+ (1, Horizontal, i18n("Notation parameters"), parameterBox);
+ QFrame *notationFrame = new QFrame(m_notationBox);
+
+ layout = new QGridLayout(notationFrame, 4, 2, 5, 3);
+
+ layout->addWidget(new QLabel(i18n("Base grid unit:"), notationFrame),
+ 1, 0);
+ m_notationUnitCombo = new KComboBox(notationFrame);
+ layout->addWidget(m_notationUnitCombo, 1, 1);
+
+ layout->addWidget(new QLabel(i18n("Complexity:"),
+ notationFrame), 0, 0);
+
+ m_simplicityCombo = new KComboBox(notationFrame);
+ m_simplicityCombo->insertItem(i18n("Very high"));
+ m_simplicityCombo->insertItem(i18n("High"));
+ m_simplicityCombo->insertItem(i18n("Normal"));
+ m_simplicityCombo->insertItem(i18n("Low"));
+ m_simplicityCombo->insertItem(i18n("Very low"));
+ layout->addWidget(m_simplicityCombo, 0, 1);
+
+ layout->addWidget(new QLabel(i18n("Tuplet level:"),
+ notationFrame), 2, 0);
+ m_maxTuplet = new KComboBox(notationFrame);
+ m_maxTuplet->insertItem(i18n("None"));
+ m_maxTuplet->insertItem(i18n("2-in-the-time-of-3"));
+ m_maxTuplet->insertItem(i18n("Triplet"));
+ /*
+ m_maxTuplet->insertItem(i18n("4-Tuplet"));
+ m_maxTuplet->insertItem(i18n("5-Tuplet"));
+ m_maxTuplet->insertItem(i18n("6-Tuplet"));
+ m_maxTuplet->insertItem(i18n("7-Tuplet"));
+ m_maxTuplet->insertItem(i18n("8-Tuplet"));
+ */
+ m_maxTuplet->insertItem(i18n("Any"));
+ layout->addWidget(m_maxTuplet, 2, 1);
+
+ m_counterpoint = new QCheckBox(i18n("Permit counterpoint"), notationFrame);
+ layout->addMultiCellWidget(m_counterpoint, 3, 3, 0, 1);
+
+ m_gridBox = new QGroupBox
+ (1, Horizontal, i18n("Grid parameters"), parameterBox);
+ QFrame *gridFrame = new QFrame(m_gridBox);
+
+ layout = new QGridLayout(gridFrame, 4, 2, 5, 3);
+
+ layout->addWidget(new QLabel(i18n("Base grid unit:"), gridFrame), 0, 0);
+ m_gridUnitCombo = new KComboBox(gridFrame);
+ layout->addWidget(m_gridUnitCombo, 0, 1);
+
+ m_swingLabel = new QLabel(i18n("Swing:"), gridFrame);
+ layout->addWidget(m_swingLabel, 1, 0);
+ m_swingCombo = new KComboBox(gridFrame);
+ layout->addWidget(m_swingCombo, 1, 1);
+
+ m_iterativeLabel = new QLabel(i18n("Iterative amount:"), gridFrame);
+ layout->addWidget(m_iterativeLabel, 2, 0);
+ m_iterativeCombo = new KComboBox(gridFrame);
+ layout->addWidget(m_iterativeCombo, 2, 1);
+
+ m_durationCheckBox = new QCheckBox
+ (i18n("Quantize durations as well as start times"), gridFrame);
+ layout->addMultiCellWidget(m_durationCheckBox, 3, 3, 0, 1);
+
+ m_postProcessingBox = new QGroupBox
+ (1, Horizontal, i18n("After quantization"), this);
+
+ if (preamble) {
+ m_mainLayout->addMultiCellWidget(m_postProcessingBox,
+ zero, zero + 1,
+ 1, 1);
+ } else {
+ m_mainLayout->addWidget(m_postProcessingBox, zero + 3, 0);
+ }
+
+ bool advanced = true;
+ m_advancedButton = 0;
+ if (showAdvancedButton) {
+ m_advancedButton =
+ new QPushButton(i18n("Show advanced options"), this);
+ m_mainLayout->addWidget(m_advancedButton, zero + 2, 0, Qt::AlignLeft);
+ QObject::connect(m_advancedButton, SIGNAL(clicked()),
+ this, SLOT(slotAdvancedChanged()));
+ }
+
+ QFrame *postFrame = new QFrame(m_postProcessingBox);
+
+ layout = new QGridLayout(postFrame, 4, 1, 5, 3);
+ m_rebeam = new QCheckBox(i18n("Re-beam"), postFrame);
+ m_articulate = new QCheckBox
+ (i18n("Add articulations (staccato, tenuto, slurs)"), postFrame);
+ m_makeViable = new QCheckBox(i18n("Tie notes at barlines etc"), postFrame);
+ m_deCounterpoint = new QCheckBox(i18n("Split-and-tie overlapping chords"), postFrame);
+
+ layout->addWidget(m_rebeam, 0, 0);
+ layout->addWidget(m_articulate, 1, 0);
+ layout->addWidget(m_makeViable, 2, 0);
+ layout->addWidget(m_deCounterpoint, 3, 0);
+
+ QPixmap noMap = NotePixmapFactory::toQPixmap
+ (NotePixmapFactory::makeToolbarPixmap("menu-no-note"));
+
+ int defaultType = 0;
+ timeT defaultUnit =
+ Note(Note::Demisemiquaver).getDuration();
+
+ if (!m_configCategory) {
+ if (defaultQuantizer == Notation)
+ m_configCategory = "Quantize Dialog Notation";
+ else
+ m_configCategory = "Quantize Dialog Grid";
+ }
+
+ int defaultSwing = 0;
+ int defaultIterate = 100;
+
+ if (m_configCategory) {
+ KConfig *config = kapp->config();
+ config->setGroup(m_configCategory);
+ defaultType =
+ config->readNumEntry("quantizetype",
+ (defaultQuantizer == Notation) ? 2 :
+ (defaultQuantizer == Legato) ? 1 :
+ 0);
+ defaultUnit =
+ config->readNumEntry("quantizeunit", defaultUnit);
+ defaultSwing =
+ config->readNumEntry("quantizeswing", defaultSwing);
+ defaultIterate =
+ config->readNumEntry("quantizeiterate", defaultIterate);
+ m_notationTarget->setChecked
+ (config->readBoolEntry("quantizenotationonly",
+ defaultQuantizer == Notation));
+ m_durationCheckBox->setChecked
+ (config->readBoolEntry("quantizedurations", false));
+ m_simplicityCombo->setCurrentItem
+ (config->readNumEntry("quantizesimplicity", 13) - 11);
+ m_maxTuplet->setCurrentItem
+ (config->readNumEntry("quantizemaxtuplet", 3) - 1);
+ m_counterpoint->setChecked
+ (config->readBoolEntry("quantizecounterpoint", false));
+ m_rebeam->setChecked
+ (config->readBoolEntry("quantizerebeam", true));
+ m_makeViable->setChecked
+ (config->readBoolEntry("quantizemakeviable", false));
+ m_deCounterpoint->setChecked
+ (config->readBoolEntry("quantizedecounterpoint", false));
+ m_articulate->setChecked
+ (config->readBoolEntry("quantizearticulate", true));
+ advanced = config->readBoolEntry("quantizeshowadvanced", false);
+ } else {
+ defaultType =
+ (defaultQuantizer == Notation) ? 2 :
+ (defaultQuantizer == Legato) ? 1 : 0;
+ m_notationTarget->setChecked(defaultQuantizer == Notation);
+ m_durationCheckBox->setChecked(false);
+ m_simplicityCombo->setCurrentItem(2);
+ m_maxTuplet->setCurrentItem(2);
+ m_counterpoint->setChecked(false);
+ m_rebeam->setChecked(true);
+ m_makeViable->setChecked(defaultQuantizer == Notation);
+ m_deCounterpoint->setChecked(defaultQuantizer == Notation);
+ m_articulate->setChecked(true);
+ advanced = false;
+ }
+
+ if (preamble || advanced) {
+ m_postProcessingBox->show();
+ } else {
+ m_postProcessingBox->hide();
+ }
+
+ for (unsigned int i = 0; i < m_standardQuantizations.size(); ++i) {
+
+ timeT time = m_standardQuantizations[i];
+ timeT error = 0;
+
+ QPixmap pmap = NotePixmapFactory::toQPixmap
+ (NotePixmapFactory::makeNoteMenuPixmap(time, error));
+ QString label = NotationStrings::makeNoteMenuLabel(time, false, error);
+
+ if (error == 0) {
+ m_gridUnitCombo->insertItem(pmap, label);
+ m_notationUnitCombo->insertItem(pmap, label);
+ } else {
+ m_gridUnitCombo->insertItem(noMap, QString("%1").arg(time));
+ m_notationUnitCombo->insertItem(noMap, QString("%1").arg(time));
+ }
+
+ if (m_standardQuantizations[i] == defaultUnit) {
+ m_gridUnitCombo->setCurrentItem(m_gridUnitCombo->count() - 1);
+ m_notationUnitCombo->setCurrentItem
+ (m_notationUnitCombo->count() - 1);
+ }
+ }
+
+ for (int i = -100; i <= 200; i += 10) {
+ m_swingCombo->insertItem(i == 0 ? i18n("None") : QString("%1%").arg(i));
+ if (i == defaultSwing)
+ m_swingCombo->setCurrentItem(m_swingCombo->count() - 1);
+ }
+
+ for (int i = 10; i <= 100; i += 10) {
+ m_iterativeCombo->insertItem(i == 100 ? i18n("Full quantize") :
+ QString("%1%").arg(i));
+ if (i == defaultIterate)
+ m_iterativeCombo->setCurrentItem(m_iterativeCombo->count() - 1);
+ }
+
+ switch (defaultType) {
+ case 0: // grid
+ m_gridBox->show();
+ m_swingLabel->show();
+ m_swingCombo->show();
+ m_iterativeLabel->show();
+ m_iterativeCombo->show();
+ m_notationBox->hide();
+ m_durationCheckBox->show();
+ m_typeCombo->setCurrentItem(0);
+ break;
+ case 1: // legato
+ m_gridBox->show();
+ m_swingLabel->hide();
+ m_swingCombo->hide();
+ m_iterativeLabel->hide();
+ m_iterativeCombo->hide();
+ m_notationBox->hide();
+ m_durationCheckBox->hide();
+ m_typeCombo->setCurrentItem(1);
+ case 2: // notation
+ m_gridBox->hide();
+ m_notationBox->show();
+ m_typeCombo->setCurrentItem(2);
+ break;
+ }
+
+ connect(m_typeCombo, SIGNAL(activated(int)), SLOT(slotTypeChanged(int)));
+}
+
+Quantizer *
+QuantizeParameters::getQuantizer() const
+{
+ //!!! Excessive duplication with
+ // EventQuantizeCommand::makeQuantizer in editcommands.cpp
+
+ int type = m_typeCombo->currentItem();
+ timeT unit = 0;
+
+ if (type == 0 || type == 1) {
+ unit = m_standardQuantizations[m_gridUnitCombo->currentItem()];
+ } else {
+ unit = m_standardQuantizations[m_notationUnitCombo->currentItem()];
+ }
+
+ Quantizer *quantizer = 0;
+
+ int swing = m_swingCombo->currentItem();
+ swing *= 10;
+ swing -= 100;
+
+ int iterate = m_iterativeCombo->currentItem();
+ iterate *= 10;
+ iterate += 10;
+
+ if (type == 0) {
+
+ if (m_notationTarget->isChecked()) {
+ quantizer = new BasicQuantizer
+ (Quantizer::RawEventData,
+ Quantizer::NotationPrefix,
+ unit, m_durationCheckBox->isChecked(),
+ swing, iterate);
+ } else {
+ quantizer = new BasicQuantizer
+ (Quantizer::RawEventData,
+ Quantizer::RawEventData,
+ unit, m_durationCheckBox->isChecked(),
+ swing, iterate);
+ }
+ } else if (type == 1) {
+ if (m_notationTarget->isChecked()) {
+ quantizer = new LegatoQuantizer
+ (Quantizer::RawEventData,
+ Quantizer::NotationPrefix, unit);
+ } else {
+ quantizer = new LegatoQuantizer
+ (Quantizer::RawEventData,
+ Quantizer::RawEventData,
+ unit);
+ }
+ } else {
+
+ NotationQuantizer *nq;
+
+ if (m_notationTarget->isChecked()) {
+ nq = new NotationQuantizer();
+ } else {
+ nq = new NotationQuantizer
+ (Quantizer::RawEventData,
+ Quantizer::RawEventData);
+ }
+
+ nq->setUnit(unit);
+ nq->setSimplicityFactor(m_simplicityCombo->currentItem() + 11);
+ nq->setMaxTuplet(m_maxTuplet->currentItem() + 1);
+ nq->setContrapuntal(m_counterpoint->isChecked());
+ nq->setArticulate(m_articulate->isChecked());
+
+ quantizer = nq;
+ }
+
+ if (m_configCategory) {
+ KConfig *config = kapp->config();
+ config->setGroup(m_configCategory);
+ config->writeEntry("quantizetype", type);
+ config->writeEntry("quantizeunit", unit);
+ config->writeEntry("quantizeswing", swing);
+ config->writeEntry("quantizeiterate", iterate);
+ config->writeEntry("quantizenotationonly",
+ m_notationTarget->isChecked());
+ if (type == 0) {
+ config->writeEntry("quantizedurations",
+ m_durationCheckBox->isChecked());
+ } else {
+ config->writeEntry("quantizesimplicity",
+ m_simplicityCombo->currentItem() + 11);
+ config->writeEntry("quantizemaxtuplet",
+ m_maxTuplet->currentItem() + 1);
+ config->writeEntry("quantizecounterpoint",
+ m_counterpoint->isChecked());
+ config->writeEntry("quantizearticulate",
+ m_articulate->isChecked());
+ }
+ config->writeEntry("quantizerebeam", m_rebeam->isChecked());
+ config->writeEntry("quantizemakeviable", m_makeViable->isChecked());
+ config->writeEntry("quantizedecounterpoint", m_deCounterpoint->isChecked());
+ }
+
+ return quantizer;
+}
+
+void
+QuantizeParameters::slotAdvancedChanged()
+{
+ if (m_postProcessingBox->isVisible()) {
+ if (m_advancedButton)
+ m_advancedButton->setText(i18n("Show Advanced Options"));
+ m_postProcessingBox->hide();
+ } else {
+ if (m_advancedButton)
+ m_advancedButton->setText(i18n("Hide Advanced Options"));
+ m_postProcessingBox->show();
+ }
+ adjustSize();
+}
+
+void
+QuantizeParameters::showAdvanced(bool show)
+{
+ if (show) {
+ m_postProcessingBox->show();
+ } else {
+ m_postProcessingBox->hide();
+ }
+ adjustSize();
+}
+
+void
+QuantizeParameters::slotTypeChanged(int index)
+{
+ if (index == 0) {
+ m_gridBox->show();
+ m_swingLabel->show();
+ m_swingCombo->show();
+ m_iterativeLabel->show();
+ m_iterativeCombo->show();
+ m_durationCheckBox->show();
+ m_notationBox->hide();
+ } else if (index == 1) {
+ m_gridBox->show();
+ m_swingLabel->hide();
+ m_swingCombo->hide();
+ m_iterativeLabel->hide();
+ m_iterativeCombo->hide();
+ m_durationCheckBox->hide();
+ m_notationBox->hide();
+ } else {
+ m_gridBox->hide();
+ m_notationBox->show();
+ }
+}
+
+}
+#include "QuantizeParameters.moc"
diff --git a/src/gui/widgets/QuantizeParameters.h b/src/gui/widgets/QuantizeParameters.h
new file mode 100644
index 0000000..8eee7ff
--- /dev/null
+++ b/src/gui/widgets/QuantizeParameters.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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_ROSEGARDENQUANTIZEPARAMETERS_H_
+#define _RG_ROSEGARDENQUANTIZEPARAMETERS_H_
+
+#include <qframe.h>
+#include <qstring.h>
+#include <vector>
+#include "base/Event.h"
+#include <qgroupbox.h>
+
+
+class QWidget;
+class QPushButton;
+class QLabel;
+class QGridLayout;
+class QCheckBox;
+class KComboBox;
+
+
+namespace Rosegarden
+{
+
+class Quantizer;
+
+
+class QuantizeParameters : public QFrame
+{
+ Q_OBJECT
+public:
+ enum QuantizerType { Grid, Legato, Notation };
+
+ QuantizeParameters(QWidget *parent,
+ QuantizerType defaultQuantizer,
+ bool showNotationOption,
+ bool showAdvancedButton,
+ QString configCategory,
+ QString preamble = 0);
+
+ /**
+ * Returned quantizer object is on heap -- caller must delete.
+ * Also writes values to KConfig if so requested in constructor.
+ */
+ Quantizer *getQuantizer() const;
+
+ QWidget *getAdvancedWidget() { return m_postProcessingBox; }
+
+ bool shouldRebeam() const { return m_rebeam; }
+ bool shouldDeCounterpoint() const { return m_deCounterpoint; }
+ bool shouldMakeViable() const { return m_makeViable; }
+
+ void showAdvanced(bool show);
+
+public slots:
+ void slotTypeChanged(int);
+ void slotAdvancedChanged();
+
+protected:
+ QString m_configCategory;
+
+ std::vector<timeT> m_standardQuantizations;
+
+ QGridLayout *m_mainLayout;
+
+ KComboBox *m_typeCombo;
+
+ QGroupBox *m_gridBox;
+ QCheckBox *m_durationCheckBox;
+ KComboBox *m_gridUnitCombo;
+ QLabel *m_swingLabel;
+ KComboBox *m_swingCombo;
+ QLabel *m_iterativeLabel;
+ KComboBox *m_iterativeCombo;
+
+ QGroupBox *m_notationBox;
+ QCheckBox *m_notationTarget;
+ KComboBox *m_notationUnitCombo;
+ KComboBox *m_simplicityCombo;
+ KComboBox *m_maxTuplet;
+ QCheckBox *m_counterpoint;
+
+ QPushButton *m_advancedButton;
+ QGroupBox *m_postProcessingBox;
+ QCheckBox *m_articulate;
+ QCheckBox *m_makeViable;
+ QCheckBox *m_deCounterpoint;
+ QCheckBox *m_rebeam;
+};
+
+
+
+}
+
+#endif
diff --git a/src/gui/widgets/RosegardenPopupMenu.h b/src/gui/widgets/RosegardenPopupMenu.h
new file mode 100644
index 0000000..aca82a9
--- /dev/null
+++ b/src/gui/widgets/RosegardenPopupMenu.h
@@ -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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_ROSEGARDENPOPUPMENU_H_
+#define _RG_ROSEGARDENPOPUPMENU_H_
+
+#include <qpopupmenu.h>
+
+namespace Rosegarden {
+
+class RosegardenPopupMenu : public QPopupMenu
+{
+ // just to make itemHeight public
+public:
+ RosegardenPopupMenu(QWidget *parent) : QPopupMenu(parent) { }
+ using QPopupMenu::itemHeight;
+};
+
+
+}
+
+#endif /*ROSEGARDENPOPUPMENU_H_*/
diff --git a/src/gui/widgets/Rotary.cpp b/src/gui/widgets/Rotary.cpp
new file mode 100644
index 0000000..36d5817
--- /dev/null
+++ b/src/gui/widgets/Rotary.cpp
@@ -0,0 +1,560 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
+
+/*
+ Rosegarden
+ A MIDI and audio sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "Rotary.h"
+
+#include "misc/Debug.h"
+#include "gui/dialogs/FloatEdit.h"
+#include "gui/general/GUIPalette.h"
+#include "TextFloat.h"
+#include <kapplication.h>
+#include <klocale.h>
+#include <qbrush.h>
+#include <qcolor.h>
+#include <qdialog.h>
+#include <qimage.h>
+#include <qpainter.h>
+#include <qpalette.h>
+#include <qpen.h>
+#include <qpixmap.h>
+#include <qpoint.h>
+#include <qstring.h>
+#include <qtimer.h>
+#include <qtooltip.h>
+#include <qwidget.h>
+#include <cmath>
+
+
+namespace Rosegarden
+{
+
+#define ROTARY_MIN (0.25 * M_PI)
+#define ROTARY_MAX (1.75 * M_PI)
+#define ROTARY_RANGE (ROTARY_MAX - ROTARY_MIN)
+
+static TextFloat* _float = 0;
+static QTimer *_floatTimer = 0;
+
+Rotary::PixmapCache Rotary::m_pixmaps;
+
+
+Rotary::Rotary(QWidget *parent,
+ float minValue,
+ float maxValue,
+ float step,
+ float pageStep,
+ float initialPosition,
+ int size,
+ TickMode ticks,
+ bool snapToTicks,
+ bool centred,
+ bool logarithmic) :
+ QWidget(parent),
+ m_minValue(minValue),
+ m_maxValue(maxValue),
+ m_step(step),
+ m_pageStep(pageStep),
+ m_size(size),
+ m_tickMode(ticks),
+ m_snapToTicks(snapToTicks),
+ m_centred(centred),
+ m_position(initialPosition),
+ m_snapPosition(m_position),
+ m_initialPosition(initialPosition),
+ m_buttonPressed(false),
+ m_lastY(0),
+ m_lastX(0),
+ m_knobColour(0, 0, 0),
+ m_logarithmic(logarithmic)
+{
+ setBackgroundMode(Qt::NoBackground);
+
+ if (!_float)
+ _float = new TextFloat(this);
+
+ if (!_floatTimer) {
+ _floatTimer = new QTimer();
+ }
+
+ // connect timer
+ connect(_floatTimer, SIGNAL(timeout()), this,
+ SLOT(slotFloatTimeout()));
+ _float->hide();
+
+ QToolTip::add
+ (this,
+ i18n("Click and drag up and down or left and right to modify.\nDouble click to edit value directly."));
+ setFixedSize(size, size);
+
+ emit valueChanged(m_snapPosition);
+}
+
+Rotary::~Rotary()
+{
+ // Remove this connection
+ //
+ disconnect(_floatTimer, SIGNAL(timeout()), this,
+ SLOT(slotFloatTimeout()));
+
+ delete _float;
+ _float = 0;
+}
+
+void
+Rotary::slotFloatTimeout()
+{
+ if (_float)
+ _float->hide();
+}
+
+void
+Rotary::setKnobColour(const QColor &colour)
+{
+ m_knobColour = colour;
+ repaint();
+}
+
+void
+Rotary::paintEvent(QPaintEvent *)
+{
+ QPainter paint;
+
+ double angle = ROTARY_MIN // offset
+ + (ROTARY_RANGE *
+ (double(m_snapPosition - m_minValue) /
+ (double(m_maxValue) - double(m_minValue))));
+ int degrees = int(angle * 180.0 / M_PI);
+
+ // RG_DEBUG << "degrees: " << degrees << ", size " << m_size << ", pixel " << m_knobColour.pixel() << endl;
+
+ int numTicks = 0;
+ switch (m_tickMode) {
+ case LimitTicks:
+ numTicks = 2;
+ break;
+ case IntervalTicks:
+ numTicks = 5;
+ break;
+ case PageStepTicks:
+ numTicks = 1 + (m_maxValue + 0.0001 - m_minValue) / m_pageStep;
+ break;
+ case StepTicks:
+ numTicks = 1 + (m_maxValue + 0.0001 - m_minValue) / m_step;
+ break;
+ default:
+ break;
+ }
+
+ CacheIndex index(m_size, m_knobColour.pixel(), degrees, numTicks, m_centred);
+
+ if (m_pixmaps.find(index) != m_pixmaps.end()) {
+ paint.begin(this);
+ paint.drawPixmap(0, 0, m_pixmaps[index]);
+ paint.end();
+ return ;
+ }
+
+ int scale = 4;
+ int width = m_size * scale;
+ QPixmap map(width, width);
+ map.fill(paletteBackgroundColor());
+ paint.begin(&map);
+
+ QPen pen;
+ pen.setColor(kapp->palette().color(QPalette::Active, QColorGroup::Dark));
+ pen.setWidth(scale);
+ paint.setPen(pen);
+
+ if (m_knobColour != Qt::black) {
+ paint.setBrush(m_knobColour);
+ } else {
+ paint.setBrush(
+ kapp->palette().color(QPalette::Active, QColorGroup::Base));
+ }
+
+ QColor c(m_knobColour);
+ pen.setColor(c);
+ paint.setPen(pen);
+
+ int indent = width * 0.15 + 1;
+
+ paint.drawEllipse(indent, indent, width - 2*indent, width - 2*indent);
+
+ pen.setWidth(2 * scale);
+ int pos = indent + (width - 2 * indent) / 8;
+ int darkWidth = (width - 2 * indent) * 2 / 3;
+ int darkQuote = (130 * 2 / (darkWidth ? darkWidth : 1)) + 100;
+ while (darkWidth) {
+ c = c.light(101);
+ pen.setColor(c);
+ paint.setPen(pen);
+ paint.drawEllipse(pos, pos, darkWidth, darkWidth);
+ if (!--darkWidth)
+ break;
+ paint.drawEllipse(pos, pos, darkWidth, darkWidth);
+ if (!--darkWidth)
+ break;
+ paint.drawEllipse(pos, pos, darkWidth, darkWidth);
+ ++pos;
+ --darkWidth;
+ }
+
+ paint.setBrush(QBrush::NoBrush);
+
+ pen.setColor(colorGroup().dark());
+ pen.setWidth(scale);
+ paint.setPen(pen);
+
+ for (int i = 0; i < numTicks; ++i) {
+ int div = numTicks;
+ if (div > 1)
+ --div;
+ drawTick(paint, ROTARY_MIN + (ROTARY_MAX - ROTARY_MIN) * i / div,
+ width, i != 0 && i != numTicks - 1);
+ }
+
+ // now the bright metering bit
+
+ pen.setColor(GUIPalette::getColour(GUIPalette::RotaryMeter));
+ pen.setWidth(indent);
+ paint.setPen(pen);
+
+ if (m_centred) {
+ paint.drawArc(indent / 2, indent / 2, width - indent, width - indent,
+ 90 * 16, -(degrees - 180) * 16);
+ } else {
+ paint.drawArc(indent / 2, indent / 2, width - indent, width - indent,
+ (180 + 45) * 16, -(degrees - 45) * 16);
+ }
+
+ pen.setWidth(scale);
+ paint.setPen(pen);
+
+ int shadowAngle = -720;
+ c = colorGroup().dark();
+ for (int arc = 120; arc < 2880; arc += 240) {
+ pen.setColor(c);
+ paint.setPen(pen);
+ paint.drawArc(indent, indent, width - 2*indent, width - 2*indent, shadowAngle + arc, 240);
+ paint.drawArc(indent, indent, width - 2*indent, width - 2*indent, shadowAngle - arc, 240);
+ c = c.light( 110 );
+ }
+
+ shadowAngle = 2160;
+ c = colorGroup().dark();
+ for (int arc = 120; arc < 2880; arc += 240) {
+ pen.setColor(c);
+ paint.setPen(pen);
+ paint.drawArc(scale / 2, scale / 2, width - scale, width - scale, shadowAngle + arc, 240);
+ paint.drawArc(scale / 2, scale / 2, width - scale, width - scale, shadowAngle - arc, 240);
+ c = c.light( 109 );
+ }
+
+ // and un-draw the bottom part
+ pen.setColor(paletteBackgroundColor());
+ paint.setPen(pen);
+ paint.drawArc(scale / 2, scale / 2, width - scale, width - scale,
+ -45 * 16, -90 * 16);
+
+ double hyp = double(width) / 2.0;
+ double len = hyp - indent;
+ --len;
+
+ double x0 = hyp;
+ double y0 = hyp;
+
+ double x = hyp - len * sin(angle);
+ double y = hyp + len * cos(angle);
+
+ pen.setWidth(scale * 2);
+ pen.setColor(colorGroup().dark());
+ paint.setPen(pen);
+
+ paint.drawLine(int(x0), int(y0), int(x), int(y));
+
+ paint.end();
+
+ QImage i = map.convertToImage().smoothScale(m_size, m_size);
+ m_pixmaps[index] = QPixmap(i);
+ paint.begin(this);
+ paint.drawPixmap(0, 0, m_pixmaps[index]);
+ paint.end();
+}
+
+void
+Rotary::drawTick(QPainter &paint, double angle, int size, bool internal)
+{
+ double hyp = double(size) / 2.0;
+ double x0 = hyp - (hyp - 1) * sin(angle);
+ double y0 = hyp + (hyp - 1) * cos(angle);
+
+ if (internal) {
+
+ double len = hyp / 4;
+ double x1 = hyp - (hyp - len) * sin(angle);
+ double y1 = hyp + (hyp - len) * cos(angle);
+
+ paint.drawLine(int(x0), int(y0), int(x1), int(y1));
+
+ } else {
+
+ double len = hyp / 4;
+ double x1 = hyp - (hyp + len) * sin(angle);
+ double y1 = hyp + (hyp + len) * cos(angle);
+
+ paint.drawLine(int(x0), int(y0), int(x1), int(y1));
+ }
+}
+
+void
+Rotary::snapPosition()
+{
+ m_snapPosition = m_position;
+
+ if (m_snapToTicks) {
+
+ switch (m_tickMode) {
+
+ case NoTicks:
+ break; // meaningless
+
+ case LimitTicks:
+ if (m_position < (m_minValue + m_maxValue) / 2.0) {
+ m_snapPosition = m_minValue;
+ } else {
+ m_snapPosition = m_maxValue;
+ }
+ break;
+
+ case IntervalTicks:
+ m_snapPosition = m_minValue +
+ (m_maxValue - m_minValue) / 4.0 *
+ int((m_snapPosition - m_minValue) /
+ ((m_maxValue - m_minValue) / 4.0));
+ break;
+
+ case PageStepTicks:
+ m_snapPosition = m_minValue +
+ m_pageStep *
+ int((m_snapPosition - m_minValue) / m_pageStep);
+ break;
+
+ case StepTicks:
+ m_snapPosition = m_minValue +
+ m_step *
+ int((m_snapPosition - m_minValue) / m_step);
+ break;
+ }
+ }
+}
+
+void
+Rotary::mousePressEvent(QMouseEvent *e)
+{
+ if (e->button() == LeftButton) {
+ m_buttonPressed = true;
+ m_lastY = e->y();
+ m_lastX = e->x();
+ } else if (e->button() == MidButton) // reset to default
+ {
+ m_position = m_initialPosition;
+ snapPosition();
+ update();
+ emit valueChanged(m_snapPosition);
+ } else if (e->button() == RightButton) // reset to centre position
+ {
+ m_position = (m_maxValue + m_minValue) / 2.0;
+ snapPosition();
+ update();
+ emit valueChanged(m_snapPosition);
+ }
+
+ QPoint totalPos = mapTo(topLevelWidget(), QPoint(0, 0));
+
+ if (!_float)
+ _float = new TextFloat(this);
+ _float->reparent(this);
+ _float->move(totalPos + QPoint(width() + 2, -height() / 2));
+ if (m_logarithmic) {
+ _float->setText(QString("%1").arg(powf(10, m_position)));
+ } else {
+ _float->setText(QString("%1").arg(m_position));
+ }
+ _float->show();
+
+// std::cerr << "Rotary::mousePressEvent: logarithmic = " << m_logarithmic
+// << ", position = " << m_position << std::endl;
+
+ if (e->button() == RightButton || e->button() == MidButton) {
+ // one shot, 500ms
+ _floatTimer->start(500, true);
+ }
+}
+
+void
+Rotary::mouseDoubleClickEvent(QMouseEvent * /*e*/)
+{
+ float minv = m_minValue;
+ float maxv = m_maxValue;
+ float val = m_position;
+ float step = m_step;
+
+ if (m_logarithmic) {
+ minv = powf(10, minv);
+ maxv = powf(10, maxv);
+ val = powf(10, val);
+ step = powf(10, step);
+ if (step > 0.001) step = 0.001;
+ }
+
+ FloatEdit dialog(this,
+ i18n("Select a new value"),
+ i18n("Enter a new value"),
+ minv,
+ maxv,
+ val,
+ step);
+
+ if (dialog.exec() == QDialog::Accepted) {
+ float newval = dialog.getValue();
+ if (m_logarithmic) {
+ if (m_position < powf(10, -10)) m_position = -10;
+ else m_position = log10f(newval);
+ } else {
+ m_position = newval;
+ }
+ snapPosition();
+ update();
+
+ emit valueChanged(m_snapPosition);
+ }
+}
+
+void
+Rotary::mouseReleaseEvent(QMouseEvent *e)
+{
+ if (e->button() == LeftButton) {
+ m_buttonPressed = false;
+ m_lastY = 0;
+ m_lastX = 0;
+
+ // Hide the float text
+ //
+ if (_float)
+ _float->hide();
+ }
+}
+
+void
+Rotary::mouseMoveEvent(QMouseEvent *e)
+{
+ if (m_buttonPressed) {
+ // Dragging by x or y axis when clicked modifies value
+ //
+ float newValue = m_position +
+ (m_lastY - float(e->y()) + float(e->x()) - m_lastX) * m_step;
+
+ if (newValue > m_maxValue)
+ m_position = m_maxValue;
+ else
+ if (newValue < m_minValue)
+ m_position = m_minValue;
+ else
+ m_position = newValue;
+
+ m_lastY = e->y();
+ m_lastX = e->x();
+
+ snapPosition();
+
+ // don't update if there's nothing to update
+ // if (m_lastPosition == m_snapPosition) return;
+
+ update();
+
+ emit valueChanged(m_snapPosition);
+
+ // draw on the float text
+ if (m_logarithmic) {
+ _float->setText(QString("%1").arg(powf(10, m_snapPosition)));
+ } else {
+ _float->setText(QString("%1").arg(m_snapPosition));
+ }
+ }
+}
+
+void
+Rotary::wheelEvent(QWheelEvent *e)
+{
+ if (e->delta() > 0)
+ m_position -= m_pageStep;
+ else
+ m_position += m_pageStep;
+
+ if (m_position > m_maxValue)
+ m_position = m_maxValue;
+
+ if (m_position < m_minValue)
+ m_position = m_minValue;
+
+ snapPosition();
+ update();
+
+ if (!_float)
+ _float = new TextFloat(this);
+
+ // draw on the float text
+ if (m_logarithmic) {
+ _float->setText(QString("%1").arg(powf(10, m_snapPosition)));
+ } else {
+ _float->setText(QString("%1").arg(m_snapPosition));
+ }
+
+ // Reposition - we need to sum the relative positions up to the
+ // topLevel or dialog to please move(). Move just top/right of the rotary
+ //
+ QPoint totalPos = mapTo(topLevelWidget(), QPoint(0, 0));
+ _float->reparent(this);
+ _float->move(totalPos + QPoint(width() + 2, -height() / 2));
+ _float->show();
+
+ // one shot, 500ms
+ _floatTimer->start(500, true);
+
+ // set it to show for a timeout value
+ emit valueChanged(m_snapPosition);
+}
+
+void
+Rotary::setPosition(float position)
+{
+ m_position = position;
+
+ snapPosition();
+ update();
+}
+
+}
+#include "Rotary.moc"
diff --git a/src/gui/widgets/Rotary.h b/src/gui/widgets/Rotary.h
new file mode 100644
index 0000000..2efacf9
--- /dev/null
+++ b/src/gui/widgets/Rotary.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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_ROSEGARDENROTARY_H_
+#define _RG_ROSEGARDENROTARY_H_
+
+#include <map>
+#include <qcolor.h>
+#include <qwidget.h>
+
+
+class QWheelEvent;
+class QPaintEvent;
+class QPainter;
+class QMouseEvent;
+
+
+namespace Rosegarden
+{
+
+
+
+class Rotary : public QWidget
+{
+ Q_OBJECT
+public:
+
+ enum TickMode {
+ NoTicks, // plain circle with no marks for end points etc
+ LimitTicks, // marks at end points but not any intermediate points
+ IntervalTicks, // end points plus quarter, half, three-quarters
+ PageStepTicks, // end points plus every page-step interval
+ StepTicks // end points plus every step interval
+ };
+
+ Rotary(QWidget *parent,
+ float minValue = 0.0,
+ float maxValue = 100.0,
+ float step = 1.0,
+ float pageStep = 10.0,
+ float initialPosition = 50.0,
+ int size = 20,
+ TickMode ticks = NoTicks,
+ bool snapToTicks = false,
+ bool centred = false,
+ bool logarithmic = false); // extents are logs, exp for display
+ ~Rotary();
+
+ void setMinValue(float min) { m_minValue = min; }
+ float getMinValue() const { return m_minValue; }
+
+ void setMaxValue(float max) { m_maxValue = max; }
+ float getMaxValue() const { return m_maxValue; }
+
+ void setStep(float step) { m_step = step; }
+ float getStep() const { return m_step; }
+
+ void setPageStep(float step) { m_pageStep = step; }
+ float getPageStep() const { return m_pageStep; }
+
+ int getSize() const { return m_size; }
+
+ // Position
+ //
+ float getPosition() const { return m_position; }
+ void setPosition(float position);
+
+ // Set the colour of the knob
+ //
+ void setKnobColour(const QColor &colour);
+ QColor getKnobColour() const { return m_knobColour; }
+
+signals:
+ void valueChanged(float);
+
+protected slots:
+ void slotFloatTimeout();
+
+protected:
+ virtual void paintEvent(QPaintEvent *e);
+ virtual void mousePressEvent(QMouseEvent *e);
+ virtual void mouseReleaseEvent(QMouseEvent *e);
+ virtual void mouseMoveEvent(QMouseEvent *e);
+ virtual void mouseDoubleClickEvent(QMouseEvent *e);
+ virtual void wheelEvent(QWheelEvent *e);
+
+ void snapPosition();
+ void drawPosition();
+ void drawTick(QPainter &paint, double angle, int size, bool internal);
+
+ float m_minValue;
+ float m_maxValue;
+ float m_step;
+ float m_pageStep;
+ int m_size;
+ TickMode m_tickMode;
+ bool m_snapToTicks;
+ bool m_centred;
+ bool m_logarithmic;
+
+ float m_position;
+ float m_snapPosition;
+ float m_initialPosition;
+ bool m_buttonPressed;
+ int m_lastY;
+ int m_lastX;
+
+ QColor m_knobColour;
+
+ struct CacheIndex {
+
+ CacheIndex(int _s, int _c, int _a, int _n, int _ct) :
+ size(_s), colour(_c), angle(_a), numTicks(_n), centred(_ct) { }
+
+ bool operator<(const CacheIndex &i) const {
+ // woo!
+ if (size < i.size) return true;
+ else if (size > i.size) return false;
+ else if (colour < i.colour) return true;
+ else if (colour > i.colour) return false;
+ else if (angle < i.angle) return true;
+ else if (angle > i.angle) return false;
+ else if (numTicks < i.numTicks) return true;
+ else if (numTicks > i.numTicks) return false;
+ else if (centred == i.centred) return false;
+ else if (!centred) return true;
+ return false;
+ }
+
+ int size;
+ unsigned int colour;
+ int angle;
+ int numTicks;
+ bool centred;
+ };
+
+ typedef std::map<CacheIndex, QPixmap> PixmapCache;
+ static PixmapCache m_pixmaps;
+};
+
+
+
+}
+
+#endif
diff --git a/src/gui/widgets/ScrollBox.cpp b/src/gui/widgets/ScrollBox.cpp
new file mode 100644
index 0000000..b409209
--- /dev/null
+++ b/src/gui/widgets/ScrollBox.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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ This file is based on code from KGhostView, Copyright 1997-2002
+ Markkhu Hihnala <mah@ee.oulu.fi>
+ and the KGhostView authors.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "ScrollBox.h"
+
+#include <qapplication.h>
+#include <qframe.h>
+#include <qimage.h>
+#include <qpainter.h>
+#include <qpixmap.h>
+#include <qpoint.h>
+#include <qrect.h>
+#include <qsize.h>
+#include <qwidget.h>
+
+
+namespace Rosegarden
+{
+
+ScrollBox::ScrollBox(QWidget* parent, SizeMode sizeMode, const char* name) :
+ QFrame(parent, name),
+ m_sizeMode(sizeMode)
+{
+ setFrameStyle(Panel | Sunken);
+}
+
+void ScrollBox::mousePressEvent(QMouseEvent* e)
+{
+ m_mouse = e->pos();
+ if (e->button() == RightButton)
+ emit button3Pressed();
+ if (e->button() == MidButton)
+ emit button2Pressed();
+}
+
+void ScrollBox::mouseMoveEvent(QMouseEvent* e)
+{
+ if (e->state() != LeftButton)
+ return ;
+
+ int dx = (e->pos().x() - m_mouse.x()) * m_pagesize.width() / width();
+ int dy = (e->pos().y() - m_mouse.y()) * m_pagesize.height() / height();
+
+ emit valueChanged(QPoint(m_viewpos.x() + dx, m_viewpos.y() + dy));
+ emit valueChangedRelative(dx, dy);
+
+ m_mouse = e->pos();
+}
+
+void ScrollBox::drawContents(QPainter* paint)
+{
+ if (m_pagesize.isEmpty())
+ return ;
+
+ QRect c(contentsRect());
+
+ paint->setPen(Qt::red);
+
+ int len = m_pagesize.width();
+ int x = c.x() + c.width() * m_viewpos.x() / len;
+ int w = c.width() * m_viewsize.width() / len ;
+ if (w > c.width())
+ w = c.width();
+
+ len = m_pagesize.height();
+ int y = c.y() + c.height() * m_viewpos.y() / len;
+ int h = c.height() * m_viewsize.height() / len;
+ if (h > c.height())
+ h = c.height();
+
+ paint->drawRect(x, y, w, h);
+}
+
+void ScrollBox::setPageSize(const QSize& s)
+{
+ m_pagesize = s;
+
+ setFixedWidth(100);
+ setFixedHeight(100);
+
+ int maxWidth = int(QApplication::desktop()->width() * 0.75);
+ int maxHeight = int(QApplication::desktop()->height() * 0.75);
+
+ if (m_sizeMode == FixWidth) {
+ int height = s.height() * width() / s.width();
+ if (height > maxHeight) {
+ setFixedWidth(width() * maxHeight / height);
+ height = maxHeight;
+ }
+ setFixedHeight(height);
+ } else {
+ int width = s.width() * height() / s.height();
+ if (width > maxWidth) {
+ setFixedHeight(height() * maxWidth / width);
+ width = maxWidth;
+ }
+ setFixedWidth(width);
+ }
+
+ repaint();
+}
+
+void ScrollBox::setViewSize(const QSize& s)
+{
+ m_viewsize = s;
+ repaint();
+}
+
+void ScrollBox::setViewPos(const QPoint& pos)
+{
+ m_viewpos = pos;
+ repaint();
+}
+
+void ScrollBox::setViewX(int x)
+{
+ m_viewpos = QPoint(x, m_viewpos.y());
+ repaint();
+}
+
+void ScrollBox::setViewY(int y)
+{
+ m_viewpos = QPoint(m_viewpos.x(), y);
+ repaint();
+}
+
+void ScrollBox::setThumbnail(QPixmap img)
+{
+ setPaletteBackgroundPixmap(img.convertToImage().smoothScale(size()));
+}
+
+}
+#include "ScrollBox.moc"
diff --git a/src/gui/widgets/ScrollBox.h b/src/gui/widgets/ScrollBox.h
new file mode 100644
index 0000000..3f8140e
--- /dev/null
+++ b/src/gui/widgets/ScrollBox.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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ This file is based on code from KGhostView, Copyright 1997-2002
+ Markkhu Hihnala <mah@ee.oulu.fi>
+ and the KGhostView authors.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_SCROLLBOX_H_
+#define _RG_SCROLLBOX_H_
+
+#include <qframe.h>
+#include <qpixmap.h>
+#include <qpoint.h>
+#include <qsize.h>
+
+
+class QWidget;
+class QPainter;
+class QMouseEvent;
+
+
+namespace Rosegarden
+{
+
+class ScrollBox: public QFrame
+{
+ Q_OBJECT
+
+public:
+ enum SizeMode { FixWidth, FixHeight };
+
+ ScrollBox(QWidget *parent = 0,
+ SizeMode mode = FixWidth,
+ const char *name = 0);
+
+public slots:
+ void setPageSize(const QSize&);
+ void setViewSize(const QSize&);
+ void setViewPos(const QPoint&);
+ void setViewPos(int x, int y) { setViewPos(QPoint(x, y)); }
+ void setViewX(int x);
+ void setViewY(int y);
+ void setThumbnail(QPixmap img);
+
+signals:
+ void valueChanged(const QPoint&);
+ void valueChangedRelative(int dx, int dy);
+ void button2Pressed();
+ void button3Pressed();
+
+protected:
+ void mousePressEvent(QMouseEvent *);
+ void mouseMoveEvent(QMouseEvent *);
+ void drawContents(QPainter *);
+
+private:
+ QPoint m_viewpos;
+ QPoint m_mouse;
+ QSize m_pagesize;
+ QSize m_viewsize;
+ SizeMode m_sizeMode;
+};
+
+
+}
+
+#endif
diff --git a/src/gui/widgets/ScrollBoxDialog.cpp b/src/gui/widgets/ScrollBoxDialog.cpp
new file mode 100644
index 0000000..e442985
--- /dev/null
+++ b/src/gui/widgets/ScrollBoxDialog.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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "ScrollBoxDialog.h"
+
+#include "ScrollBox.h"
+#include <kdialog.h>
+#include <qframe.h>
+#include <qpainter.h>
+#include <qpixmap.h>
+#include <qpoint.h>
+#include <qsize.h>
+#include <qwidget.h>
+
+
+namespace Rosegarden
+{
+
+ScrollBoxDialog::ScrollBoxDialog(QWidget *parent,
+ ScrollBox::SizeMode sizeMode,
+ const char *name,
+ WFlags flags) :
+ KDialog(parent, name, flags),
+ m_scrollbox(new ScrollBox(this, sizeMode))
+{ }
+
+
+ScrollBoxDialog::~ScrollBoxDialog()
+{ }
+
+
+void ScrollBoxDialog::closeEvent(QCloseEvent *e)
+{
+ e->accept();
+ emit closed();
+}
+
+void ScrollBoxDialog::setPageSize(const QSize& s)
+{
+ m_scrollbox->setPageSize(s);
+ setFixedHeight(m_scrollbox->height());
+ setFixedWidth(m_scrollbox->width());
+}
+
+}
+#include "ScrollBoxDialog.moc"
diff --git a/src/gui/widgets/ScrollBoxDialog.h b/src/gui/widgets/ScrollBoxDialog.h
new file mode 100644
index 0000000..8da1771
--- /dev/null
+++ b/src/gui/widgets/ScrollBoxDialog.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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_SCROLLBOXDIALOG_H_
+#define _RG_SCROLLBOXDIALOG_H_
+
+#include "ScrollBox.h"
+#include <kdialog.h>
+
+
+class QWidget;
+class QSize;
+class QCloseEvent;
+
+
+namespace Rosegarden
+{
+
+
+
+class ScrollBoxDialog : public KDialog
+{
+ Q_OBJECT
+
+public:
+ ScrollBoxDialog(QWidget *parent = 0,
+ ScrollBox::SizeMode mode = ScrollBox::FixWidth,
+ const char *name = 0,
+ WFlags flags = 0);
+ ~ScrollBoxDialog();
+
+ ScrollBox *scrollbox() { return m_scrollbox; }
+ void setPageSize(const QSize&);
+
+protected:
+ virtual void closeEvent(QCloseEvent * e);
+
+signals:
+ void closed();
+
+private:
+ ScrollBox *m_scrollbox;
+};
+
+
+
+}
+
+#endif
diff --git a/src/gui/widgets/SpinBox.cpp b/src/gui/widgets/SpinBox.cpp
new file mode 100644
index 0000000..10963c8
--- /dev/null
+++ b/src/gui/widgets/SpinBox.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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "SpinBox.h"
+#include "misc/Strings.h"
+
+#include <qspinbox.h>
+#include <qstring.h>
+#include <qwidget.h>
+
+
+namespace Rosegarden
+{
+
+SpinBox::SpinBox(QWidget *parent, const char *name):
+ QSpinBox(parent, name), m_doubleValue(0)
+{}
+
+QString
+SpinBox::mapValueToText(int value)
+{
+ QString doubleStr;
+
+ // Assume we want to show the precision
+ //
+ if ((int)m_doubleValue != value)
+ m_doubleValue = (double) value;
+
+ doubleStr.sprintf("%4.6f", m_doubleValue);
+
+ // clear any special value
+ //setSpecialValueText("");
+
+ return doubleStr;
+}
+
+int
+SpinBox::mapTextToValue(bool * /*ok*/)
+{
+ double number = qstrtodouble(text());
+
+ if (number) {
+ m_doubleValue = number;
+ return ((int)number);
+ }
+
+ return 120; // default
+}
+
+}
+#include "SpinBox.moc"
diff --git a/src/gui/widgets/SpinBox.h b/src/gui/widgets/SpinBox.h
new file mode 100644
index 0000000..a0dfffe
--- /dev/null
+++ b/src/gui/widgets/SpinBox.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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_ROSEGARDENSPINBOX_H_
+#define _RG_ROSEGARDENSPINBOX_H_
+
+#include <qspinbox.h>
+#include <qstring.h>
+
+
+class QWidget;
+
+
+namespace Rosegarden
+{
+
+
+
+/**
+ * A Combobox that just about handles doubles - you have
+ * to set the precision outside of this class if you're
+ * using it with Qt designer. Urch.
+ */
+class SpinBox : public QSpinBox
+{
+ Q_OBJECT
+public:
+ SpinBox(QWidget *parent = 0, const char *name=0);
+
+ double getDoubleValue() const { return m_doubleValue; }
+
+protected:
+ virtual QString mapValueToText (int value);
+ virtual int mapTextToValue(bool *ok);
+
+ double m_doubleValue;
+};
+
+
+
+}
+
+#endif
diff --git a/src/gui/widgets/TextFloat.cpp b/src/gui/widgets/TextFloat.cpp
new file mode 100644
index 0000000..5e3ddbc
--- /dev/null
+++ b/src/gui/widgets/TextFloat.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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "TextFloat.h"
+#include <kapplication.h>
+
+#include "gui/general/GUIPalette.h"
+#include <qfontmetrics.h>
+#include <qpainter.h>
+#include <qpalette.h>
+#include <qpoint.h>
+#include <qrect.h>
+#include <qstring.h>
+#include <qwidget.h>
+
+
+namespace Rosegarden
+{
+
+TextFloat::TextFloat(QWidget *parent):
+ QWidget(parent, "TextFloat",
+ WStyle_Customize | WStyle_NoBorder | WStyle_StaysOnTop),
+ m_text("")
+{
+ reparent(parentWidget());
+ resize(20, 20);
+}
+
+void
+TextFloat::reparent(QWidget *newParent)
+{
+ QPoint position = newParent->pos();
+
+ // Get position and reparent to either top level or dialog
+ //
+ while (newParent->parentWidget() && !newParent->isTopLevel()
+ && !newParent->isDialog()) {
+ newParent = newParent->parentWidget();
+ position += newParent->pos();
+ }
+
+ // Position this widget to the right of the parent
+ //
+ //move(pos + QPoint(parent->width() + 5, 5));
+
+ QWidget::reparent(newParent,
+ WStyle_Customize | WStyle_NoBorder | WStyle_StaysOnTop,
+ position + QPoint(20, 5));
+}
+
+void
+TextFloat::paintEvent(QPaintEvent *e)
+{
+ QPainter paint(this);
+
+ paint.setClipRegion(e->region());
+ paint.setClipRect(e->rect().normalize());
+
+ paint.setPen(kapp->palette().color(QPalette::Active, QColorGroup::Dark));
+
+ paint.setPen(GUIPalette::getColour(GUIPalette::RotaryFloatForeground));
+ paint.setBrush(GUIPalette::getColour(GUIPalette::RotaryFloatBackground));
+
+ QFontMetrics metrics(paint.fontMetrics());
+
+ QRect r = metrics.boundingRect(0, 0, 400, 400, Qt::AlignAuto, m_text);
+ resize(r.width() + 7, r.height() + 7);
+ paint.drawRect(0, 0, r.width() + 6, r.height() + 6);
+ paint.setPen(Qt::black);
+ paint.drawText(QRect(3, 3, r.width(), r.height()), Qt::AlignAuto, m_text);
+
+ /*
+ QRect textBound = metrics.boundingRect(m_text);
+
+ resize(textBound.width() + 7, textBound.height() + 7);
+ paint.drawRect(0, 0, textBound.width() + 6, textBound.height() + 6);
+
+ paint.setPen(Qt::black);
+ paint.drawText(3, textBound.height() + 3, m_text);
+ */
+}
+
+void
+TextFloat::setText(const QString &text)
+{
+ m_text = text;
+ repaint();
+}
+
+}
diff --git a/src/gui/widgets/TextFloat.h b/src/gui/widgets/TextFloat.h
new file mode 100644
index 0000000..d604a83
--- /dev/null
+++ b/src/gui/widgets/TextFloat.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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_ROSEGARDENTEXTFLOAT_H_
+#define _RG_ROSEGARDENTEXTFLOAT_H_
+
+#include <qstring.h>
+#include <qwidget.h>
+
+
+class QPaintEvent;
+
+
+namespace Rosegarden
+{
+
+
+
+class TextFloat : public QWidget
+{
+public:
+ TextFloat(QWidget *parent);
+ virtual ~TextFloat() {;}
+
+ void setText(const QString &text);
+
+ // Reparent the float correctly by context
+ //
+ void reparent(QWidget *newParent);
+
+protected:
+ virtual void paintEvent(QPaintEvent *e);
+
+ QString m_text;
+};
+
+
+
+
+}
+
+#endif
diff --git a/src/gui/widgets/TimeWidget.cpp b/src/gui/widgets/TimeWidget.cpp
new file mode 100644
index 0000000..b9bad6f
--- /dev/null
+++ b/src/gui/widgets/TimeWidget.cpp
@@ -0,0 +1,668 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
+
+/*
+ Rosegarden
+ A MIDI and audio sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "TimeWidget.h"
+#include <qlayout.h>
+
+#include <klocale.h>
+#include "misc/Debug.h"
+#include "base/Composition.h"
+#include "base/NotationTypes.h"
+#include "base/RealTime.h"
+#include "gui/editors/notation/NotationStrings.h"
+#include "gui/editors/notation/NotePixmapFactory.h"
+#include <qcombobox.h>
+#include <qframe.h>
+#include <qgroupbox.h>
+#include <qlabel.h>
+#include <qlineedit.h>
+#include <qpixmap.h>
+#include <qspinbox.h>
+#include <qstring.h>
+#include <qwidget.h>
+
+
+namespace Rosegarden
+{
+
+TimeWidget::TimeWidget(QString title,
+ QWidget *parent,
+ Composition *composition,
+ timeT absTime,
+ bool editable,
+ bool constrainToCompositionDuration) :
+ QGroupBox(1, Horizontal, title, parent),
+ m_composition(composition),
+ m_isDuration(false),
+ m_constrain(constrainToCompositionDuration),
+ m_time(absTime),
+ m_startTime(0),
+ m_defaultTime(absTime)
+{
+ init(editable);
+}
+
+TimeWidget::TimeWidget(QString title,
+ QWidget *parent,
+ Composition *composition,
+ timeT startTime,
+ timeT duration,
+ bool editable,
+ bool constrainToCompositionDuration) :
+ QGroupBox(1, Horizontal, title, parent),
+ m_composition(composition),
+ m_isDuration(true),
+ m_constrain(constrainToCompositionDuration),
+ m_time(duration),
+ m_startTime(startTime),
+ m_defaultTime(duration)
+{
+ init(editable);
+}
+
+void
+TimeWidget::init(bool editable)
+{
+ int denoms[] = {
+ 1, 2, 3, 4, 6, 8, 12, 16, 24, 32, 48, 64, 96, 128
+ };
+
+ bool savedEditable = editable;
+ editable = true;
+
+ QFrame *frame = new QFrame(this);
+ QGridLayout *layout = new QGridLayout(frame, 7, 3, 5, 5);
+ QLabel *label = 0;
+
+ if (m_isDuration) {
+
+ label = new QLabel(i18n("Note:"), frame);
+ label->setAlignment(Qt::AlignRight | Qt::AlignVCenter);
+ layout->addWidget(label, 0, 0);
+
+ if (editable) {
+ m_note = new QComboBox(frame);
+ m_noteDurations.push_back(0);
+ m_note->insertItem(i18n("<inexact>"));
+ for (size_t i = 0; i < sizeof(denoms) / sizeof(denoms[0]); ++i) {
+
+ timeT duration =
+ Note(Note::Breve).getDuration() / denoms[i];
+
+ if (denoms[i] > 1 && denoms[i] < 128 && (denoms[i] % 3) != 0) {
+ // not breve or hemidemi, not a triplet
+ timeT dottedDuration = duration * 3 / 2;
+ m_noteDurations.push_back(dottedDuration);
+ timeT error = 0;
+ QString label = NotationStrings::makeNoteMenuLabel
+ (dottedDuration, false, error);
+ QPixmap pmap = NotePixmapFactory::toQPixmap
+ (NotePixmapFactory::makeNoteMenuPixmap(dottedDuration, error));
+ m_note->insertItem(pmap, label); // ignore error
+ }
+
+ m_noteDurations.push_back(duration);
+ timeT error = 0;
+ QString label = NotationStrings::makeNoteMenuLabel
+ (duration, false, error);
+ QPixmap pmap = NotePixmapFactory::toQPixmap
+ (NotePixmapFactory::makeNoteMenuPixmap(duration, error));
+ m_note->insertItem(pmap, label); // ignore error
+ }
+ connect(m_note, SIGNAL(activated(int)),
+ this, SLOT(slotNoteChanged(int)));
+ layout->addMultiCellWidget(m_note, 0, 0, 1, 3);
+
+ } else {
+
+ m_note = 0;
+ timeT error = 0;
+ QString label = NotationStrings::makeNoteMenuLabel
+ (m_time, false, error);
+ if (error != 0)
+ label = i18n("<inexact>");
+ QLineEdit *le = new QLineEdit(label, frame);
+ le->setReadOnly(true);
+ layout->addMultiCellWidget(le, 0, 0, 1, 3);
+ }
+
+ label = new QLabel(i18n("Units:"), frame);
+ label->setAlignment(Qt::AlignRight | Qt::AlignVCenter);
+ layout->addWidget(label, 0, 4);
+
+ if (editable) {
+ m_timeT = new QSpinBox(frame);
+ m_timeT->setLineStep
+ (Note(Note::Shortest).getDuration());
+ connect(m_timeT, SIGNAL(valueChanged(int)),
+ this, SLOT(slotTimeTChanged(int)));
+ layout->addWidget(m_timeT, 0, 5);
+ } else {
+ m_timeT = 0;
+ QLineEdit *le = new QLineEdit(QString("%1").arg(m_time), frame);
+ le->setReadOnly(true);
+ layout->addWidget(le, 0, 5);
+ }
+
+ } else {
+
+ m_note = 0;
+
+ label = new QLabel(i18n("Time:"), frame);
+ label->setAlignment(Qt::AlignRight | Qt::AlignVCenter);
+ layout->addWidget(label, 0, 0);
+
+ if (editable) {
+ m_timeT = new QSpinBox(frame);
+ m_timeT->setLineStep
+ (Note(Note::Shortest).getDuration());
+ connect(m_timeT, SIGNAL(valueChanged(int)),
+ this, SLOT(slotTimeTChanged(int)));
+ layout->addWidget(m_timeT, 0, 1);
+ layout->addWidget(new QLabel(i18n("units"), frame), 0, 2);
+ } else {
+ m_timeT = 0;
+ QLineEdit *le = new QLineEdit(QString("%1").arg(m_time), frame);
+ le->setReadOnly(true);
+ layout->addWidget(le, 0, 2);
+ }
+ }
+
+ label = new QLabel(m_isDuration ? i18n("Measures:") : i18n("Measure:"), frame);
+ label->setAlignment(Qt::AlignRight | Qt::AlignVCenter);
+ layout->addWidget(label, 1, 0);
+
+ if (editable) {
+ m_barLabel = 0;
+ m_bar = new QSpinBox(frame);
+ if (m_isDuration)
+ m_bar->setMinValue(0);
+ connect(m_bar, SIGNAL(valueChanged(int)),
+ this, SLOT(slotBarBeatOrFractionChanged(int)));
+ layout->addWidget(m_bar, 1, 1);
+ } else {
+ m_bar = 0;
+ m_barLabel = new QLineEdit(frame);
+ m_barLabel->setReadOnly(true);
+ layout->addWidget(m_barLabel, 1, 1);
+ }
+
+ label = new QLabel(m_isDuration ? i18n("beats:") : i18n("beat:"), frame);
+ label->setAlignment(Qt::AlignRight | Qt::AlignVCenter);
+ layout->addWidget(label, 1, 2);
+
+ if (editable) {
+ m_beatLabel = 0;
+ m_beat = new QSpinBox(frame);
+ m_beat->setMinValue(1);
+ connect(m_beat, SIGNAL(valueChanged(int)),
+ this, SLOT(slotBarBeatOrFractionChanged(int)));
+ layout->addWidget(m_beat, 1, 3);
+ } else {
+ m_beat = 0;
+ m_beatLabel = new QLineEdit(frame);
+ m_beatLabel->setReadOnly(true);
+ layout->addWidget(m_beatLabel, 1, 3);
+ }
+
+ label = new QLabel(i18n("%1:").arg(NotationStrings::getShortNoteName
+ (Note
+ (Note::Shortest), true)),
+ frame);
+ label->setAlignment(Qt::AlignRight | Qt::AlignVCenter);
+ layout->addWidget(label, 1, 4);
+
+ if (editable) {
+ m_fractionLabel = 0;
+ m_fraction = new QSpinBox(frame);
+ m_fraction->setMinValue(1);
+ connect(m_fraction, SIGNAL(valueChanged(int)),
+ this, SLOT(slotBarBeatOrFractionChanged(int)));
+ layout->addWidget(m_fraction, 1, 5);
+ } else {
+ m_fraction = 0;
+ m_fractionLabel = new QLineEdit(frame);
+ m_fractionLabel->setReadOnly(true);
+ layout->addWidget(m_fractionLabel, 1, 5);
+ }
+
+ m_timeSig = new QLabel(frame);
+ layout->addWidget(m_timeSig, 1, 6);
+
+ label = new QLabel(i18n("Seconds:"), frame);
+ label->setAlignment(Qt::AlignRight | Qt::AlignVCenter);
+ layout->addWidget(label, 2, 0);
+
+ if (editable) {
+ m_secLabel = 0;
+ m_sec = new QSpinBox(frame);
+ if (m_isDuration)
+ m_sec->setMinValue(0);
+ connect(m_sec, SIGNAL(valueChanged(int)),
+ this, SLOT(slotSecOrMSecChanged(int)));
+ layout->addWidget(m_sec, 2, 1);
+ } else {
+ m_sec = 0;
+ m_secLabel = new QLineEdit(frame);
+ m_secLabel->setReadOnly(true);
+ layout->addWidget(m_secLabel, 2, 1);
+ }
+
+ label = new QLabel(i18n("msec:"), frame);
+ label->setAlignment(Qt::AlignRight | Qt::AlignVCenter);
+ layout->addWidget(label, 2, 2);
+
+ if (editable) {
+ m_msecLabel = 0;
+ m_msec = new QSpinBox(frame);
+ m_msec->setMinValue(0);
+ m_msec->setLineStep(10);
+ connect(m_msec, SIGNAL(valueChanged(int)),
+ this, SLOT(slotSecOrMSecChanged(int)));
+ layout->addWidget(m_msec, 2, 3);
+ } else {
+ m_msec = 0;
+ m_msecLabel = new QLineEdit(frame);
+ m_msecLabel->setReadOnly(true);
+ layout->addWidget(m_msecLabel, 2, 3);
+ }
+
+ if (m_isDuration) {
+ m_tempo = new QLabel(frame);
+ layout->addWidget(m_tempo, 2, 6);
+ } else {
+ m_tempo = 0;
+ }
+
+ if (!savedEditable) {
+ if (m_note)
+ m_note ->setEnabled(false);
+ if (m_timeT)
+ m_timeT ->setEnabled(false);
+ if (m_bar)
+ m_bar ->setEnabled(false);
+ if (m_beat)
+ m_beat ->setEnabled(false);
+ if (m_fraction)
+ m_fraction ->setEnabled(false);
+ if (m_sec)
+ m_sec ->setEnabled(false);
+ if (m_msec)
+ m_msec ->setEnabled(false);
+ }
+
+ populate();
+}
+
+void
+TimeWidget::populate()
+{
+ // populate everything from m_time and m_startTime
+
+ if (m_note)
+ m_note ->blockSignals(true);
+ if (m_timeT)
+ m_timeT ->blockSignals(true);
+ if (m_bar)
+ m_bar ->blockSignals(true);
+ if (m_beat)
+ m_beat ->blockSignals(true);
+ if (m_fraction)
+ m_fraction ->blockSignals(true);
+ if (m_sec)
+ m_sec ->blockSignals(true);
+ if (m_msec)
+ m_msec ->blockSignals(true);
+
+ if (m_isDuration) {
+
+ if (m_time + m_startTime > m_composition->getEndMarker()) {
+ m_time = m_composition->getEndMarker() - m_startTime;
+ }
+
+ if (m_timeT) {
+ m_timeT->setMinValue(0);
+ if (m_constrain) {
+ m_timeT->setMaxValue(m_composition->getEndMarker() - m_startTime);
+ } else {
+ m_timeT->setMaxValue(INT_MAX);
+ }
+ m_timeT->setValue(m_time);
+ }
+
+ if (m_note) {
+ m_note->setCurrentItem(0);
+ for (size_t i = 0; i < m_noteDurations.size(); ++i) {
+ if (m_time == m_noteDurations[i]) {
+ m_note->setCurrentItem(i);
+ break;
+ }
+ }
+ }
+
+ // the bar/beat etc timings are considered to be times of a note
+ // starting at the start of a bar, in the time signature in effect
+ // at m_startTime
+
+ int bars = 0, beats = 0, hemidemis = 0, remainder = 0;
+ m_composition->getMusicalTimeForDuration(m_startTime, m_time,
+ bars, beats, hemidemis, remainder);
+ TimeSignature timeSig =
+ m_composition->getTimeSignatureAt(m_startTime);
+
+ if (m_bar) {
+ m_bar->setMinValue(0);
+ if (m_constrain) {
+ m_bar->setMaxValue
+ (m_composition->getBarNumber(m_composition->getEndMarker()) -
+ m_composition->getBarNumber(m_startTime));
+ } else {
+ m_bar->setMaxValue(9999);
+ }
+ m_bar->setValue(bars);
+ } else {
+ m_barLabel->setText(QString("%1").arg(bars));
+ }
+
+ if (m_beat) {
+ m_beat->setMinValue(0);
+ m_beat->setMaxValue(timeSig.getBeatsPerBar() - 1);
+ m_beat->setValue(beats);
+ } else {
+ m_beatLabel->setText(QString("%1").arg(beats));
+ }
+
+ if (m_fraction) {
+ m_fraction->setMinValue(0);
+ m_fraction->setMaxValue(timeSig.getBeatDuration() /
+ Note(Note::Shortest).
+ getDuration() - 1);
+ m_fraction->setValue(hemidemis);
+ } else {
+ m_fractionLabel->setText(QString("%1").arg(hemidemis));
+ }
+
+ m_timeSig->setText(i18n("(%1/%2 time)").arg(timeSig.getNumerator()).
+ arg(timeSig.getDenominator()));
+
+ timeT endTime = m_startTime + m_time;
+
+ RealTime rt = m_composition->getRealTimeDifference
+ (m_startTime, endTime);
+
+ if (m_sec) {
+ m_sec->setMinValue(0);
+ if (m_constrain) {
+ m_sec->setMaxValue(m_composition->getRealTimeDifference
+ (m_startTime, m_composition->getEndMarker()).sec);
+ } else {
+ m_sec->setMaxValue(9999);
+ }
+ m_sec->setValue(rt.sec);
+ } else {
+ m_secLabel->setText(QString("%1").arg(rt.sec));
+ }
+
+ if (m_msec) {
+ m_msec->setMinValue(0);
+ m_msec->setMaxValue(999);
+ m_msec->setValue(rt.msec());
+ } else {
+ m_msecLabel->setText(QString("%1").arg(rt.msec()));
+ }
+
+ bool change = (m_composition->getTempoChangeNumberAt(endTime) !=
+ m_composition->getTempoChangeNumberAt(m_startTime));
+
+ //!!! imprecise -- better to work from tempoT directly
+ double tempo = m_composition->getTempoQpm(m_composition->getTempoAtTime(m_startTime));
+
+ int qpmc = int(tempo * 100.0);
+ int bpmc = qpmc;
+ if (timeSig.getBeatDuration()
+ != Note(Note::Crotchet).getDuration()) {
+ bpmc = int(tempo * 100.0 *
+ Note(Note::Crotchet).getDuration() /
+ timeSig.getBeatDuration());
+ }
+ if (change) {
+ if (bpmc != qpmc) {
+ m_tempo->setText(i18n("(starting %1.%2 qpm, %2.%3 bpm)").
+ arg(qpmc / 100).
+ arg(qpmc % 100).
+ arg(bpmc / 100).
+ arg(bpmc % 100));
+ } else {
+ m_tempo->setText(i18n("(starting %1.%2 bpm)").
+ arg(bpmc / 100).
+ arg(bpmc % 100));
+ }
+ } else {
+ if (bpmc != qpmc) {
+ m_tempo->setText(i18n("(%1.%2 qpm, %2.%3 bpm)").
+ arg(qpmc / 100).
+ arg(qpmc % 100).
+ arg(bpmc / 100).
+ arg(bpmc % 100));
+ } else {
+ m_tempo->setText(i18n("(%1.%2 bpm)").
+ arg(bpmc / 100).
+ arg(bpmc % 100));
+ }
+ }
+
+ } else {
+
+ if (m_time > m_composition->getEndMarker()) {
+ m_time = m_composition->getEndMarker();
+ }
+
+ if (m_timeT) {
+ if (m_constrain) {
+ m_timeT->setMinValue(m_composition->getStartMarker());
+ m_timeT->setMaxValue(m_composition->getEndMarker());
+ } else {
+ m_timeT->setMinValue(INT_MIN);
+ m_timeT->setMaxValue(INT_MAX);
+ }
+ m_timeT->setValue(m_time);
+ }
+
+ int bar = 1, beat = 1, hemidemis = 0, remainder = 0;
+ m_composition->getMusicalTimeForAbsoluteTime
+ (m_time, bar, beat, hemidemis, remainder);
+
+ TimeSignature timeSig =
+ m_composition->getTimeSignatureAt(m_time);
+
+ if (m_bar) {
+ m_bar->setMinValue(INT_MIN);
+ if (m_constrain) {
+ m_bar->setMaxValue(m_composition->getBarNumber
+ (m_composition->getEndMarker()));
+ } else {
+ m_bar->setMaxValue(9999);
+ }
+ m_bar->setValue(bar + 1);
+ } else {
+ m_barLabel->setText(QString("%1").arg(bar + 1));
+ }
+
+ if (m_beat) {
+ m_beat->setMinValue(1);
+ m_beat->setMaxValue(timeSig.getBeatsPerBar());
+ m_beat->setValue(beat);
+ } else {
+ m_beatLabel->setText(QString("%1").arg(beat));
+ }
+
+ if (m_fraction) {
+ m_fraction->setMinValue(0);
+ m_fraction->setMaxValue(timeSig.getBeatDuration() /
+ Note(Note::Shortest).
+ getDuration() - 1);
+ m_fraction->setValue(hemidemis);
+ } else {
+ m_fractionLabel->setText(QString("%1").arg(hemidemis));
+ }
+
+ m_timeSig->setText(i18n("(%1/%2 time)").arg(timeSig.getNumerator()).
+ arg(timeSig.getDenominator()));
+
+ RealTime rt = m_composition->getElapsedRealTime(m_time);
+
+ if (m_sec) {
+ m_sec->setMinValue(INT_MIN);
+ if (m_constrain) {
+ m_sec->setMaxValue(m_composition->getElapsedRealTime
+ (m_composition->getEndMarker()).sec);
+ } else {
+ m_sec->setMaxValue(9999);
+ }
+ m_sec->setValue(rt.sec);
+ } else {
+ m_secLabel->setText(QString("%1").arg(rt.sec));
+ }
+
+ if (m_msec) {
+ m_msec->setMinValue(0);
+ m_msec->setMaxValue(999);
+ m_msec->setValue(rt.msec());
+ } else {
+ m_msecLabel->setText(QString("%1").arg(rt.msec()));
+ }
+ }
+
+ if (m_note)
+ m_note ->blockSignals(false);
+ if (m_timeT)
+ m_timeT ->blockSignals(false);
+ if (m_bar)
+ m_bar ->blockSignals(false);
+ if (m_beat)
+ m_beat ->blockSignals(false);
+ if (m_fraction)
+ m_fraction ->blockSignals(false);
+ if (m_sec)
+ m_sec ->blockSignals(false);
+ if (m_msec)
+ m_msec ->blockSignals(false);
+}
+
+timeT
+TimeWidget::getTime()
+{
+ return m_time;
+}
+
+RealTime
+TimeWidget::getRealTime()
+{
+ if (m_isDuration) {
+ return m_composition->getRealTimeDifference(m_startTime,
+ m_startTime + m_time);
+ } else {
+ return m_composition->getElapsedRealTime(m_time);
+ }
+}
+
+void
+TimeWidget::slotSetTime(timeT t)
+{
+ bool change = (m_time != t);
+ if (!change)
+ return ;
+ m_time = t;
+ populate();
+ emit timeChanged(getTime());
+ emit realTimeChanged(getRealTime());
+}
+
+void
+TimeWidget::slotSetRealTime(RealTime rt)
+{
+ if (m_isDuration) {
+ RealTime startRT = m_composition->getElapsedRealTime(m_startTime);
+ if (rt >= RealTime::zeroTime) {
+ slotSetTime(m_composition->getElapsedTimeForRealTime(startRT + rt) -
+ m_startTime);
+ } else {
+ RG_DEBUG << "WARNING: TimeWidget::slotSetRealTime: rt must be >0 for duration widget (was " << rt << ")" << endl;
+ }
+ } else {
+ slotSetTime(m_composition->getElapsedTimeForRealTime(rt));
+ }
+}
+
+void
+TimeWidget::slotResetToDefault()
+{
+ slotSetTime(m_defaultTime);
+}
+
+void
+TimeWidget::slotNoteChanged(int n)
+{
+ if (n > 0) {
+ slotSetTime(m_noteDurations[n]);
+ }
+}
+
+void
+TimeWidget::slotTimeTChanged(int t)
+{
+ RG_DEBUG << "slotTimeTChanged: t is " << t << ", value is " << m_timeT->value() << endl;
+
+ slotSetTime(t);
+}
+
+void
+TimeWidget::slotBarBeatOrFractionChanged(int)
+{
+ int bar = m_bar->value();
+ int beat = m_beat->value();
+ int fraction = m_fraction->value();
+
+ if (m_isDuration) {
+ slotSetTime(m_composition->getDurationForMusicalTime
+ (m_startTime, bar, beat, fraction, 0));
+
+ } else {
+ slotSetTime(m_composition->getAbsoluteTimeForMusicalTime
+ (bar, beat, fraction, 0));
+ }
+}
+
+void
+TimeWidget::slotSecOrMSecChanged(int)
+{
+ int sec = m_sec->value();
+ int msec = m_msec->value();
+
+ slotSetRealTime(RealTime(sec, msec * 1000000));
+}
+
+}
+#include "TimeWidget.moc"
diff --git a/src/gui/widgets/TimeWidget.h b/src/gui/widgets/TimeWidget.h
new file mode 100644
index 0000000..5ffa17a
--- /dev/null
+++ b/src/gui/widgets/TimeWidget.h
@@ -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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_ROSEGARDENTIMEWIDGET_H_
+#define _RG_ROSEGARDENTIMEWIDGET_H_
+
+#include "base/RealTime.h"
+#include <qgroupbox.h>
+#include <qstring.h>
+#include <vector>
+#include "base/Event.h"
+
+
+class QWidget;
+class QSpinBox;
+class QLineEdit;
+class QLabel;
+class QComboBox;
+
+
+namespace Rosegarden
+{
+
+class Composition;
+
+
+class TimeWidget : public QGroupBox
+{
+ Q_OBJECT
+public:
+ /**
+ * Constructor for absolute time widget
+ */
+ TimeWidget(QString title,
+ QWidget *parent,
+ Composition *composition, // for bar/beat/msec
+ timeT initialTime,
+ bool editable = true,
+ bool constrainToCompositionDuration = true);
+
+ /**
+ * Constructor for duration widget. startTime is the absolute time
+ * at which this duration begins, necessary so that we can show the
+ * correct real-time (based on tempo at startTime) etc.
+ */
+ TimeWidget(QString title,
+ QWidget *parent,
+ Composition *composition, // for bar/beat/msec
+ timeT startTime,
+ timeT initialDuration,
+ bool editable = true,
+ bool constrainToCompositionDuration = true);
+
+ timeT getTime();
+ RealTime getRealTime();
+
+signals:
+ void timeChanged(timeT);
+ void realTimeChanged(RealTime);
+
+public slots:
+ void slotSetTime(timeT);
+ void slotSetRealTime(RealTime);
+ void slotResetToDefault();
+
+ void slotNoteChanged(int);
+ void slotTimeTChanged(int);
+ void slotBarBeatOrFractionChanged(int);
+ void slotSecOrMSecChanged(int);
+
+private:
+ Composition *m_composition;
+ bool m_isDuration;
+ bool m_constrain;
+ timeT m_time;
+ timeT m_startTime;
+ timeT m_defaultTime;
+
+ QComboBox *m_note;
+ QSpinBox *m_timeT;
+ QSpinBox *m_bar;
+ QSpinBox *m_beat;
+ QSpinBox *m_fraction;
+ QLineEdit *m_barLabel;
+ QLineEdit *m_beatLabel;
+ QLineEdit *m_fractionLabel;
+ QLabel *m_timeSig;
+ QSpinBox *m_sec;
+ QSpinBox *m_msec;
+ QLineEdit *m_secLabel;
+ QLineEdit *m_msecLabel;
+ QLabel *m_tempo;
+
+ void init(bool editable);
+ void populate();
+
+ std::vector<timeT> m_noteDurations;
+};
+
+
+}
+
+#endif
diff --git a/src/gui/widgets/TristateCheckBox.cpp b/src/gui/widgets/TristateCheckBox.cpp
new file mode 100644
index 0000000..89ade5d
--- /dev/null
+++ b/src/gui/widgets/TristateCheckBox.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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "TristateCheckBox.h"
+
+#include <qcheckbox.h>
+#include <qwidget.h>
+
+
+namespace Rosegarden
+{
+
+void
+TristateCheckBox::mouseReleaseEvent(QMouseEvent *)
+{}
+
+TristateCheckBox::~TristateCheckBox()
+{}
+
+}
+#include "TristateCheckBox.moc"
diff --git a/src/gui/widgets/TristateCheckBox.h b/src/gui/widgets/TristateCheckBox.h
new file mode 100644
index 0000000..699252a
--- /dev/null
+++ b/src/gui/widgets/TristateCheckBox.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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_ROSEGARDENTRISTATECHECKBOX_H_
+#define _RG_ROSEGARDENTRISTATECHECKBOX_H_
+
+#include <qcheckbox.h>
+
+
+class QWidget;
+class QMouseEvent;
+
+
+namespace Rosegarden
+{
+
+
+
+/** Create out own check box which is always Tristate
+ * and allows us to click only between on and off
+ * and only to _show_ the third ("Some") state
+ */
+class TristateCheckBox : public QCheckBox
+{
+Q_OBJECT
+public:
+ TristateCheckBox(QWidget *parent=0,
+ const char *name=0):QCheckBox(parent, name)
+ { setTristate(true) ;}
+
+ virtual ~TristateCheckBox();
+
+protected:
+ // don't emit when the button is released
+ virtual void mouseReleaseEvent(QMouseEvent *);
+
+private:
+};
+
+
+// A label that emits a double click signal and provides scroll wheel information.
+//
+//
+
+}
+
+#endif
diff --git a/src/gui/widgets/VUMeter.cpp b/src/gui/widgets/VUMeter.cpp
new file mode 100644
index 0000000..ae9fe84
--- /dev/null
+++ b/src/gui/widgets/VUMeter.cpp
@@ -0,0 +1,694 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
+
+/*
+ Rosegarden
+ A MIDI and audio sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "VUMeter.h"
+
+#include "misc/Debug.h"
+#include "base/AudioLevel.h"
+#include "gui/general/GUIPalette.h"
+#include "gui/rulers/VelocityColour.h"
+#include <qbrush.h>
+#include <qcolor.h>
+#include <qlabel.h>
+#include <qpainter.h>
+#include <qtimer.h>
+#include <qwidget.h>
+
+
+namespace Rosegarden
+{
+
+VUMeter::VUMeter(QWidget *parent,
+ VUMeterType type,
+ bool stereo,
+ bool hasRecord,
+ int width,
+ int height,
+ VUAlignment alignment,
+ const char *name):
+ QLabel(parent, name),
+ m_originalHeight(height),
+ m_active(true),
+ m_type(type),
+ m_alignment(alignment),
+ m_levelLeft(0),
+ m_recordLevelLeft(0),
+ m_peakLevelLeft(0),
+ m_levelStepLeft(m_baseLevelStep),
+ m_recordLevelStepLeft(m_baseLevelStep),
+ m_fallTimerLeft(0),
+ m_peakTimerLeft(0),
+ m_levelRight(0),
+ m_recordLevelRight(0),
+ m_peakLevelRight(0),
+ m_levelStepRight(0),
+ m_recordLevelStepRight(0),
+ m_fallTimerRight(0),
+ m_peakTimerRight(0),
+ m_showPeakLevel(true),
+ m_baseLevelStep(3),
+ m_stereo(stereo),
+ m_hasRecord(hasRecord)
+{
+ // Work out if we need peak hold first
+ //
+ switch (m_type) {
+ case PeakHold:
+ case AudioPeakHoldShort:
+ case AudioPeakHoldLong:
+ case AudioPeakHoldIEC:
+ case AudioPeakHoldIECLong:
+ case FixedHeightVisiblePeakHold:
+ m_showPeakLevel = true;
+ break;
+
+ default:
+ case Plain:
+ m_showPeakLevel = false;
+ break;
+ }
+
+ // Always init the left fall timer
+ //
+ m_fallTimerLeft = new QTimer();
+
+ connect(m_fallTimerLeft, SIGNAL(timeout()),
+ this, SLOT(slotReduceLevelLeft()));
+
+ if (m_showPeakLevel) {
+ m_peakTimerLeft = new QTimer();
+
+ connect(m_peakTimerLeft, SIGNAL(timeout()),
+ this, SLOT(slotStopShowingPeakLeft()));
+ }
+
+ if (stereo) {
+ m_fallTimerRight = new QTimer();
+
+ connect(m_fallTimerRight, SIGNAL(timeout()),
+ this, SLOT(slotReduceLevelRight()));
+
+ if (m_showPeakLevel) {
+ m_peakTimerRight = new QTimer();
+ connect(m_peakTimerRight, SIGNAL(timeout()),
+ this, SLOT(slotStopShowingPeakRight()));
+ }
+
+ }
+
+ setMinimumSize(width, m_originalHeight);
+ setMaximumSize(width, m_originalHeight);
+
+ if (m_alignment == Vertical)
+ m_maxLevel = height;
+ else
+ m_maxLevel = width;
+
+ int max = m_maxLevel;
+ int red, orange, green;
+
+ if (m_type == AudioPeakHoldShort) {
+ red = AudioLevel::dB_to_fader( 0.0, max, AudioLevel::ShortFader);
+ orange = AudioLevel::dB_to_fader( -2.0, max, AudioLevel::ShortFader);
+ green = AudioLevel::dB_to_fader( -10.0, max, AudioLevel::ShortFader);
+ m_background = QColor(50, 50, 50);
+ } else if (m_type == AudioPeakHoldLong) {
+ red = AudioLevel::dB_to_fader( 0.0, max, AudioLevel::LongFader);
+ orange = AudioLevel::dB_to_fader( -2.0, max, AudioLevel::LongFader);
+ green = AudioLevel::dB_to_fader( -10.0, max, AudioLevel::LongFader);
+ m_background = QColor(50, 50, 50);
+ } else if (m_type == AudioPeakHoldIEC) {
+ red = AudioLevel::dB_to_fader( -0.1, max, AudioLevel::IEC268Meter);
+ orange = AudioLevel::dB_to_fader( -6.0, max, AudioLevel::IEC268Meter);
+ green = AudioLevel::dB_to_fader( -10.0, max, AudioLevel::IEC268Meter);
+ m_background = QColor(50, 50, 50);
+ } else if (m_type == AudioPeakHoldIECLong) {
+ red = AudioLevel::dB_to_fader( 0.0, max, AudioLevel::IEC268LongMeter);
+ orange = AudioLevel::dB_to_fader( -6.0, max, AudioLevel::IEC268LongMeter);
+ green = AudioLevel::dB_to_fader( -10.0, max, AudioLevel::IEC268LongMeter);
+ m_background = QColor(50, 50, 50);
+ } else {
+ red = max * 92 / 100;
+ orange = max * 60 / 100;
+ green = max * 10 / 100;
+ m_background = Qt::black;
+ }
+
+ if (m_type == AudioPeakHoldLong ||
+ m_type == AudioPeakHoldShort ||
+ m_type == AudioPeakHoldIEC ||
+ m_type == AudioPeakHoldIECLong) {
+ m_velocityColour =
+ new VelocityColour(GUIPalette::getColour(GUIPalette::LevelMeterSolidRed),
+ GUIPalette::getColour(GUIPalette::LevelMeterSolidOrange),
+ GUIPalette::getColour(GUIPalette::LevelMeterSolidGreen),
+ max, red, orange, green);
+ } else {
+ m_velocityColour =
+ new VelocityColour(GUIPalette::getColour(GUIPalette::LevelMeterRed),
+ GUIPalette::getColour(GUIPalette::LevelMeterOrange),
+ GUIPalette::getColour(GUIPalette::LevelMeterGreen),
+ max, red, orange, green);
+ }
+}
+
+VUMeter::~VUMeter()
+{
+ delete m_velocityColour;
+ delete m_peakTimerRight;
+ delete m_peakTimerLeft;
+ delete m_fallTimerRight;
+ delete m_fallTimerLeft;
+}
+
+void
+VUMeter::setLevel(double level)
+{
+ setLevel(level, level, false);
+}
+
+void
+VUMeter::setLevel(double leftLevel, double rightLevel)
+{
+ setLevel(leftLevel, rightLevel, false);
+}
+
+void
+VUMeter::setRecordLevel(double level)
+{
+ setLevel(level, level, true);
+}
+
+void
+VUMeter::setRecordLevel(double leftLevel, double rightLevel)
+{
+ setLevel(leftLevel, rightLevel, true);
+}
+
+void
+VUMeter::setLevel(double leftLevel, double rightLevel, bool record)
+{
+ if (!isVisible())
+ return ;
+
+ // RG_DEBUG << "setLevel(" << (void *)this << "): record=" << record << ", leftLevel=" << leftLevel << ", hasRecord=" << m_hasRecord << endl;
+
+ if (record && !m_hasRecord)
+ return ;
+
+ short &ll = (record ? m_recordLevelLeft : m_levelLeft);
+ short &lr = (record ? m_recordLevelRight : m_levelRight);
+
+ switch (m_type) {
+
+ case AudioPeakHoldShort:
+ ll = AudioLevel::dB_to_fader
+ (leftLevel, m_maxLevel, AudioLevel::ShortFader);
+ lr = AudioLevel::dB_to_fader
+ (rightLevel, m_maxLevel, AudioLevel::ShortFader);
+ break;
+
+ case AudioPeakHoldLong:
+ ll = AudioLevel::dB_to_fader
+ (leftLevel, m_maxLevel, AudioLevel::LongFader);
+ lr = AudioLevel::dB_to_fader
+ (rightLevel, m_maxLevel, AudioLevel::LongFader);
+ break;
+
+ case AudioPeakHoldIEC:
+ ll = AudioLevel::dB_to_fader
+ (leftLevel, m_maxLevel, AudioLevel::IEC268Meter);
+ lr = AudioLevel::dB_to_fader
+ (rightLevel, m_maxLevel, AudioLevel::IEC268Meter);
+ break;
+
+ case AudioPeakHoldIECLong:
+ ll = AudioLevel::dB_to_fader
+ (leftLevel, m_maxLevel, AudioLevel::IEC268LongMeter);
+ lr = AudioLevel::dB_to_fader
+ (rightLevel, m_maxLevel, AudioLevel::IEC268LongMeter);
+ break;
+
+ default:
+ ll = (int)(double(m_maxLevel) * leftLevel);
+ lr = (int)(double(m_maxLevel) * rightLevel);
+ };
+
+ if (ll < 0)
+ ll = 0;
+ if (ll > m_maxLevel)
+ ll = m_maxLevel;
+ if (lr < 0)
+ lr = 0;
+ if (lr > m_maxLevel)
+ lr = m_maxLevel;
+
+ if (record) {
+ m_recordLevelStepLeft = m_baseLevelStep;
+ m_recordLevelStepRight = m_baseLevelStep;
+ } else {
+ m_levelStepLeft = m_baseLevelStep;
+ m_levelStepRight = m_baseLevelStep;
+ }
+
+ // Only start the timer when we need it
+ if (ll > 0) {
+ if (m_fallTimerLeft->isActive() == false) {
+ m_fallTimerLeft->start(40); // 40 ms per level fall iteration
+ meterStart();
+ }
+ }
+
+ if (lr > 0) {
+ if (m_fallTimerRight && m_fallTimerRight->isActive() == false) {
+ m_fallTimerRight->start(40); // 40 ms per level fall iteration
+ meterStart();
+ }
+ }
+
+ if (!record) {
+
+ // Reset level and reset timer if we're exceeding the
+ // current peak
+ //
+ if (ll >= m_peakLevelLeft && m_showPeakLevel) {
+ m_peakLevelLeft = ll;
+
+ if (m_peakTimerLeft->isActive())
+ m_peakTimerLeft->stop();
+
+ m_peakTimerLeft->start(1000); // milliseconds of peak hold
+ }
+
+ if (lr >= m_peakLevelRight && m_showPeakLevel) {
+ m_peakLevelRight = lr;
+
+ if (m_peakTimerRight) {
+ if (m_peakTimerRight->isActive())
+ m_peakTimerRight->stop();
+
+ m_peakTimerRight->start(1000); // milliseconds of peak hold
+ }
+ }
+ }
+
+ if (m_active) {
+ QPainter paint(this);
+ drawMeterLevel(&paint);
+ }
+}
+
+void
+VUMeter::paintEvent(QPaintEvent *e)
+{
+ // RG_DEBUG << "VUMeter::paintEvent - height = " << height() << endl;
+ QPainter paint(this);
+
+ if (m_type == VUMeter::AudioPeakHoldShort ||
+ m_type == VUMeter::AudioPeakHoldLong ||
+ m_type == VUMeter::AudioPeakHoldIEC ||
+ m_type == VUMeter::AudioPeakHoldIECLong) {
+ paint.setPen(m_background);
+ paint.setBrush(m_background);
+ paint.drawRect(0, 0, width(), height());
+
+ drawMeterLevel(&paint);
+
+ paint.setPen(colorGroup().background());
+ paint.drawPoint(0, 0);
+ paint.drawPoint(width() - 1, 0);
+ paint.drawPoint(0, height() - 1);
+ paint.drawPoint(width() - 1, height() - 1);
+ } else if (m_type == VUMeter::FixedHeightVisiblePeakHold) {
+ paint.setPen(m_background);
+ paint.setBrush(m_background);
+ paint.drawRect(0, 0, width(), height());
+
+ if (m_fallTimerLeft->isActive())
+ drawMeterLevel(&paint);
+ else {
+ meterStop();
+ drawFrame(&paint);
+ drawContents(&paint);
+ }
+ } else {
+ if (m_fallTimerLeft->isActive()) {
+ paint.setPen(m_background);
+ paint.setBrush(m_background);
+ paint.drawRect(0, 0, width(), height());
+ drawMeterLevel(&paint);
+ } else {
+ meterStop();
+ drawFrame(&paint);
+ drawContents(&paint);
+ }
+ }
+}
+
+void
+VUMeter::drawColouredBar(QPainter *paint, int channel,
+ int x, int y, int w, int h)
+{
+ if (m_type == AudioPeakHoldLong ||
+ m_type == AudioPeakHoldShort ||
+ m_type == AudioPeakHoldIEC ||
+ m_type == AudioPeakHoldIECLong) {
+
+ Qt::BrushStyle style = Qt::SolidPattern;
+
+ int medium = m_velocityColour->getMediumKnee(),
+ loud = m_velocityColour->getLoudKnee();
+
+ if (m_alignment == Vertical) {
+ if (h > loud) {
+ paint->setPen(m_velocityColour->getLoudColour());
+ paint->setBrush(QBrush(m_velocityColour->getLoudColour(),
+ style));
+ paint->drawRect(x, y, w, h - loud);
+ }
+ } else {
+ if (w > loud) {
+ paint->setPen(m_velocityColour->getLoudColour());
+ paint->setBrush(QBrush(m_velocityColour->getLoudColour(),
+ style));
+ paint->drawRect(x + loud, y, w - loud, h);
+ }
+ }
+
+ if (m_alignment == Vertical) {
+ if (h > medium) {
+ paint->setPen(m_velocityColour->getMediumColour());
+ paint->setBrush(QBrush(m_velocityColour->getMediumColour(),
+ style));
+ paint->drawRect(x, y + (h > loud ? (h - loud) : 0),
+ w, std::min(h - medium, loud - medium));
+ }
+ } else {
+ if (w > medium) {
+ paint->setPen(m_velocityColour->getMediumColour());
+ paint->setBrush(QBrush(m_velocityColour->getMediumColour(),
+ style));
+ paint->drawRect(x + medium, y,
+ std::min(w - medium, loud - medium), h);
+ }
+ }
+
+ if (m_alignment == Vertical) {
+ paint->setPen(m_velocityColour->getQuietColour());
+ paint->setBrush(QBrush(m_velocityColour->getQuietColour(),
+ style));
+ paint->drawRect(x, y + (h > medium ? (h - medium) : 0),
+ w, std::min(h, medium));
+ } else {
+ paint->setPen(m_velocityColour->getQuietColour());
+ paint->setBrush(QBrush(m_velocityColour->getQuietColour(),
+ style));
+ paint->drawRect(x, y, std::min(w, medium), h);
+ }
+
+ } else {
+
+ if (channel == 0) {
+
+ QColor mixedColour = m_velocityColour->getColour(m_levelLeft);
+
+ paint->setPen(mixedColour);
+ paint->setBrush(mixedColour);
+
+ } else {
+
+ QColor mixedColour = m_velocityColour->getColour(m_levelRight);
+
+ paint->setPen(mixedColour);
+ paint->setBrush(mixedColour);
+ }
+
+ // RG_DEBUG << "VUMeter::drawColouredBar - level = " << m_levelLeft << endl;
+
+ paint->drawRect(x, y, w, h);
+ }
+}
+
+void
+VUMeter::drawMeterLevel(QPainter* paint)
+{
+ int medium = m_velocityColour->getMediumKnee(),
+ loud = m_velocityColour->getLoudKnee();
+
+ if (m_stereo) {
+ if (m_alignment == VUMeter::Vertical) {
+ int hW = width() / 2;
+
+ int midWidth = 1;
+ if (m_hasRecord) {
+ if (width() > 10) {
+ midWidth = 2;
+ }
+ }
+
+ // Draw the left bar
+ //
+ int y = height() - (m_levelLeft * height()) / m_maxLevel;
+ int ry = height() - (m_recordLevelLeft * height()) / m_maxLevel;
+
+ drawColouredBar(paint, 0, 0, y, hW - midWidth, height() - y);
+
+ if (m_hasRecord) {
+ drawColouredBar(paint, 0, hW - midWidth, ry, midWidth + 1, height() - ry);
+ }
+
+ paint->setPen(m_background);
+ paint->setBrush(m_background);
+ paint->drawRect(0, 0, hW - midWidth, y);
+
+ if (m_hasRecord) {
+ paint->drawRect(hW - midWidth, 0, midWidth + 1, ry);
+ }
+
+ if (m_showPeakLevel) {
+ int h = (m_peakLevelLeft * height()) / m_maxLevel;
+ y = height() - h;
+
+ if (h > loud) {
+ paint->setPen(Qt::red); // brighter than the red meter bar
+ paint->drawLine(0, y - 1, hW - midWidth - 1, y - 1);
+ paint->drawLine(0, y + 1, hW - midWidth - 1, y + 1);
+ }
+
+ paint->setPen(Qt::white);
+ paint->drawLine(0, y, hW - midWidth - 1, y);
+ }
+
+ // Draw the right bar
+ //
+ y = height() - (m_levelRight * height()) / m_maxLevel;
+ ry = height() - (m_recordLevelRight * height()) / m_maxLevel;
+ drawColouredBar(paint, 1, hW + midWidth, y, hW - midWidth, height() - y);
+
+ if (m_hasRecord) {
+ drawColouredBar(paint, 1, hW, ry, midWidth + 1, height() - ry);
+ }
+
+ paint->setPen(m_background);
+ paint->setBrush(m_background);
+ paint->drawRect(hW + midWidth, 0, hW - midWidth + 1, y);
+
+ if (m_hasRecord) {
+ paint->drawRect(hW, 0, midWidth, ry);
+ }
+
+ if (m_showPeakLevel) {
+ int h = (m_peakLevelRight * height()) / m_maxLevel;
+ y = height() - h;
+
+ if (h > loud) {
+ paint->setPen(Qt::red); // brighter than the red meter bar
+ paint->drawLine(hW + midWidth, y - 1, width(), y - 1);
+ paint->drawLine(hW + midWidth, y + 1, width(), y + 1);
+ }
+
+ paint->setPen(Qt::white);
+ paint->setBrush(Qt::white);
+
+ paint->drawLine(hW + midWidth, y, width(), y);
+ }
+ } else // horizontal
+ {
+ paint->setPen(m_background);
+ paint->setBrush(m_background);
+ paint->drawRect(0, 0, width(), height());
+
+ int x = (m_levelLeft * width()) / m_maxLevel;
+ if (x > 0)
+ paint->drawRect(0, 0, x, height());
+
+ if (m_showPeakLevel) {
+ paint->setPen(Qt::white);
+ paint->setBrush(Qt::white);
+
+ // show peak level
+ x = m_peakLevelLeft * width() / m_maxLevel;
+ if (x < (width() - 1))
+ x++;
+ else
+ x = width() - 1;
+
+ paint->drawLine(x, 0, x, height());
+ }
+ }
+ } else {
+ // Paint a vertical meter according to type
+ //
+ if (m_alignment == VUMeter::Vertical) {
+ int y = height() - (m_levelLeft * height()) / m_maxLevel;
+ drawColouredBar(paint, 0, 0, y, width(), height());
+
+ paint->setPen(m_background);
+ paint->setBrush(m_background);
+ paint->drawRect(0, 0, width(), y);
+
+ /*
+ RG_DEBUG << "VUMeter::drawMeterLevel - height = " << height()
+ << ", vertical rect height = " << y << endl;
+ */
+
+ if (m_showPeakLevel) {
+ paint->setPen(Qt::white);
+ paint->setBrush(Qt::white);
+
+ y = height() - (m_peakLevelLeft * height()) / m_maxLevel;
+
+ paint->drawLine(0, y, width(), y);
+ }
+ } else {
+ int x = (m_levelLeft * width()) / m_maxLevel;
+ if (x > 0)
+ drawColouredBar(paint, 0, 0, 0, x, height());
+
+ paint->setPen(m_background);
+ paint->setBrush(m_background);
+ paint->drawRect(x, 0, width() - x, height());
+
+ if (m_showPeakLevel) {
+ paint->setPen(Qt::white);
+ paint->setBrush(Qt::white);
+
+ // show peak level
+ x = (m_peakLevelLeft * width()) / m_maxLevel;
+ if (x < (width() - 1))
+ x++;
+ else
+ x = width() - 1;
+
+ paint->drawLine(x, 0, x, height());
+ }
+ }
+ }
+}
+
+void
+VUMeter::slotReduceLevelRight()
+{
+ m_levelStepRight = int(m_levelRight) * m_baseLevelStep / 100 + 1;
+ if (m_levelStepRight < 1)
+ m_levelStepRight = 1;
+
+ m_recordLevelStepRight = int(m_recordLevelRight) * m_baseLevelStep / 100 + 1;
+ if (m_recordLevelStepRight < 1)
+ m_recordLevelStepRight = 1;
+
+ if (m_levelRight > 0)
+ m_levelRight -= m_levelStepRight;
+ if (m_recordLevelRight > 0)
+ m_recordLevelRight -= m_recordLevelStepRight;
+
+ if (m_levelRight <= 0) {
+ m_levelRight = 0;
+ m_peakLevelRight = 0;
+ }
+
+ if (m_recordLevelRight <= 0)
+ m_recordLevelRight = 0;
+
+ if (m_levelRight == 0 && m_recordLevelRight == 0) {
+ // Always stop the timer when we don't need it
+ if (m_fallTimerRight)
+ m_fallTimerRight->stop();
+ meterStop();
+ }
+
+ QPainter paint(this);
+ drawMeterLevel(&paint);
+}
+
+void
+VUMeter::slotReduceLevelLeft()
+{
+ m_levelStepLeft = int(m_levelLeft) * m_baseLevelStep / 100 + 1;
+ if (m_levelStepLeft < 1)
+ m_levelStepLeft = 1;
+
+ m_recordLevelStepLeft = int(m_recordLevelLeft) * m_baseLevelStep / 100 + 1;
+ if (m_recordLevelStepLeft < 1)
+ m_recordLevelStepLeft = 1;
+
+ if (m_levelLeft > 0)
+ m_levelLeft -= m_levelStepLeft;
+ if (m_recordLevelLeft > 0)
+ m_recordLevelLeft -= m_recordLevelStepLeft;
+
+ if (m_levelLeft <= 0) {
+ m_levelLeft = 0;
+ m_peakLevelLeft = 0;
+ }
+
+ if (m_recordLevelLeft <= 0)
+ m_recordLevelLeft = 0;
+
+ if (m_levelLeft == 0 && m_recordLevelLeft == 0) {
+ // Always stop the timer when we don't need it
+ if (m_fallTimerLeft)
+ m_fallTimerLeft->stop();
+ meterStop();
+ }
+
+ QPainter paint(this);
+ drawMeterLevel(&paint);
+}
+
+void
+VUMeter::slotStopShowingPeakRight()
+{
+ m_peakLevelRight = 0;
+}
+
+void
+VUMeter::slotStopShowingPeakLeft()
+{
+ m_peakLevelLeft = 0;
+}
+
+}
+#include "VUMeter.moc"
diff --git a/src/gui/widgets/VUMeter.h b/src/gui/widgets/VUMeter.h
new file mode 100644
index 0000000..0a06dfd
--- /dev/null
+++ b/src/gui/widgets/VUMeter.h
@@ -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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_VUMETER_H_
+#define _RG_VUMETER_H_
+
+#include <qcolor.h>
+#include <qlabel.h>
+
+
+class QWidget;
+class QTimer;
+class QPaintEvent;
+class QPainter;
+
+
+namespace Rosegarden
+{
+
+class VelocityColour;
+
+
+class VUMeter : public QLabel
+{
+Q_OBJECT
+
+public:
+ typedef enum
+ {
+ Plain,
+ PeakHold,
+ AudioPeakHoldShort,
+ AudioPeakHoldLong,
+ AudioPeakHoldIEC,
+ AudioPeakHoldIECLong,
+ FixedHeightVisiblePeakHold
+ } VUMeterType;
+
+ typedef enum
+ {
+ Horizontal,
+ Vertical
+ } VUAlignment;
+
+ // Mono and stereo level setting. The AudioPeakHold meter types
+ // expect levels in dB; other types expect levels between 0 and 1.
+ //
+ void setLevel(double level);
+ void setLevel(double leftLevel, double rightLevel);
+
+ // Mono and stereo record level setting. Same units. Only
+ // applicable if hasRecord true in constructor.
+ //
+ void setRecordLevel(double level);
+ void setRecordLevel(double leftLevel, double rightLevel);
+
+ virtual void paintEvent(QPaintEvent*);
+
+protected:
+ // Constructor is protected - we can only create an object
+ // from a sub-class of this type from a sub-class.
+ //
+ VUMeter(QWidget *parent = 0,
+ VUMeterType type = Plain,
+ bool stereo = false,
+ bool hasRecord = false,
+ int width = 0,
+ int height = 0,
+ VUAlignment alignment = Horizontal,
+ const char *name = 0);
+ ~VUMeter();
+
+ virtual void meterStart() = 0;
+ virtual void meterStop() = 0;
+
+ int m_originalHeight;
+ bool m_active;
+
+ void setLevel(double leftLevel, double rightLevel, bool record);
+
+private slots:
+ void slotReduceLevelLeft();
+ void slotStopShowingPeakLeft();
+
+ void slotReduceLevelRight();
+ void slotStopShowingPeakRight();
+
+private:
+
+ void drawMeterLevel(QPainter *paint);
+ void drawColouredBar(QPainter *paint, int channel,
+ int x, int y, int w, int h);
+
+ VUMeterType m_type;
+ VUAlignment m_alignment;
+ QColor m_background;
+
+ short m_maxLevel;
+
+ short m_levelLeft;
+ short m_recordLevelLeft;
+ short m_peakLevelLeft;
+ short m_levelStepLeft;
+ short m_recordLevelStepLeft;
+ QTimer *m_fallTimerLeft;
+ QTimer *m_peakTimerLeft;
+
+ short m_levelRight;
+ short m_recordLevelRight;
+ short m_peakLevelRight;
+ short m_levelStepRight;
+ short m_recordLevelStepRight;
+ QTimer *m_fallTimerRight;
+ QTimer *m_peakTimerRight;
+
+ bool m_showPeakLevel;
+ short m_baseLevelStep;
+
+ bool m_stereo;
+ bool m_hasRecord;
+
+ // We use this to work out our colours
+ //
+ VelocityColour *m_velocityColour;
+
+
+};
+
+
+}
+
+#endif
diff --git a/src/gui/widgets/WheelyButton.cpp b/src/gui/widgets/WheelyButton.cpp
new file mode 100644
index 0000000..e63cb3b
--- /dev/null
+++ b/src/gui/widgets/WheelyButton.cpp
@@ -0,0 +1,35 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
+
+/*
+ Rosegarden
+ A MIDI and audio sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "WheelyButton.h"
+
+#include <qpushbutton.h>
+#include <qwidget.h>
+
+
+namespace Rosegarden
+{
+}
+#include "WheelyButton.moc"
diff --git a/src/gui/widgets/WheelyButton.h b/src/gui/widgets/WheelyButton.h
new file mode 100644
index 0000000..83de80d
--- /dev/null
+++ b/src/gui/widgets/WheelyButton.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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_WHEELYBUTTON_H_
+#define _RG_WHEELYBUTTON_H_
+
+#include <qpushbutton.h>
+
+
+class QWidget;
+class QWheelEvent;
+
+
+namespace Rosegarden
+{
+
+
+
+class WheelyButton : public QPushButton
+{
+ Q_OBJECT
+
+public:
+ WheelyButton(QWidget *w) : QPushButton(w) { }
+ virtual ~WheelyButton() { }
+
+signals:
+ void wheel(bool up);
+
+protected:
+ void wheelEvent(QWheelEvent *e) {
+ emit wheel(e->delta() > 0);
+ }
+};
+
+
+
+// A specialised menu for selecting audio inputs or outputs, that
+// queries the studio and instrument to find out what it should show.
+// Available in a "compact" size, which is a push button with a popup
+// menu attached, or a regular size which is a combobox.
+//
+
+}
+
+#endif
diff --git a/src/gui/widgets/ZoomSlider.cpp b/src/gui/widgets/ZoomSlider.cpp
new file mode 100644
index 0000000..1986635
--- /dev/null
+++ b/src/gui/widgets/ZoomSlider.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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "ZoomSlider.h"
+
+#include <qslider.h>
+#include <qwidget.h>
+
+
+namespace Rosegarden
+{
+}
diff --git a/src/gui/widgets/ZoomSlider.h b/src/gui/widgets/ZoomSlider.h
new file mode 100644
index 0000000..635d074
--- /dev/null
+++ b/src/gui/widgets/ZoomSlider.h
@@ -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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _RG_ZOOMSLIDER_H_
+#define _RG_ZOOMSLIDER_H_
+
+#include <qslider.h>
+#include <vector>
+
+
+class T;
+class QWidget;
+
+
+namespace Rosegarden
+{
+
+
+
+template <class T>
+class ZoomSlider : public QSlider
+{
+public:
+
+ /**
+ * Construct a ZoomSlider offering a selection from the
+ * given set of sizes.
+ *
+ * A ZoomSlider is a not-very-well-named slider widget that
+ * offers the user an integral range of values (say, 1..3)
+ * but maps those values internally onto a list of "sizes",
+ * which may be any values of any type (for example the
+ * strings "small", "medium" or "large" or the doubles 1.0,
+ * 1.2 and 1.5). It may be useful where a GUI wants to
+ * offer a fairly limited range of sizes or options that
+ * may actually be arbitrary values chosen because they
+ * work well for some internal reason but that should appear
+ * to the user as a nice continuous range.
+ */
+ ZoomSlider(const std::vector<T> &sizes, T defaultValue,
+ Orientation, QWidget * parent, const char * name=0);
+
+ virtual ~ZoomSlider();
+
+ void reinitialise(const std::vector<T> &sizes, T defaultValue);
+
+ const T &getCurrentSize() const;
+ const T &getDefault() const;
+
+public slots:
+ void setToDefault(); // restore the initial value
+ void setSize(T size);
+ void increment();
+ void decrement();
+
+protected:
+ static int getIndex(const std::vector<T> &, T size);
+ std::vector<T> m_sizes;
+ T m_defaultValue;
+};
+
+
+template<class T>
+ZoomSlider<T>::ZoomSlider(const std::vector<T> &sizes,
+ T initialSize, Orientation o,
+ QWidget *parent, const char *name) :
+ QSlider(0, sizes.size()-1, 1,
+ getIndex(sizes, initialSize), o, parent, name),
+ m_sizes(sizes),
+ m_defaultValue(initialSize)
+{
+ setTracking(false);
+ setFixedWidth(150);
+ setFixedHeight(15);
+ setLineStep(1);
+ setTickmarks(Below);
+}
+
+template<class T>
+ZoomSlider<T>::~ZoomSlider() { }
+
+template<class T>
+int
+ZoomSlider<T>::getIndex(const std::vector<T> &sizes, T size)
+{
+ for (unsigned int i = 0; i < sizes.size(); ++i) {
+ if (sizes[i] == size) return i;
+ }
+ return sizes.size()/2;
+}
+
+template<class T>
+void
+ZoomSlider<T>::reinitialise(const std::vector<T> &sizes, T size)
+{
+ m_sizes = sizes;
+ setMinValue(0);
+ setMaxValue(sizes.size()-1);
+ setValue(getIndex(sizes, size));
+ setLineStep(1);
+ setTickmarks(Below);
+}
+
+template<class T>
+void
+ZoomSlider<T>::setToDefault()
+{
+ setValue(getIndex(m_sizes, m_defaultValue));
+}
+
+template <class T>
+const T &
+ZoomSlider<T>::getCurrentSize() const
+{
+ return m_sizes[value()];
+}
+
+template <class T>
+void
+ZoomSlider<T>::setSize(T size)
+{
+ setValue(getIndex(m_sizes, size));
+}
+
+template <class T>
+void
+ZoomSlider<T>::increment()
+{
+ if (value() + 1 >= m_sizes.size()) return;
+ setValue(value() + 1);
+}
+
+template <class T>
+void
+ZoomSlider<T>::decrement()
+{
+ if (value() <= 0) return;
+ setValue(value() - 1);
+}
+
+template <class T>
+const T &
+ZoomSlider<T>::getDefault() const
+{
+ return m_defaultValue;
+}
+
+
+
+
+}
+
+#endif
diff --git a/src/helpers/rosegarden-audiofile-importer b/src/helpers/rosegarden-audiofile-importer
new file mode 100755
index 0000000..8cbaa45
--- /dev/null
+++ b/src/helpers/rosegarden-audiofile-importer
@@ -0,0 +1,270 @@
+#!/bin/bash
+#
+# Helper application to convert and import audio files for Rosegarden.
+# Copyright 2005-2008 Chris Cannam. Distributed under the GNU General
+# Public License.
+#
+# Can take audio files of various kinds as input, always produces WAV
+# files that are compatible with Rosegarden as output.
+#
+# Not actually specific to Rosegarden in any way, except that
+# Rosegarden needs to know it can rely on its presence and calling
+# interface.
+#
+# Usage:
+#
+# rosegarden-audiofile-importer -t [<version>]
+# rosegarden-audiofile-importer --conftest [<version>]
+# -- Exit successfully if the importer is available and working
+# [and is of at least version <version>]. If some of the
+# required helper applications are missing, also print to
+# stdout a line saying "Required: application1, application2",
+# listing the missing programs.
+#
+# rosegarden-audiofile-importer -l [<version>]
+# -- List known file extensions (e.g. ogg wav flac). Return code
+# is same as for -t (so can combine two calls into one)
+#
+# rosegarden-audiofile-importer [-r <rate>] -w <inputfile>
+# -- Test whether any work is needed to convert <inputfile>, either
+# because it isn't in a Rosegarden-compatible format or because
+# its samplerate differs from <rate>. Exit successfully if no
+# work is required; return error code 2 if conversion required,
+# 3 if resampling required, 4 if both required, 1 for other error.
+#
+# rosegarden-audiofile-importer [-r <rate>] -c <inputfile> <outputfile>
+# -- Convert <inputfile> [and resample if necessary] and write to
+# <outputfile> as a WAV file
+#
+# N.B. <outputfile> must not already exist -- this program will not
+# overwrite it, but will fail instead if it does
+
+version=1
+
+OTHER_ERROR=1
+CONVERSION_REQD=2
+RESAMPLE_REQD=3
+BOTH_REQD=4
+
+echo "rosegarden-audiofile-importer: $@" 1>&2
+
+conftest() {
+ # ssrc is our favourite resampler, but it only works for certain
+ # samplerates, so we have to have at least one other just in case
+ if [ -x "`type -path sndfile-resample`" ]; then
+ return 0
+ fi
+ if [ -x "`type -path sox`" ] && sox -h 2>&1 | grep -q polyphase; then
+ return 0
+ fi
+ echo "Required: sox OR sndfile-resample"
+ echo "rosegarden-audiofile-importer: ERROR: No resampler available, failing configuration test" 1>&2
+ return 1
+}
+
+[ -x "`type -path oggdec`" ] && have_oggdec=1
+[ -x "`type -path flac`" ] && have_flac=1
+[ -x "`type -path mpg321`" ] && have_mpg321=1
+[ -x "`type -path sndfile-convert`" -a -x "`type -path sndfile-info`" ] && have_sndfile=1
+
+if [ -n "$have_mpg321" ]; then
+ if ! mpg321 --help 2>&1 | grep -q 'wav N'; then
+ have_mpg321=
+ fi
+fi
+
+case "$1" in
+ -t|--conftest) conftest; exit $?;;
+ -l) conftest || exit $?
+ {
+ [ -n "$have_oggdec" ] && echo ogg
+ [ -n "$have_flac" ] && echo flac
+ [ -n "$have_mpg321" ] && echo mp3
+ [ -n "$have_sndfile" ] && sndfile-convert --help 2>&1 | \
+ grep '^ *[a-z][a-z]* *: ' | awk '{ print $1; }'
+ } | sort | uniq | fmt -1000
+ exit 0;;
+ -r) rate="$2"; shift; shift;;
+esac
+
+case "$1" in
+ -w) test_work=1;;
+ -c) convert=1;;
+ *) exit 2;;
+esac
+
+conftest || exit $OTHER_ERROR
+
+infile="$2"
+if [ ! -r "$infile" ]; then
+ echo "rosegarden-audiofile-importer: ERROR: input file \"$infile\" cannot be opened" 1>&2
+ exit $OTHER_ERROR
+fi
+
+if [ -n "$convert" ]; then
+ outfile="$3"
+ if [ -z "$outfile" ]; then
+ echo "rosegarden-audiofile-importer: ERROR: output file not specified" 1>&2
+ exit $OTHER_ERROR
+ fi
+ if [ -f "$outfile" ]; then
+ echo "rosegarden-audiofile-importer: ERROR: output file \"$outfile\" already exists, not overwriting" 1>&2
+ exit $OTHER_ERROR
+ fi
+fi
+
+base=`basename "$infile"`
+stem=${base%.*}
+extension=${base##*.}
+
+extension=`echo $extension | tr '[A-Z]' '[a-z]'`
+
+# If the file ends in ogg, mp3, wav or flac, we believe it. Otherwise
+# use file to see whether it's actually something else
+
+case "$extension" in
+ ogg|mp3|wav|flac) ;;
+ *) case "`file $infile`" in
+ *Ogg*Vorbis*) extension=ogg;;
+ *MP3*) extension=mp3;;
+ *FLAC*) extension=flac;;
+ esac;;
+esac
+
+case "$extension" in
+ ogg) converter=oggdec
+ [ -x "`type -path ogginfo`" ] && filerate=`ogginfo "$infile" | grep '^Rate: ' | awk '{ print $2; }'`;;
+ mp3) converter=mpg321
+ # don't have a helper program to work out rate
+ ;;
+ *) filerate=`sndfile-info "$infile" | grep '^Sample Rate :' | awk '{ print $4; }'`;;
+esac
+
+case "$extension" in
+ wav) ;;
+ flac) converter=flac; convert_reqd=1;;
+ogg|mp3) convert_reqd=1;;
+ *) converter=sndfile-convert; convert_reqd=1;;
+esac
+
+echo "Extension is $extension, converter is $converter, reqd is $convert_reqd"
+
+case "$filerate" in [0-9]*);; *) filerate=; intermediate=1;; esac
+
+[ -n "$filerate" ] && echo "File samplerate is $filerate (requested: $rate)"
+
+[ -n "$filerate" -a -n "$rate" ] && [ "$filerate" -ne "$rate" ] && resample_reqd=1
+[ -n "$resample_reqd" -a -n "$convert_reqd" ] && intermediate=1
+
+[ -n "$intermediate" ] && echo "Using intermediate file"
+
+if [ -z "$convert" ]; then # -w option
+ if [ -n "$convert_reqd" ]; then
+ [ -n "$intermediate" -o -n "$resample_reqd" ] && exit $BOTH_REQD
+ exit $CONVERSION_REQD;
+ else
+ [ -n "$intermediate" -o -n "$resample_reqd" ] && exit $RESAMPLE_REQD
+ exit 0;
+ fi
+fi
+
+target="$outfile"
+if [ -n "$intermediate" ]; then
+ target="$outfile.tmp.wav"
+ if [ -f "$target" ]; then
+ echo "rosegarden-audiofile-importer: ERROR: intermediate file \"$target\" already exists, not overwriting" 1>&2
+ exit $OTHER_ERROR
+ fi
+ trap "rm -f \"$target\"" 0
+ trap "rm -f \"$target\" \"$outfile\"" 1 2 9 11 15
+else
+ trap "rm -f \"$outfile\"" 1 2 9 11 15
+fi
+
+resample() {
+ _source=$1; shift
+ _target=$1; shift
+ simple=""
+ case "$filerate" in
+ 192000|176400|96000|88200|48000|44100|24000|22050|12000|11025)
+ case "$rate" in
+ 192000|176400|96000|88200|48000|44100|24000|22050|12000|11025)
+ simple=1;;
+ esac
+ esac
+ done=""
+ if [ -x "`type -path ssrc`" -a -n "$simple" ]; then
+ echo "Resampling using ssrc..." 1>&2
+ ssrc --rate "$rate" --twopass --dither 4 --profile standard "$_source" "$_target" && done=1
+ fi
+ if [ -z "$done" ]; then
+ if [ -x "`type -path sndfile-resample`" ]; then
+ echo "Resampling using sndfile-resample..." 1>&2
+ sndfile-resample -to "$rate" -c 0 "$_source" "$_target" || return 4
+ else
+ echo "Resampling using sox..." 1>&2
+ sox "$_source" -r "$rate" "$_target" polyphase || return 4
+ fi
+ fi
+ return 0
+}
+
+if [ -n "$convert_reqd" ]; then
+ case "$converter" in
+ flac) [ -z "$have_flac" -a -n "$have_sndfile" ] && converter=sndfile-convert;;
+ esac
+ case "$converter" in
+ oggdec)
+ [ -n "$have_oggdec" ] || exit $OTHER_ERROR
+ oggdec --output "$target" "$infile" || exit $OTHER_ERROR
+ ;;
+ mpg321)
+ [ -n "$have_mpg321" ] || exit $OTHER_ERROR
+ mpg321 --wav "$target" "$infile" || exit $OTHER_ERROR
+ ;;
+ flac)
+ [ -n "$have_flac" ] || exit $OTHER_ERROR
+ flac --decode --output-name="$target" "$infile" || exit $OTHER_ERROR
+ ;;
+ *)
+ [ -n "$have_sndfile" ] || exit $OTHER_ERROR
+ sndfile-convert -pcm16 "$infile" "$target" || exit $OTHER_ERROR
+ ;;
+ esac
+elif [ -n "$resample_reqd" ]; then
+ resample "$infile" "$target" || exit $OTHER_ERROR
+ resample_reqd=
+fi
+
+if [ ! -f "$target" ]; then
+ echo "rosegarden-audiofile-importer: ERROR: target file not found" 1>&2
+ exit $OTHER_ERROR
+fi
+
+if [ -z "$intermediate" ]; then
+ echo "rosegarden-audiofile-importer: Done" 1>&2
+ exit 0
+fi
+
+if [ -n "$intermediate" -a -n "$rate" -a -z "$filerate" ]; then
+ filerate=`sndfile-info "$target" | grep '^Sample Rate :' | awk '{ print $4; }'`
+ case "$filerate" in
+ [0-9]*) if [ "$filerate" -ne "$rate" ]; then
+ resample_reqd=1
+ fi;;
+ *) echo "rosegarden-audiofile-importer: ERROR: failed to extract samplerate of intermediate file" 1>&2
+ rm "$target"
+ exit $OTHER_ERROR
+ ;;
+ esac
+fi
+
+if [ -n "$resample_reqd" ]; then
+ resample "$target" "$outfile" || exit $OTHER_ERROR
+ rm "$target"
+else
+ mv "$target" "$outfile"
+fi
+
+echo "rosegarden-audiofile-importer: Done" 1>&2
+exit 0
diff --git a/src/helpers/rosegarden-lilypondview b/src/helpers/rosegarden-lilypondview
new file mode 100755
index 0000000..95cc25e
--- /dev/null
+++ b/src/helpers/rosegarden-lilypondview
@@ -0,0 +1,395 @@
+#!/bin/bash
+#
+# Helper program used by Rosegarden to preview and print Lilypond output.
+# Copyright 2004-2008 Chris Cannam and Fervent Software Ltd.
+# Copyright 2006-2008 Heikki Junes.
+# Distributed under the GNU General Public License.
+
+tmpdir=/tmp/$$_lilypondview
+mkdir "$tmpdir" || exit 1
+
+trap "rm -rf \"$tmpdir\"" 0
+
+# Requirements, actual or potential:
+#
+# lilypond (that actually runs, of course)
+# mktemp OR tempfile
+# kpdf OR kghostview OR gpdf OR xpdf OR evince OR acroread
+# kdialog [for graphical mode only]
+# kprinter OR gtklp
+
+prog_lilypond=""
+prog_temp_file=""
+prog_kdialog=""
+prog_pdf_view=""
+prog_printer=""
+
+set eval -- `getopt -n$0 --longoptions="graphical,pdf,print,version,conftest" "gpPv" "$@"`
+
+input_files=""
+pdf_view=""
+printing=""
+graphical=""
+while [ $# -gt 0 ]
+do
+ # Parse options
+ case "$1" in
+ -g|--graphical) graphical=true ;;
+ -p|--print) printing=true ;;
+ -P|--pdf) pdf_view=true ;;
+ -v|--version) print_version=true ;;
+ --conftest) conftest=true ;;
+ esac
+
+ # Check and list the listed LilyPond input files
+ # getopt adds quotation marks ('): "'input.ly'"
+ if [ "`expr match $1 '.*.ly'`" == "`expr length $1 - 1`" -o \
+ "`expr match $1 '.*.ly.gz'`" == "`expr length $1 - 1`" ]; then
+ input="${1:1:`expr length "$1" - 2`}"
+ if [ ! -f "$input" ]; then
+ echo "Error: Can't open \"$input\" for reading" 1>&2
+ exit 1
+ fi
+ input_files="$input_files
+$input"
+ fi
+ shift
+done
+
+#echo "input files: $input_files"
+
+if [ -n "$print_version" ]; then
+ echo "rosegarden-lilypondview v1.6" 1>&2
+ exit 0
+fi
+
+if [ -x "`type -path lilypond`" ]; then
+ lilypond_version="`lilypond --version | grep LilyPond | head -1 | sed 's/^.* //'`"
+ case "$lilypond_version" in
+ 1.*|2.[0-5].*)
+ echo "rosegarden-lilypondview: Unsupported LilyPond version ($lilypond_version)" 1>&2
+ echo "Required: LilyPond v2.6 or newer";;
+ *)
+ prog_lilypond=lilypond;;
+ esac
+else
+ echo "rosegarden-lilypondview: LilyPond unavailable" 1>&2
+ echo "Required: LilyPond"
+fi
+
+reqd=
+
+for x in mktemp tempfile; do
+ if [ -x "`type -path $x`" ]; then
+ prog_temp_file=$x
+ break;
+ fi
+done
+
+if [ -z "$prog_temp_file" ]; then
+ reqd="mktemp OR tempfile, $reqd"
+ echo "rosegarden-lilypondview: No temporary file utility found" 1>&2
+fi
+
+if [ -x "`type -path kdialog`" ]; then
+ prog_kdialog=kdialog
+fi
+
+for x in acroread kpdf kghostview gpdf xpdf evince; do
+ if [ -x "`type -path $x`" ]; then
+ prog_pdf_view=$x
+ break;
+ fi
+done
+
+if [ -z "$prog_pdf_view" ]; then
+ reqd="kpdf OR kghostview OR gpdf OR xpdf OR evince OR acroread, $reqd"
+ echo "rosegarden-lilypondview: No PDF viewer found" 1>&2
+fi
+
+for x in kprinter gtklp; do
+ if [ -x "`type -path $x`" ]; then
+ case "$x" in kprinter) x="$x --stdin";; esac
+ prog_printer=$x
+ break;
+ fi
+done
+
+if [ -z "$prog_printer" ]; then
+ reqd="kprinter OR gtklp, $reqd"
+ echo "rosegarden-lilypondview: No printing program found" 1>&2
+fi
+
+if [ -n "$conftest" ]; then
+ if [ -n "$reqd" ]; then
+ echo "Required: "${reqd%%, }
+ fi
+fi
+
+[ -z "$prog_lilypond" ] && exit 1
+[ -z "$prog_pdf_view" ] && exit 1
+[ -z "$prog_temp_file" ] && exit 1
+
+if [ -n "$conftest" ]; then
+ echo "LilyPond version: $lilypond_version"
+ exit 0
+fi
+
+if [ -z "$prog_kdialog" ]; then
+ # can't do graphical mode
+ echo "rosegarden-lilypondview: Graphical progress dialog requires kdialog utility" 1>&2
+ graphical=""
+fi
+
+if [ -z "$input" ]; then
+cat <<End-of-Usage-message
+Process LilyPond files to PDF files and print/view them.
+
+Usage: rosegarden-lilypondview [OPTION] file.ly ...
+
+Options:
+ -g, --graphical : show Lilypond progress in a graphical dialog
+ -P, --pdf : view created PDF files (DEFAULT)
+ -p, --print : print created PDF files; together with '--pdf', viewing starts first
+ -v, --version : print version of rosegarden-lilypondview
+ --conftest : check that all dependencies are present
+
+example: rosegarden-lilypondview -g file.ly
+
+End-of-Usage-message
+ exit 2
+fi
+
+for x in $input_files; do
+ case "$x" in
+ *.ly|*.ly.gz)
+ input="$x"
+ cp "$input" "$tmpdir/" || exit 1
+
+(
+ dir=`dirname "$input"`
+ base=`basename "$input"`
+ cd "$tmpdir"
+
+ if [ -n "$graphical" ]; then
+ dcopRef=`kdialog --title "Processing" --icon "rosegarden" --progressbar "Processing LilyPond file..." 100`
+ fi
+
+ send_dcop() {
+ if [ -n "$graphical" ]; then
+ dcop $dcopRef "$@"
+ fi
+ }
+
+ trap "send_dcop close" 0
+
+ send_dcop showCancelButton true
+
+ if file "$base" | grep -q gzip; then
+ gunzip "$base"
+ base=`basename "$base" .gz`
+ fi
+
+ includes=`\
+ grep '\\include ' "$base" | \
+ sed -e 's/\\include//' \
+ -e 's/^.*[^a-zA-Z0-9-]\([a-zA-Z0-9-][a-zA-Z0-9-]*.ly\).*$/\1/'`
+
+ for include in $includes; do
+ if [ -r "$dir/$include" ]; then
+ cp "$dir/$include" .
+ elif [ -r "$dir/$include.gz" ]; then
+ gunzip -c "$dir/$include" > ./"$include"
+ fi
+ done
+
+ fileversion=`grep '\\version ' "$base" | head -1 | \
+ sed -e 's/\\version //' -e 's/[^0-9.]//g'`
+
+ args=""
+ convert=""
+echo "LilyPond version is $lilypond_version, file version is $fileversion"
+ case "$lilypond_version" in
+ 2.6.*)
+ args="--format=pdf"
+ case "$fileversion" in
+ 1.*|2.[0-5].*) convert=true;;
+ esac;;
+ 2.8.*)
+ args="--format=pdf"
+ case "$fileversion" in
+ 1.*|2.[0-7].*) convert=true;;
+ esac;;
+ 2.10.*)
+ args="--format=pdf"
+ case "$fileversion" in
+ 1.*|2.[0-9].*) convert=true;;
+ esac;;
+ esac
+
+ logfile="lilypond-output.log"
+ cat </dev/null >"$logfile"
+
+ if [ -n "$convert" ]; then
+ echo "File version is $fileversion against LilyPond version $lilypond_version -- converting..." 1>&2
+ send_dcop setLabel "Updating LilyPond file..."
+ for srcfile in "$base" $includes; do
+ if [ ! -f "$srcfile" ]; then continue; fi
+ case "$fileversion" in
+ 1.*|2.[012345].*)
+ grep -v override-auto-beam-setting "$srcfile" > "${srcfile}_tmp"
+ mv "${srcfile}_tmp" "$srcfile"
+ ;;
+ esac
+ if [ -n "$graphical" ]; then
+ convert-ly "$srcfile" > "${srcfile}_converted" 2> "$logfile" && mv "${srcfile}_converted" "$srcfile"
+ else
+ convert-ly "$srcfile" > "${srcfile}_converted" && mv "${srcfile}_converted" "$srcfile"
+ fi
+ done
+ send_dcop setLabel "Processing LilyPond file..."
+ fi
+
+ if [ -n "$graphical" ] ; then
+ # special bar comment syntax RG exports -- hopefully benign if absent
+ bars=`grep '^%% [0-9][0-9]*$' "$base" | sort -k 1 -n | tail -1 | awk '{ print $2; }'`
+
+ if [ -z "$bars" ]; then
+ staffs=`grep 'Staff *[=<]' "$base" | wc -l`
+ [ "$staffs" -eq 0 ] && staffs=1
+ bars=`grep -v '^ *%' "$base" | wc -l`
+ bars=$(($bars / $staffs))
+ fi
+
+ bars=$(($bars + 5))
+ count=$(($bars * 7 / 3))
+
+ indev=/dev/pts/0
+ if [ ! -c "$indev" ]; then indev=/dev/ptya0; fi
+
+ PROGRESS=`$prog_temp_file`
+
+# echo "Running $prog_lilypond $args \"$base\""
+ $prog_lilypond $args "$base" <$indev 2>&1 | tee -a "$logfile" | \
+ perl -e '
+ $| = 1;
+ print "0\n";
+ $state = 0;
+ $n = "";
+ $base = 0;
+ while (defined ($key = getc)) {
+ if ($key eq "[") { # bar number start mark
+ $state = 1;
+ } elsif ($key eq "]") { # bar number end mark
+ $state = 2;
+ $val = int( ($base + $n) * 100 / '$count' );
+ if ($val > 100) { $val = 100; }
+ print " $val";
+ print "\n";
+ $n = "";
+ } elsif ($key eq "\n") {
+ if ($state == 2) {
+ $base = $base + '$bars'; $state = 0;
+ }
+ } elsif ($state == 1) { # bar number
+ $n = $n . $key;
+ }
+ }
+ print "end\n"
+ ' >& $PROGRESS &
+
+ PROCESSINGSTAGE=0
+ #
+ # Stages:
+ # 0 -- Process LilyPond file
+ # 1 -- Create PDF output
+ # 2 -- Finished
+ #
+ until [ "$PROCESSINGSTAGE" != "0" ]; do
+ sleep 0.2
+ PROGRESSVALUE=`tail -c 4 $PROGRESS`
+ ## Debugging code:
+ # cat $PROGRESS
+ # echo "= $PROGRESSVALUE =="
+ if [ "$PROGRESSVALUE" == "end" ]; then
+ #
+ # Processing was terminated:
+ # - either the number of bars was not known,
+ # - or there appeared an error during processing.
+ #
+ send_dcop setProgress 100
+ PROCESSINGSTAGE=2
+ elif [ "$PROGRESSVALUE" == "100" ]; then
+ PROCESSINGSTAGE=1
+ #
+ # Note: percentage is 100 only after PDF has been created.
+ #
+ send_dcop setProgress 99
+ send_dcop setLabel "Creating PDF output..."
+ else
+ send_dcop setProgress $PROGRESSVALUE
+ fi
+ if [ "true" == `send_dcop wasCancelled` ]; then
+ send_dcop close
+ rm $PROGRESS
+ exit 1;
+ fi
+ done
+ until [ "$PROCESSINGSTAGE" == "2" ]; do
+ sleep 0.2
+ if [ "true" == `send_dcop wasCancelled` ]; then
+ send_dcop close
+ rm $PROGRESS
+ exit 1;
+ fi
+ PROGRESSVALUE=`tail -c 4 $PROGRESS`
+ if [ "$PROGRESSVALUE" == "end" ]; then
+ PROCESSINGSTAGE=2
+ send_dcop setProgress 100
+ fi
+ done
+ ( sleep 2 ; send_dcop close ) &
+ rm $PROGRESS
+ else
+# echo "running $prog_lilypond $args \"$base\"..."
+ $prog_lilypond $args "$base"
+ fi
+
+ target="${base%.*}.pdf"
+
+ if [ -f "$target" ]; then
+ if [ -z "$printing" ]; then
+ $prog_pdf_view "$target"
+ else
+ if [ -n "$pdf_view" ]; then
+ $prog_pdf_view "$target"
+ fi
+ if [ -n "$printing" ]; then
+ $prog_printer < "$target"
+ fi
+ fi
+ exit 0
+ elif [ -n "$graphical" ]; then
+ cat $logfile 1>&2
+ echo 1>&2
+ echo "LilyPond failed" 1>&2
+ LOGGINGFILE=`$prog_temp_file`
+ if [ -n "$graphical" ]; then
+ ( echo
+ echo " ERROR: LilyPond processing failed."
+ echo " LilyPond output follows:"
+ echo
+ cat "$logfile" ) > $LOGGINGFILE
+ $prog_kdialog --textbox $LOGGINGFILE 600 200
+ fi
+ rm $LOGGINGFILE
+ else
+ echo 1>&2
+ echo "LilyPond processing failed." 1>&2
+ fi
+ exit 1
+)
+ ;;
+ *) ;;
+ esac
+done;
diff --git a/src/helpers/rosegarden-project-package b/src/helpers/rosegarden-project-package
new file mode 100755
index 0000000..d9f2cb8
--- /dev/null
+++ b/src/helpers/rosegarden-project-package
@@ -0,0 +1,839 @@
+#!/bin/bash
+#
+# Check Perl requirements before executing the program proper
+if [ "$1" = "--conftest" ]; then
+ reqd=
+ if ! type -path perl >/dev/null; then
+ reqd="Perl 5"
+ else
+ for x in Getopt::Long File::Copy File::Basename XML::Twig; do
+ if ! perl -e "require $x" 2>/dev/null; then
+ reqd="$reqd, Perl module $x"
+ fi
+ done
+ fi
+ if [ -n "$reqd" ]; then
+ echo "Required: ${reqd#, }"
+ exit 1
+ fi
+fi
+exec perl -x "$0" "$@"
+
+#!/usr/bin/perl -w
+# -*- cperl-indent-level: 4 -*-
+#
+# Rosegarden Project file packager
+# Copyright 2005-2008 Chris Cannam and Fervent Software Ltd.
+# Distributed under the GNU General Public License.
+#
+# This program converts between Rosegarden (.rg) files and Rosegarden
+# Project (.rgp) files.
+#
+# A Rosegarden file (.rg) is a single file (in gzipped XML format)
+# that defines a Rosegarden composition in terms of events, segments,
+# programs, plugin data, references to audio files, and so on. Some
+# of these types of information depend on the presence of external
+# files such as the audio files or plugin preset data, without which
+# the file cannot be used.
+#
+# A Rosegarden Project (.rgp) file is a bundle of files, including the
+# Rosegarden file itself, as well as any audio file or other external
+# data it requires, in compressed form. A Rosegarden Project file is
+# intended to be portable and self-contained, but the Rosegarden
+# program itself does not (at the time of writing) understand these
+# files directly.
+#
+# This program can take a Rosegarden file, examine it to find out
+# which other data files it uses, and pack it to create the
+# corresponding Rosegarden Project file. Conversely, it can also take
+# a Rosegarden Project file and unpack it as a project directory for
+# use with Rosegarden.
+
+# TODO:
+# - quiet
+# - versioning
+# - graceful recovery from running out of disk space
+# - mucho tidying
+
+use strict;
+
+use XML::Twig;
+use File::Copy;
+use File::Basename;
+use Getopt::Long;
+
+print STDERR "Rosegarden Project Packager v0.25 (c) Fervent Software Ltd 2005-2008\n";
+print STDERR "This program is Free Software under the GNU General Public License.\n";
+
+my $dcop = undef;
+sub usage {
+ print STDERR "Usage: rosegarden-project-package [--quiet] [--pack] file.rg [file.rgp]\n";
+ print STDERR " rosegarden-project-package [--quiet] --unpack file.rgp\n";
+ print STDERR " rosegarden-project-package [--quiet] --rg file.rgp\n";
+ print STDERR " rosegarden-project-package --conftest\n";
+ if (defined $dcop) {
+ `dcop "$dcop" close`;
+ }
+ exit 2;
+}
+
+my $kdialog = "kdialog --title \"Rosegarden Project\" --icon \"rosegarden\"";
+
+my $pack = 0;
+my $unpack = 0;
+my $conftest = 0;
+my $rg = 0;
+my $quiet = 0;
+my $result = GetOptions("pack" => \$pack,
+ "unpack" => \$unpack,
+ "conftest" => \$conftest,
+ "rg" => \$rg,
+ "quiet" => \$quiet);
+if ($result eq "") {
+ usage;
+}
+
+if (!$conftest) {
+ $dcop = `kdialog --title \"Rosegarden Project Progress\" --icon \"rosegarden\" --progressbar "Please wait..." 100`;
+ chomp $dcop;
+}
+
+my @temporaries = ();
+$SIG{__DIE__} = sub {
+ my $error = shift;
+ $error =~ s/ at .*$/./;
+ `dcop "$dcop" close`;
+ map { system qq{rm -rf $_} } @temporaries;
+ system qq{kdialog --error "$error"};
+ return 1;
+};
+
+sub conftest {
+ my @required = ();
+ system "kdialog -v >/dev/null" and push @required, "kdialog";
+ system "flac --help >/dev/null" and push @required, "flac";
+ # sndfile-convert --help always returns 1
+ system "which sndfile-convert >/dev/null" and push @required, "sndfile-convert";
+# system "oggenc -h >/dev/null 2>&1" and push @required, "oggenc";
+# system "oggdec -h >/dev/null 2>&1" and push @required, "oggdec";
+ system "dcop --help >/dev/null" and push @required, "dcop";
+ return @required;
+}
+
+sub canonicalise {
+ my $b = shift;
+ chomp $b;
+ my $p = shift;
+ chomp $p;
+ $p =~ s,^~/,$ENV{"HOME"}/,;
+ $p =~ s,^~$,$ENV{"HOME"},;
+ $p =~ s,^([^/]),$b/$1,;
+ $p =~ s,/./,/,g;
+ $p =~ s,/$,,;
+ return $p;
+}
+
+sub relativise {
+ my $b = shift;
+ chomp $b;
+ my $p = shift;
+ chomp $p;
+ if ($p =~ m,^$b/,) {
+ $p =~ s,^$b/,,;
+ }
+ return $p;
+}
+
+sub name {
+ my $rgFile = shift;
+ my $n = shift;
+ my $base = basename $rgFile;
+ $base =~ s/\.rg$//i;
+
+ if ($n =~ m,^RG-AUDIO-\d+\.wav(.pk)?$,i or
+ $n =~ m,/RG-AUDIO-\d+\.wav(.pk)?$,i) {
+
+ $n =~ s,RG-AUDIO-(\d+)\.wav,$base-rg-$1.wav,;
+
+ } elsif ($n =~ m,^rg-[0-9-]+.wav(.pk)?$, or
+ $n =~ m,/rg-[0-9-]+.wav(.pk)?$,) {
+
+ $n =~ s,rg-([0-9-]+).wav,$base-rg-$1.wav,;
+ }
+
+ return $n;
+}
+
+sub convertType {
+ my $file = shift;
+ if ($file =~ m/\.(wav|w64)$/i) {
+ return 'flac';
+ } else {
+ return 'copy';
+ }
+}
+
+sub contrib { # Approximate amount of contribution to overall processing time
+ my $file = shift;
+ my $size = (stat $file)[7];
+ if (!defined $size) {
+ return 10.0;
+ }
+ if (convertType($file) eq 'flac' or convertType($file) eq 'ogg') {
+ return $size / 1000.0;
+ } else {
+ return $size / 50000.0;
+ }
+}
+
+sub locate {
+ my $file = shift;
+ if (-f $file) {
+ return $file;
+ }
+ my $code = system qq{$kdialog --warningyesnocancel 'File not found:\n$file\nDo you want to browse for this file now?'};
+ if ($code == 512) { # cancel
+ die "File $file not found.";
+ } elsif ($code == 256) { # no
+ return "";
+ }
+ my $dir = dirname $file;
+ if (! -d $dir) { $dir = $ENV{'HOME'}; }
+ $file = basename $file;
+ $file = `$kdialog --getopenfilename "$dir" "$file"`;
+ chomp $file;
+ return $file;
+}
+
+sub convert {
+ my $source = shift;
+ my $target = shift;
+ print STDERR "$source -> $target\n";
+ $source = locate $source;
+ return if ($source eq "");
+ my $d = dirname $target;
+ print STDERR "Creating directory $d\n";
+ system qq{mkdir -p "$d"} and die "Failed to create directory $d: $!";
+ print STDERR "Convert type is " . convertType($source) . "\n";
+ if (convertType($source) eq 'flac') {
+ print STDERR "Running flac -o $target.rgp.flac $source\n";
+ system qq{flac -s -o "$target.rgp.flac" "$source"} and do {
+ # Conversion failed. First let's assume that's because the
+ # input is a type of WAV file flac doesn't like.
+ system qq{sndfile-convert -pcm24 "$source" "$target.rgp.wav"} and do {
+ die "Failed to convert $source to PCM-24 intermediate file: $!";
+ };
+ system qq{flac -s -o "$target.rgp.flac" "$target.rgp.wav"} and do {
+ # OK, that didn't help.
+ system qq{rm -f "$target.rgp.wav"};
+ die "Failed to convert PCM-24 intermediate file $target.rgp.wav to $target.rgp.flac: $!"; # warn via dialog & recover
+ };
+ # Looks good, remove intermediate file
+ system qq{rm -f "$target.rgp.wav"};
+ };
+ } elsif (convertType($source) eq 'ogg') {
+ print STDERR "Running oggenc -o $target $source\n";
+ system qq{oggenc -o "$target.rgp.ogg" "$source"} and
+ die "Failed to convert $source to $target.rgp.ogg"; # warn via dialog & recover
+ } else {
+ print STDERR "Copying $source to $target\n";
+ copy $source, $target or die "Failed to copy $source to $target: $!";
+ }
+ return 1;
+}
+
+sub rgPack {
+
+ my ($d, $an, $pn);
+ $an = 0;
+ $pn = 0;
+ my $projectDir = "";
+ my %audioFiles = ();
+ my %unusedAudioFiles = ();
+ my @possibles = ();
+ my @indices = ();
+
+ my $rgFile = shift;
+ my $projectFile = shift;
+
+ $rgFile = canonicalise `pwd`, $rgFile;
+ $projectFile = canonicalise `pwd`, $projectFile;
+
+ if (-d $projectFile) {
+ die "Project file $projectFile exists and is a directory -- not overwriting it";
+ }
+
+ if (-f $projectFile) {
+ if (system qq{$kdialog --warningyesno "Project file \"$projectFile\" already exists. Overwrite it?"}) {
+ die "Not overwriting existing project file $projectFile";
+ }
+ }
+
+ my $targetDir = $projectFile . ".d";
+ if (-f $targetDir or -d $targetDir) {
+ system qq{$kdialog --sorry "Packaging directory \"`pwd`/$targetDir\" already exists.\nCannot continue to create project file."};
+ die "Not overwriting existing packaging directory $targetDir";
+ }
+
+ `dcop "$dcop" setLabel "Reading Rosegarden file..."`;
+
+ my $dir = '.';
+ if ($rgFile =~ m,/,) {
+ $dir = $rgFile;
+ $dir =~ s,[^/]*$,,;
+ }
+
+ if ($dir =~ m,^[^/],) {
+ my $wd = `pwd`;
+ chomp $wd;
+ $dir = "$wd/$dir";
+ }
+
+ $dir =~ s,/\.?/,/,g;
+
+ print STDERR "dir is $dir\n";
+
+ my $twig = XML::Twig->new();
+
+ open INZIP, "-|", "gunzip -c $rgFile";
+
+ eval {
+ $twig->parse(\*INZIP);
+ };
+
+ close INZIP;
+
+ my $root = $twig->root;
+ if (!defined $root) { die "No root"; }
+
+ if ($root->gi ne 'rosegarden-data') {
+ die "Not a Rosegarden file";
+ }
+
+ my $audioFilesNode = $root->first_child('audiofiles');
+
+ my $audioPathNode = $audioFilesNode->first_child('audioPath');
+ $projectDir = canonicalise $dir, $audioPathNode->att('value');
+
+ my @audioNodes = $audioFilesNode->children('audio');
+ foreach my $audioNode (@audioNodes) {
+ my $file = $audioNode->att('file');
+ my $id = $audioNode->att('id');
+ my $done = 0;
+ foreach my $segment ($root->children('segment')) {
+ if (defined $segment->att('type') and
+ $segment->att('type') eq "audio" and
+ $segment->att('file') == $id) {
+ $audioFiles{$id} = $file;
+ $done = 1;
+ last;
+ }
+ }
+ if (!$done) { $unusedAudioFiles{$id} = $file; }
+ }
+
+ if (%unusedAudioFiles) {
+ my @args;
+ map {
+ my $id = $_;
+ my $file = $unusedAudioFiles{$id};
+
+ my $haveElsewhere = 0;
+ foreach my $usedFile (values %audioFiles) {
+ if ($usedFile eq $file) {
+ $haveElsewhere = 1;
+ last;
+ }
+ }
+
+ if (!$haveElsewhere) {
+
+ my $fn = canonicalise $projectDir, $file;
+ my $desc = `file "$fn"`;
+ my $sz = `wc -c "$fn"`;
+ chomp $desc;
+ chomp $sz;
+ $sz =~ s,\s+.*,,;
+ $desc =~ s,^$fn: ,,;
+ $desc =~ s,\([^\)]*\)\s+,,g;
+ $fn =~ s,^$projectDir/,[PROJECT]/,;
+ push @args, ($id, "\"$fn - $sz bytes - $desc\"", "off");
+ }
+
+ } sort { $a <=> $b } keys %unusedAudioFiles;
+
+ print STDERR "args are @args\n";
+ my $result = `$kdialog --checklist "The following audio files are referred to in the Rosegarden file,\nbut are not used in any segment.\n\nBy default these will not be included in the project file.\n\nPlease select any that you wish to include." @args`;
+ if ($? == 256) {
+ die "Operation cancelled";
+ }
+ chomp $result;
+ print STDERR "result is $result\n";
+ @indices =
+ map { s/^"([^"]*)"$/$1/; $_ }
+ split '\s', $result;
+ }
+
+ map { $audioFiles{$_} = $unusedAudioFiles{$_} } @indices;
+ @indices = ();
+
+ my @peakFiles = ();
+ map {
+ my $peakFile = canonicalise($projectDir, $_) . ".pk";
+ -f $peakFile and push @peakFiles, $peakFile;
+ } values %audioFiles;
+
+ my %possibleMap;
+
+ my $studioNode = $root->first_child('studio');
+ my @deviceNodes = $studioNode->children('device');
+ foreach my $deviceNode (@deviceNodes) {
+ my @instrumentNodes = $deviceNode->children('instrument');
+ foreach my $instrumentNode (@instrumentNodes) {
+ my @pluginNodes = $instrumentNode->children('plugin');
+ push @pluginNodes, $instrumentNode->children('synth');
+ my @configureNodes;
+ map { push @configureNodes, $_->children('configure') } @pluginNodes;
+ map {
+ my $possible = $_->att('value');
+ my $loc = canonicalise $projectDir, $possible;
+ if (-f $loc && -r $loc) {
+ $possibleMap{$possible} = 1;
+ }
+ } @configureNodes;
+ }
+ }
+
+ @possibles = sort keys %possibleMap;
+
+ `dcop "$dcop" setProgress 5`;
+
+ print STDERR "Possibles: @possibles\n";
+
+ if (@possibles) {
+ print STDERR "$#possibles possibles\n";
+ my @args;
+ my $n = 0;
+ map {
+ my $possible = $_;
+ my $fn = canonicalise $projectDir, $possible;
+ my $desc = `file "$fn"`;
+ my $sz = `wc -c "$fn"`;
+ my $status = "off";
+ chomp $desc;
+ chomp $sz;
+ $sz =~ s,\s+.*,,;
+ $desc =~ s,^$fn: ,,;
+ $desc =~ s,\([^\)]*\)\s+,,g;
+ if ($fn =~ m,^$projectDir/[^/]*$,) {
+ $status = "on";
+ }
+ $fn =~ s,^$projectDir/,(PROJECT)/,;
+ push @args, ($n, "\"$fn - $sz bytes - $desc\"", $status);
+ ++$n;
+ } @possibles;
+ print STDERR "args are @args\n";
+ my $result = `$kdialog --checklist "The following files may be required for use by plugins used in this composition.\n\nPlease select any that you wish to include in the project file." @args`;
+ if ($? == 256) {
+ die "Operation cancelled";
+ }
+ chomp $result;
+ print STDERR "result is $result\n";
+ @indices =
+ map { s/^"([^"]*)"$/$1/; $_ }
+ split '\s', $result;
+ }
+
+ mkdir $targetDir or die "Cannot create packaging directory $targetDir: $!";
+ push @temporaries, $targetDir;
+
+ my @sourceFiles = sort values %audioFiles;
+ push @sourceFiles, @peakFiles;
+ map { push @sourceFiles, $possibles[$_] } @indices;
+
+ print STDERR "Source files: @sourceFiles\n";
+
+ `dcop "$dcop" setProgress 10`;
+
+ my %fileMap;
+
+ my $totalContrib = 0.0;
+ map { $totalContrib += contrib canonicalise $projectDir, $_; } @sourceFiles;
+ if ($totalContrib == 0) {
+ $totalContrib = 10;
+ }
+
+ my $contribCount = 0.0;
+
+ print STDERR "Total contribution: $totalContrib\n";
+
+ my $newProjectDir = basename $rgFile;
+ $newProjectDir =~ s/.rg$//i;
+ if ($newProjectDir eq basename $rgFile) {
+ $newProjectDir = $newProjectDir . ".d";
+ }
+
+ foreach my $origSourceFile (@sourceFiles) {
+ my $sourceCanonical = canonicalise $projectDir, $origSourceFile;
+ my $sourceRelative = relativise $projectDir, $sourceCanonical;
+ my $renamedRelative = name $rgFile, $sourceRelative;
+ if ($sourceRelative =~ m,^/,) {
+ $renamedRelative = basename $renamedRelative;
+ }
+ my $sourceFile = canonicalise $projectDir, $sourceRelative;
+ my $targetFile = canonicalise($targetDir . "/" . $newProjectDir, $renamedRelative);
+ print STDERR "first guess: $sourceFile -> $targetFile (renamedRelative is $renamedRelative)\n";
+ my $count = 1;
+ while (-f $targetFile or -d $targetFile) {
+ print STDERR "$targetFile exists... ";
+ if ($renamedRelative =~ m,\.([^.]+)$,) {
+ $renamedRelative =~ s,^(.*)\.([^.]+)$,$1_$count.$2,;
+ } else {
+ $renamedRelative = $renamedRelative . "_$count";
+ }
+ $targetFile = canonicalise($targetDir . "/" . $newProjectDir, $renamedRelative);
+ ++$count;
+ print STDERR "trying $targetFile\n";
+ }
+ if (convertType($sourceFile) eq 'flac' or
+ convertType($sourceFile) eq 'ogg') {
+ `dcop "$dcop" setLabel "Converting $renamedRelative..."`;
+ } else {
+ `dcop "$dcop" setLabel "Including $renamedRelative..."`;
+ }
+ $fileMap{$origSourceFile} = $renamedRelative;
+ convert $sourceFile, $targetFile;
+ $contribCount += contrib $sourceFile;
+ my $progress = int(10.0 + $contribCount * 82.0 / $totalContrib);
+ `dcop "$dcop" setProgress $progress`;
+ }
+
+ my $code;
+ while (($code = system qq{$kdialog --yesno "Do you want to include any additional files in this project?"}) == 0) {
+ my $file = `$kdialog --getopenfilename "$dir" "*"`;
+ chomp $file;
+ my $targetFile = canonicalise($targetDir . "/" . $newProjectDir, basename $file);
+ convert $file, $targetFile;
+ }
+
+ `dcop "$dcop" setLabel "Converting Rosegarden file..."`;
+
+ $audioPathNode->set_att('value', $newProjectDir);
+
+ foreach my $audioNode (@audioNodes) {
+ if (exists $audioFiles{$audioNode->att('id')}) {
+ if (exists $fileMap{$audioNode->att('file')}) {
+ my $before = $audioNode->att('file');
+ my $after = $fileMap{$before};
+ print STDERR "Renaming $before to $after in XML\n";
+ $audioNode->set_att('file', $after);
+ }
+ } else {
+ $audioNode->cut;
+ }
+ }
+
+ foreach my $deviceNode (@deviceNodes) {
+ my @instrumentNodes = $deviceNode->children('instrument');
+ foreach my $instrumentNode (@instrumentNodes) {
+ my @pluginNodes = $instrumentNode->children('plugin');
+ push @pluginNodes, $instrumentNode->children('synth');
+ my @configureNodes;
+ map { push @configureNodes, $_->children('configure') } @pluginNodes;
+ foreach my $configureNode (@configureNodes) {
+ if (exists $fileMap{$configureNode->att('value')}) {
+ my $before = $configureNode->att('value');
+ my $after = $fileMap{$before};
+ print STDERR "Renaming $before to $after in configure XML\n";
+ $configureNode->set_att('value', $after);
+ }
+ }
+ }
+ }
+
+ my $targetRgFile = $targetDir . "/" . basename $rgFile;
+ my $targetRgXml = $targetRgFile . ".xml";
+
+ print STDERR "out is $targetRgFile\n";
+
+ open OUT, "| gzip -c > $targetRgFile" or die "Failed to open $targetRgFile for writing via gzip";
+
+ $twig->print(\*OUT);
+
+ close OUT;
+
+ `dcop "$dcop" setLabel "Packaging..."`;
+ `dcop "$dcop" setProgress 95`;
+
+ system qq{rm -f "$projectFile"} and die "Overwriting old project file failed: $!";
+
+ my $baseTarget = basename $targetDir;
+ my $baseRg = basename $targetRgFile;
+ my $baseProject = basename $projectFile;
+ print STDERR "dir is $targetDir\nbaseTarget is $baseTarget\nbaseRg is $baseRg\nnewProjectDir is $newProjectDir\n";
+ system qq{mkdir -p "$targetDir/$newProjectDir"};
+ system qq{cd "$targetDir"; tar cf - "$baseRg" "$newProjectDir" | gzip --fast -c > "$baseProject" ; mv "$baseProject" ..} and die "Making tarball failed";
+
+ `dcop "$dcop" setLabel "Done"`;
+ `dcop "$dcop" setProgress 100`;
+
+ system qq{rm -r "$targetDir"} and die "Cleaning up packaging directory failed: $!";
+
+ sleep 1;
+
+ my $completeProject = canonicalise $targetDir, "../$baseProject";
+ system qq{$kdialog --msgbox "Packaging complete."};
+
+ `dcop "$dcop" close`;
+
+ return 1;
+}
+
+sub rgUnpack {
+
+ my $rgFile = shift;
+ my $projectFile = shift;
+
+ $rgFile = canonicalise `pwd`, $rgFile;
+ $projectFile = canonicalise `pwd`, $projectFile;
+
+ if (! -f $projectFile) {
+ die "Project file $projectFile not found";
+ }
+
+ $rgFile =~ /.rg$/i or $rgFile = "$rgFile.rg";
+
+ # directory containing target rg file
+ my $rgDir = dirname $rgFile;
+
+ my $origRgFile = basename $projectFile;
+ $origRgFile =~ s/\.rg\.rgp$/.rg/i;
+ $origRgFile =~ s/\.rgp$/.rg/i;
+
+ if (-d $rgFile) {
+ die "Target Rosegarden file $rgFile exists and is a directory -- not overwriting";
+ }
+
+ if (-f $rgFile) {
+ if (system qq{$kdialog --warningyesno "Rosegarden file \"$rgFile\" already exists. Overwrite it?"}) {
+ die "Not overwriting existing Rosegarden file $rgFile";
+ }
+ }
+
+ my $projectDir = $rgFile;
+ $projectDir =~ s/\.[^\.\/]*$//;
+ if ($projectDir eq $rgFile) {
+ $projectDir = "$projectDir.d";
+ }
+ # projectDir is now absolute or relative to cwd
+
+ if (-f $projectDir) {
+
+ die "Project directory $projectDir already exists and is a file -- not overwriting it";
+
+ } elsif (-d $projectDir) {
+
+ if (system qq{$kdialog --warningyesno "Project directory \"$projectDir\" already exists. Overwrite any duplicate files?"}) {
+ die "Not overwriting existing project directory $projectDir";
+ }
+
+ } else {
+ push @temporaries, $projectDir;
+ system qq{mkdir -p "$projectDir"} and die "Failed to create target project directory $projectDir";
+ }
+
+ my %dirs;
+ map { chomp; s,/.*$,,; $dirs{$_} = 1; } `gunzip -c "$projectFile" | tar tf -`;
+
+ my $origProjectDir = "";
+ for my $targetDir (keys %dirs) {
+ if ($targetDir =~ /.rg$/ and !($targetDir =~ m,/,)) {
+ $origRgFile = $targetDir;
+ next;
+ }
+ if ($targetDir eq $origRgFile) {
+ next;
+ }
+ if ($origProjectDir eq "") {
+ $origProjectDir = $targetDir;
+ next;
+ }
+ }
+ # origProjectDir is now relative to the base path
+ # origRgFile is a base name only (no path)
+
+ # We have:
+ # rgFile -> target to unpack rg file to
+ # projectFile -> original project file
+ # rgDir -> target directory to contain rg file
+ # origRgFile -> original rg file (virtual) in same dir as project file
+ # projectDir -> target to unpack project files to (rgFile.d)
+ # origProjectDir -> original name of project directory (probably origRgFile.d)
+
+ # procedure:
+ # create a temporary directory tmpdir
+ # go there and unpack the original project file
+ # this will create original rg file and original project dir in tmpdir
+ # move original project dir from tmpdir to rgDir
+ # unpack audio files in new project dir
+ # move original rg file from tmpdir to rgFile
+ # if the project dir basename differs from the original project dir basename,
+ # edit the rgFile (just the audioPath node) appropriately.
+ # !!! extra above: show error and exit if rgFile exists and is a directory
+
+ my $tmpdir = "rgp_tmp_$$"; ##!!! clean up
+ if (-e $tmpdir) { die "Temporary directory $tmpdir already exists, abandoning"; }
+ system qq{ mkdir "$tmpdir" } and die "Failed to create temporary directory $tmpdir";
+
+ push @temporaries, $tmpdir;
+
+ `dcop "$dcop" setLabel "Unpacking..."`;
+ `gunzip -c "$projectFile" | ( cd "$tmpdir"; tar xf - )`;
+
+ `dcop "$dcop" setProgress 20`;
+
+ for my $item (glob "$tmpdir/$origProjectDir/*") {
+ system qq{mv "$item" "$projectDir/"} and die "Failed to move $item to $projectDir";
+ }
+
+ my @encFiles = `ls $projectDir/*.rgp.flac $projectDir/*.rgp.ogg 2>/dev/null`;
+ my $count = 0;
+ foreach my $encFile (@encFiles) {
+ chomp $encFile;
+ my $origFile = $encFile;
+ $origFile =~ s/\.rgp\.(flac|ogg)$//;
+ my $decoder = 'flac -s -f -d';
+ if ($encFile =~ /ogg$/) {
+ $decoder = 'oggdec';
+ }
+ if (system qq{$decoder -o "$origFile" "$encFile"}) {
+ die "Failed to unpack $origFile"; # should warn & continue
+ } else {
+ system qq{rm "$encFile"};
+ }
+ my $progress = @encFiles;
+ $progress = int(25 + $count * 75 / $progress);
+ ++$count;
+ `dcop "$dcop" setProgress $progress`;
+ }
+
+ # we now have the target project directory set up correctly:
+ # all we need is the rg file
+
+ open INZIP, "-|", "gunzip -c $tmpdir/$origRgFile" or die "Failed to open original Rosegarden file $tmpdir/$origRgFile";
+ open OUTZIP, "| gzip -c > $rgFile" or die "Failed to open target Rosegarden file $rgFile for writing";
+ my $encProjDir = basename $projectDir;
+ while (<INZIP>) {
+ if (/<audioPath\s/) {
+ s/<audioPath\s+value=\"[^\"]*"\s*\/>/<audioPath value="$encProjDir"\/>/;
+ }
+ print OUTZIP;
+ }
+ close INZIP;
+ close OUTZIP;
+
+ `dcop "$dcop" setLabel "Done"`;
+ `dcop "$dcop" setProgress 100`;
+
+ sleep 1;
+
+ system qq{rm -rf "$tmpdir"};
+ `dcop "$dcop" close`;
+
+ @temporaries = ();
+ return 1;
+}
+
+
+if (!$pack && !$unpack && !$conftest && !$rg) {
+ if (exists $ARGV[0]) {
+ if ($ARGV[0] =~ /.rg$/i) {
+ $pack = 1;
+ } elsif ($ARGV[0] =~ /.rgp$/i) {
+ $unpack = 1;
+ } else {
+ usage;
+ }
+ } else {
+ usage;
+ }
+}
+
+my @required = conftest;
+if (@required) {
+ print STDERR "The following additional packages are required but not available in the PATH:\n";
+ print STDERR join ' ', @required;
+ print STDERR "\n";
+ if ($conftest) {
+ print "Required: ";
+ print join ', ', @required;
+ print "\n";
+ exit 1;
+ } else {
+ die "Configuration requirements not met\nThe following helper applications were not found: " . join(', ', @required);
+ }
+}
+
+if ($conftest) {
+ exit 0;
+}
+
+if ($pack) {
+
+ if ($unpack || $conftest || $rg) { usage; }
+
+ my ($rgFile, $projectFile);
+
+ if (exists $ARGV[0]) {
+ $rgFile = $ARGV[0];
+ chomp $rgFile;
+ }
+ if (! -f $rgFile) {
+ die "$rgFile: No such file or directory\n";
+ }
+
+ if (exists $ARGV[1]) {
+ $projectFile = $ARGV[1];
+ chomp $projectFile;
+ } else {
+ $projectFile = $rgFile;
+ $projectFile =~ s/\.rg$/.rgp/;
+ if ($projectFile eq $rgFile) {
+ $projectFile = $rgFile . ".rgp";
+ }
+ }
+
+ rgPack $rgFile, $projectFile;
+
+} elsif ($unpack || $rg) {
+
+ if ($pack || $conftest) { usage; }
+ if ($unpack && $rg) { usage; }
+
+ my $projectFile;
+
+ if (exists $ARGV[0]) {
+ $projectFile = $ARGV[0];
+ chomp $projectFile;
+ }
+ if (! -f $projectFile) {
+ die "$projectFile: No such file or directory\n";
+ }
+
+ my $rgFile;
+ if (exists $ARGV[1]) {
+ $rgFile = $ARGV[1];
+ chomp $rgFile;
+ } else {
+ $rgFile = $projectFile;
+ $rgFile =~ s/\.rg\.rgp$/.rg/i;
+ $rgFile =~ s/\.rgp$/.rg/i;
+ }
+
+ rgUnpack $rgFile, $projectFile;
+
+ if ($rg) {
+ system qq{rosegarden "$rgFile"};
+ }
+}
+
diff --git a/src/misc/Debug.cpp b/src/misc/Debug.cpp
new file mode 100644
index 0000000..33d5464
--- /dev/null
+++ b/src/misc/Debug.cpp
@@ -0,0 +1,396 @@
+// -*- c-basic-offset: 4 -*-
+
+/*
+ Rosegarden
+ A sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <bownie@bownie.com>
+
+ 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 "Strings.h"
+#include "misc/Debug.h"
+#include "Debug.h"
+
+#if KDE_VERSION < KDE_MAKE_VERSION(3,2,0)
+#include <qdatetime.h>
+#include <qpoint.h>
+#include <qrect.h>
+#include <qregion.h>
+#include <qstringlist.h>
+#include <qpen.h>
+#include <qbrush.h>
+#include <qsize.h>
+#include <kurl.h>
+#endif
+
+#include "base/Event.h"
+#include "base/Segment.h"
+#include "base/RealTime.h"
+#include "base/Colour.h"
+#include "gui/editors/guitar/Chord.h"
+#include "gui/editors/guitar/Fingering.h"
+
+#ifndef NDEBUG
+
+kdbgstream&
+operator<<(kdbgstream &dbg, const std::string &s)
+{
+ dbg << strtoqstr(s);
+ return dbg;
+}
+
+kdbgstream&
+operator<<(kdbgstream &dbg, const Rosegarden::Event &e)
+{
+ dbg << "Event type : " << e.getType() << endl;
+
+ dbg << "\tDuration : " << e.getDuration()
+ << "\n\tAbsolute Time : " << e.getAbsoluteTime()
+ << endl;
+
+ // for (Event::PropertyMap::const_iterator i = e.properties().begin();
+ // i != e.properties().end(); ++i) {
+ // dbg << "\t\t" << (*i).first << " : "
+ // << ((*i).second)->unparse() << '\n';
+ // }
+
+ // e.dump(std::cerr);
+
+ return dbg;
+}
+
+kdbgstream&
+operator<<(kdbgstream &dbg, const Rosegarden::Segment &t)
+{
+ dbg << "Segment for instrument " << t.getTrack()
+ << " starting at " << t.getStartTime() << endl;
+
+ for (Rosegarden::Segment::const_iterator i = t.begin();
+ i != t.end(); ++i) {
+ if (!(*i)) {
+ dbg << "WARNING : skipping null event ptr\n";
+ continue;
+ }
+
+ dbg << "Dumping Event : \n";
+ dbg << *(*i) << endl;
+ }
+
+ return dbg;
+}
+
+kdbgstream&
+operator<<(kdbgstream &dbg, const Rosegarden::RealTime &t)
+{
+ dbg << t.toString();
+ return dbg;
+}
+
+kdbgstream&
+operator<<(kdbgstream &dbg, const Rosegarden::Colour &c)
+{
+ dbg << "Colour : rgb = " << c.getRed() << "," << c.getGreen() << "," << c.getBlue();
+ return dbg;
+}
+
+kdbgstream&
+operator<<(kdbgstream &dbg, const Rosegarden::Guitar::Chord &c)
+{
+ dbg << "Chord root = " << c.getRoot() << ", ext = '" << c.getExt() << "'";
+
+// for(unsigned int i = 0; i < c.getNbFingerings(); ++i) {
+// dbg << "\nFingering " << i << " : " << c.getFingering(i).toString().c_str();
+// }
+
+ Rosegarden::Guitar::Fingering f = c.getFingering();
+
+ dbg << ", fingering : ";
+
+ for(unsigned int j = 0; j < 6; ++j) {
+ int pos = f[j];
+ if (pos >= 0)
+ dbg << pos << ' ';
+ else
+ dbg << "x ";
+ }
+ return dbg;
+}
+
+#ifdef NOT_DEFINED
+
+ostream&
+kdbgostreamAdapter::operator<<(bool i)
+{
+ m_kdbgStream << i;
+ return *this;
+}
+
+ostream&
+kdbgostreamAdapter::operator<<(short i)
+{
+ m_kdbgStream << i;
+ return *this;
+}
+
+ostream&
+kdbgostreamAdapter::operator<<(unsigned short i)
+{
+ m_kdbgStream << i;
+ return *this;
+}
+
+ostream&
+kdbgostreamAdapter::operator<<(char i)
+{
+ m_kdbgStream << i;
+ return *this;
+}
+
+ostream&
+kdbgostreamAdapter::operator<<(unsigned char i)
+{
+ m_kdbgStream << i;
+ return *this;
+}
+
+ostream&
+kdbgostreamAdapter::operator<<(int i)
+{
+ m_kdbgStream << i;
+ return *this;
+}
+
+ostream&
+kdbgostreamAdapter::operator<<(unsigned int i)
+{
+ m_kdbgStream << i;
+ return *this;
+}
+
+ostream&
+kdbgostreamAdapter::operator<<(long i)
+{
+ m_kdbgStream << i;
+ return *this;
+}
+
+ostream&
+kdbgostreamAdapter::operator<<(unsigned long i)
+{
+ m_kdbgStream << i;
+ return *this;
+}
+
+ostream&
+kdbgostreamAdapter::operator<<(const QString& string)
+{
+ m_kdbgStream << string;
+ return *this;
+}
+
+ostream&
+kdbgostreamAdapter::operator<<(const char *string)
+{
+ m_kdbgStream << string;
+ return *this;
+}
+
+ostream&
+kdbgostreamAdapter::operator<<(const QCString& string)
+{
+ m_kdbgStream << string;
+ return *this;
+}
+
+ostream&
+kdbgostreamAdapter::operator<<(void * p)
+{
+ m_kdbgStream << p;
+ return *this;
+}
+
+ostream&
+kdbgostreamAdapter::operator<<(KDBGFUNC f)
+{
+ (*f)(m_kdbgStream);
+ return *this;
+}
+
+ostream&
+kdbgostreamAdapter::operator<<(double d)
+{
+ m_kdbgStream << d;
+ return *this;
+}
+
+#endif
+
+// ostream& endl( ostream &s)
+// {
+// s << "\n"; return s;
+// }
+
+// void DBCheckThrow()
+// {
+// using Rosegarden::Int;
+
+// Rosegarden::Event ev;
+
+// try {
+// int pitch = ev.get<Int>("BLAH");
+
+// } catch (Rosegarden::Event::NoData) {
+// RG_DEBUG << "DBCheckThrow()" << endl;
+// }
+// }
+
+#if KDE_VERSION < KDE_MAKE_VERSION(3,2,0)
+kdbgstream&
+operator<<(kdbgstream &dbg, const QDateTime& time)
+{
+ dbg << time.toString();
+ return dbg;
+}
+
+kdbgstream&
+operator<<(kdbgstream &dbg, const QDate& date)
+{
+ dbg << date.toString();
+ return dbg;
+}
+
+kdbgstream&
+operator<<(kdbgstream &dbg, const QTime& time )
+{
+ dbg << time.toString();
+ return dbg;
+}
+
+kdbgstream&
+operator<<(kdbgstream &dbg, const QPoint& p )
+{
+ dbg << "(" << p.x() << ", " << p.y() << ")";
+ return dbg;
+}
+
+kdbgstream&
+operator<<(kdbgstream &dbg, const QSize& s )
+{
+ dbg << "[" << s.width() << "x" << s.height() << "]";
+ return dbg;
+}
+
+kdbgstream&
+operator<<(kdbgstream &dbg, const QRect& r )
+{
+ dbg << "[" << r.x() << "," << r.y() << " - "
+ << r.width() << "x" << r.height() << "]";
+ return dbg;
+}
+
+kdbgstream&
+operator<<(kdbgstream &dbg, const QRegion& reg )
+{
+ dbg << "[ ";
+
+ QMemArray<QRect>rs = reg.rects();
+ for (uint i = 0;i < rs.size();++i)
+ dbg << QString("[%1,%2 - %3x%4] ").arg(rs[i].x()).arg(rs[i].y())
+ .arg(rs[i].width()).arg(rs[i].height() ) ;
+
+ dbg << "]";
+ return dbg;
+}
+
+kdbgstream&
+operator<<(kdbgstream &dbg, const KURL& u )
+{
+ dbg << u.prettyURL();
+ return dbg;
+}
+
+kdbgstream&
+operator<<(kdbgstream &dbg, const QStringList& l )
+{
+ dbg << "(";
+ dbg << l.join(",");
+ dbg << ")";
+
+ return dbg;
+}
+
+kdbgstream&
+operator<<(kdbgstream &dbg, const QColor& c )
+{
+ if ( c.isValid() )
+ dbg << c.name();
+ else
+ dbg << "(invalid/default)";
+ return dbg;
+}
+
+kdbgstream&
+operator<<(kdbgstream &dbg, const QPen& p )
+{
+ static const char* const s_penStyles[] = {
+ "NoPen", "SolidLine", "DashLine", "DotLine", "DashDotLine",
+ "DashDotDotLine"
+ };
+ static const char* const s_capStyles[] = {
+ "FlatCap", "SquareCap", "RoundCap"
+ };
+ dbg << "[ style:";
+ dbg << s_penStyles[ p.style() ];
+ dbg << " width:";
+ dbg << p.width();
+ dbg << " color:";
+ if ( p.color().isValid() )
+ dbg << p.color().name();
+ else
+ dbg << "(invalid/default)";
+ if ( p.width() > 0 ) {
+ dbg << " capstyle:";
+ dbg << s_capStyles[ p.capStyle() >> 4 ];
+ }
+ dbg << " ]";
+ return dbg;
+}
+
+kdbgstream&
+operator<<(kdbgstream &dbg, const QBrush& b)
+{
+ static const char* const s_brushStyles[] = {
+ "NoBrush", "SolidPattern", "Dense1Pattern", "Dense2Pattern", "Dense3Pattern",
+ "Dense4Pattern", "Dense5Pattern", "Dense6Pattern", "Dense7Pattern",
+ "HorPattern", "VerPattern", "CrossPattern", "BDiagPattern", "FDiagPattern",
+ "DiagCrossPattern"
+ };
+
+ dbg << "[ style: ";
+ dbg << s_brushStyles[ b.style() ];
+ dbg << " color: ";
+ if ( b.color().isValid() )
+ dbg << b.color().name() ;
+ else
+ dbg << "(invalid/default)";
+ if ( b.pixmap() )
+ dbg << " has a pixmap";
+ dbg << " ]";
+ return dbg;
+}
+#endif
+
+#endif
+
diff --git a/src/misc/Debug.h b/src/misc/Debug.h
new file mode 100644
index 0000000..8f8f4dd
--- /dev/null
+++ b/src/misc/Debug.h
@@ -0,0 +1,166 @@
+// -*- c-basic-offset: 4 -*-
+
+/*
+ Rosegarden
+ A sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <bownie@bownie.com>
+
+ 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 ROSEDEBUG_H
+#define ROSEDEBUG_H
+
+#include <string>
+#include <iostream>
+#include <kdebug.h>
+#include <kdeversion.h>
+
+#if KDE_VERSION < KDE_MAKE_VERSION(3,2,0)
+class QDateTime;
+class QDate;
+class QTime;
+class QPoint;
+class QSize;
+class QRect;
+class QRegion;
+class KURL;
+class QStringList;
+class QColor;
+class QPen;
+class QBrush;
+#endif
+
+namespace Rosegarden { class Event; class Segment; class RealTime; class Colour; namespace Guitar { class Chord; } }
+
+#define KDEBUG_AREA 200000
+#define KDEBUG_AREA_NOTATION 200001
+#define KDEBUG_AREA_MATRIX 200002
+#define KDEBUG_AREA_SEQUENCER 200003
+#define KDEBUG_AREA_SEQUENCEMANAGER 200004
+
+#define RG_DEBUG kdDebug(KDEBUG_AREA)
+#define NOTATION_DEBUG kdDebug(KDEBUG_AREA_NOTATION)
+#define MATRIX_DEBUG kdDebug(KDEBUG_AREA_MATRIX)
+#define SEQUENCER_DEBUG kdDebug(KDEBUG_AREA_SEQUENCER)
+#define SEQMAN_DEBUG kdDebug(KDEBUG_AREA_SEQUENCEMANAGER)
+
+#ifndef NDEBUG
+
+kdbgstream&
+operator<<(kdbgstream&, const std::string&);
+
+kdbgstream&
+operator<<(kdbgstream&, const Rosegarden::Event&);
+
+kdbgstream&
+operator<<(kdbgstream&, const Rosegarden::Segment&);
+
+kdbgstream&
+operator<<(kdbgstream&, const Rosegarden::RealTime&);
+
+kdbgstream&
+operator<<(kdbgstream&, const Rosegarden::Colour&);
+
+kdbgstream&
+operator<<(kdbgstream&, const Rosegarden::Guitar::Chord&);
+
+#else
+
+inline kndbgstream&
+operator<<(kndbgstream &s, const std::string&) { return s; }
+
+inline kndbgstream&
+operator<<(kndbgstream &s, const Rosegarden::Event&) { return s; }
+
+inline kndbgstream&
+operator<<(kndbgstream &s, const Rosegarden::Segment&) { return s; }
+
+inline kndbgstream&
+operator<<(kndbgstream &s, const Rosegarden::RealTime&) { return s; }
+
+inline kndbgstream&
+operator<<(kndbgstream &s, const Rosegarden::Colour&) { return s; }
+
+inline kndbgstream&
+operator<<(kndbgstream &s, const Rosegarden::Guitar::Chord&) { return s; }
+
+#endif
+
+#ifndef NO_TIMING
+
+#include <iostream>
+#include <ctime>
+
+#define START_TIMING \
+ clock_t dbgStart = clock();
+#define ELAPSED_TIME \
+ ((clock() - dbgStart) * 1000 / CLOCKS_PER_SEC)
+#define PRINT_ELAPSED(n) \
+ RG_DEBUG << n << ": " << ELAPSED_TIME << "ms elapsed" << endl;
+
+#else
+
+#define START_TIMING
+#define ELAPSED_TIME 0
+#define PRINT_ELAPSED(n)
+
+#endif
+
+
+
+
+// This doesn't work - keeping it just in case I somehow get it
+// working someday
+
+#ifdef NOT_DEFINED
+
+// can't be bothered to even get this to compile with gcc-3.0 at the
+// moment
+
+class kdbgostreamAdapter : public std::ostream
+{
+public:
+ kdbgostreamAdapter(kdbgstream &e) : m_kdbgStream(e) {}
+
+ std::ostream& operator<<(bool i);
+ std::ostream& operator<<(short i);
+ std::ostream& operator<<(unsigned short i);
+ std::ostream& operator<<(char i);
+ std::ostream& operator<<(unsigned char i);
+ std::ostream& operator<<(int i);
+ std::ostream& operator<<(unsigned int i);
+ std::ostream& operator<<(long i);
+ std::ostream& operator<<(unsigned long i);
+ std::ostream& operator<<(const QString& str);
+ std::ostream& operator<<(const char *str);
+ std::ostream& operator<<(const QCString& str);
+ std::ostream& operator<<(void * p);
+ std::ostream& operator<<(KDBGFUNC f);
+ std::ostream& operator<<(double d);
+
+ kdbgstream& dbgStream() { return m_kdbgStream; }
+
+protected:
+ kdbgstream &m_kdbgStream;
+};
+
+#endif
+
+// std::ostream& endl(std::ostream& s);
+
+void DBCheckThrow();
+
+
+#endif
diff --git a/src/misc/Strings.cpp b/src/misc/Strings.cpp
new file mode 100644
index 0000000..3c139dc
--- /dev/null
+++ b/src/misc/Strings.cpp
@@ -0,0 +1,110 @@
+// -*- c-basic-offset: 4 -*-
+/*
+ Rosegarden
+ A sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <bownie@bownie.com>
+
+ 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 "Strings.h"
+#include "Strings.h"
+
+#include "base/Composition.h"
+#include "base/Segment.h"
+#include "base/Event.h"
+
+#include <qtextcodec.h>
+
+
+
+QString strtoqstr(const std::string &str)
+{
+ return QString::fromUtf8(str.c_str());
+}
+
+QString strtoqstr(const Rosegarden::PropertyName &p)
+{
+ return QString::fromUtf8(p.c_str());
+}
+
+std::string qstrtostr(const QString &qstr)
+{
+ return std::string(qstr.utf8().data());
+}
+
+/**
+ * Unlike strtod(3) or QString::toDouble(), this is locale-independent
+ * and always uses '.' as a decimal point. We use it when reading
+ * things like configuration values from XML files where we want to
+ * guarantee the same value is used regardless of surrounding locale.
+ */
+double strtodouble(const std::string &s)
+{
+ int dp = 0;
+ int sign = 1;
+ int i = 0;
+ double result = 0.0;
+ size_t len = s.length();
+
+ result = 0.0;
+
+ while (i < len && isspace(s[i]))
+ ++i;
+
+ if (i < len && s[i] == '-')
+ sign = -1;
+
+ while (i < len) {
+
+ char c = s[i];
+
+ if (isdigit(c)) {
+
+ double d = c - '0';
+
+ if (dp > 0) {
+ for (int p = dp; p > 0; --p)
+ d /= 10.0;
+ ++dp;
+ } else {
+ result *= 10.0;
+ }
+
+ result += d;
+
+ } else if (c == '.') {
+ dp = 1;
+ }
+
+ ++i;
+ }
+
+ return result * sign;
+}
+
+double qstrtodouble(const QString &s)
+{
+ return strtodouble(qstrtostr(s));
+}
+
+std::string
+convertFromCodec(std::string text, QTextCodec *codec)
+{
+ if (codec)
+ return qstrtostr(codec->toUnicode(text.c_str(), text.length()));
+ else
+ return text;
+}
+
diff --git a/src/misc/Strings.h b/src/misc/Strings.h
new file mode 100644
index 0000000..898a775
--- /dev/null
+++ b/src/misc/Strings.h
@@ -0,0 +1,38 @@
+// -*- c-basic-offset: 4 -*-
+/*
+ Rosegarden
+ A sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <bownie@bownie.com>
+
+ 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 _ROSE_STRINGS_H_
+#define _ROSE_STRINGS_H_
+
+#include <string>
+#include <qstring.h>
+#include "PropertyName.h"
+#include "Exception.h"
+
+extern QString strtoqstr(const std::string &);
+extern QString strtoqstr(const Rosegarden::PropertyName &);
+extern std::string qstrtostr(const QString &);
+extern double strtodouble(const std::string &);
+extern double qstrtodouble(const QString &);
+
+class QTextCodec;
+extern std::string convertFromCodec(std::string, QTextCodec *);
+
+#endif
diff --git a/src/misc/stableheaders.h b/src/misc/stableheaders.h
new file mode 100644
index 0000000..db6b86e
--- /dev/null
+++ b/src/misc/stableheaders.h
@@ -0,0 +1,208 @@
+#ifndef STABLEHEADERS_H_
+#define STABLEHEADERS_H_
+
+// Standard C++ library
+#include <algorithm>
+#include <cassert>
+#include <cctype>
+#include <cmath>
+#include <cstdio>
+#include <cstdlib>
+#include <cstring>
+#include <ctime>
+#include <deque>
+#include <exception>
+#include <fstream>
+#include <iomanip>
+#include <ios>
+#include <iostream>
+#include <iterator>
+#include <list>
+#include <map>
+#include <set>
+#include <sstream>
+#include <stack>
+#include <string>
+#include <strstream>
+#include <utility>
+#include <vector>
+
+// QT3 headers
+
+// Common headers, or used by sources generated by moc
+#include <qmap.h>
+#include <qglobal.h>
+#include <private/qucomextra_p.h>
+#include <qmetaobject.h>
+#include <qobjectdefs.h>
+#include <qsignalslotimp.h>
+#include <qstyle.h>
+
+// Headers used by Rosegarden or KDE3
+#include <qaccel.h>
+#include <qapplication.h>
+#include <qbitmap.h>
+#include <qbrush.h>
+#include <qbuffer.h>
+#include <qbuttongroup.h>
+#include <qbutton.h>
+#include <qcanvas.h>
+#include <qcheckbox.h>
+#include <qcolor.h>
+#include <qcombobox.h>
+#include <qcstring.h>
+#include <qcursor.h>
+#include <qdatastream.h>
+#include <qdatetime.h>
+#include <qdialog.h>
+#include <qdict.h>
+#include <qdir.h>
+#include <qdom.h>
+#include <qdragobject.h>
+#include <qdrawutil.h>
+#include <qevent.h>
+#include <qeventloop.h>
+#include <qfile.h>
+#include <qfileinfo.h>
+#include <qfont.h>
+#include <qfontinfo.h>
+#include <qfontmetrics.h>
+#include <qframe.h>
+#include <qgarray.h>
+#include <qgrid.h>
+#include <qgroupbox.h>
+#include <qguardedptr.h>
+#include <qhbox.h>
+#include <qheader.h>
+#include <qiconset.h>
+#include <qimage.h>
+#include <qinputdialog.h>
+#include <qlabel.h>
+#include <qlayout.h>
+#include <qlineedit.h>
+#include <qlistbox.h>
+#include <qlistview.h>
+#include <qmemarray.h>
+#include <qmutex.h>
+#include <qobject.h>
+#include <qobjectlist.h>
+#include <qpaintdevicemetrics.h>
+#include <qpainter.h>
+#include <qpalette.h>
+#include <qpen.h>
+#include <qpixmap.h>
+#include <qpointarray.h>
+#include <qpoint.h>
+#include <qpopupmenu.h>
+#include <qprinter.h>
+#include <qprogressdialog.h>
+#include <qptrdict.h>
+#include <qptrlist.h>
+#include <qpushbutton.h>
+#include <qradiobutton.h>
+#include <qrect.h>
+#include <qregexp.h>
+#include <qregion.h>
+#include <qscrollbar.h>
+#include <qscrollview.h>
+#include <qsessionmanager.h>
+#include <qsignalmapper.h>
+#include <qsize.h>
+#include <qsizepolicy.h>
+#include <qslider.h>
+#include <qsocketnotifier.h>
+#include <qspinbox.h>
+#include <qsplitter.h>
+#include <qstring.h>
+#include <qstringlist.h>
+#include <qstrlist.h>
+#include <qtable.h>
+#include <qtabwidget.h>
+#include <qtextcodec.h>
+#include <qtextedit.h>
+#include <qtextstream.h>
+#include <qthread.h>
+#include <qtimer.h>
+#include <qtoolbutton.h>
+#include <qtooltip.h>
+#include <qvalidator.h>
+#include <qvaluelist.h>
+#include <qvaluevector.h>
+#include <qvariant.h>
+#include <qvbox.h>
+#include <qvgroupbox.h>
+#include <qwhatsthis.h>
+#include <qwidget.h>
+#include <qwidgetstack.h>
+#include <qwmatrix.h>
+#include <qxml.h>
+
+// KDE3 headers
+#include <dcopclient.h>
+#include <dcopobject.h>
+#include <dcopref.h>
+#include <kaboutdata.h>
+#include <kaccel.h>
+#include <kactioncollection.h>
+#include <kaction.h>
+#include <kapp.h>
+#include <kapplication.h>
+#include <karrowbutton.h>
+#include <kcmdlineargs.h>
+#include <kcolordialog.h>
+#include <kcombobox.h>
+#include <kcommand.h>
+#include <kcompletion.h>
+#include <kconfig.h>
+#include <kcursor.h>
+#include <kdcopactionproxy.h>
+#include <kdebug.h>
+#include <kdeversion.h>
+#include <kdialogbase.h>
+#include <kdialog.h>
+#include <kdiskfreesp.h>
+#include <kdockwidget.h>
+#include <kedittoolbar.h>
+#include <kfiledialog.h>
+#include <kfile.h>
+#include <kfilterdev.h>
+#include <kfontrequester.h>
+#include <kglobal.h>
+#include <kglobalsettings.h>
+#include <kiconloader.h>
+#include <kinputdialog.h>
+#include <kio/netaccess.h>
+#include <kkeydialog.h>
+#include <kled.h>
+#include <klineeditdlg.h>
+#include <klineedit.h>
+#include <klistview.h>
+#include <klocale.h>
+#include <kmainwindow.h>
+#include <kmessagebox.h>
+#include <kmimetype.h>
+#include <kpixmapeffect.h>
+#include <kpopupmenu.h>
+#include <kprinter.h>
+#include <kprocess.h>
+#include <kpushbutton.h>
+#include <ksqueezedtextlabel.h>
+#include <kstandarddirs.h>
+#include <kstatusbar.h>
+#include <kstdaccel.h>
+#include <kstdaction.h>
+#include <kstddirs.h>
+#include <ktabwidget.h>
+#include <ktempfile.h>
+#include <ktip.h>
+#include <ktoolbar.h>
+#include <kuniqueapplication.h>
+#include <kurl.h>
+#include <kxmlguiclient.h>
+#include <kxmlguifactory.h>
+
+#define private protected // fugly
+#include <kprogress.h>
+#undef private
+
+#endif /*STABLEHEADERS_H_*/
diff --git a/src/sequencer/ControlBlockMmapper.cpp b/src/sequencer/ControlBlockMmapper.cpp
new file mode 100644
index 0000000..d9bf83d
--- /dev/null
+++ b/src/sequencer/ControlBlockMmapper.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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#include "ControlBlockMmapper.h"
+
+#include <fcntl.h>
+#include <unistd.h>
+#include <errno.h>
+#include <sys/mman.h>
+
+#include "misc/Debug.h"
+
+namespace Rosegarden
+{
+
+ControlBlockMmapper::ControlBlockMmapper(QString fileName)
+ : m_fileName(fileName),
+ m_fd( -1),
+ m_mmappedBuffer(0),
+ m_mmappedSize(sizeof(ControlBlock)),
+ m_controlBlock(0)
+{
+ m_fd = ::open(m_fileName.latin1(), O_RDWR);
+
+ if (m_fd < 0) {
+ SEQMAN_DEBUG << "ControlBlockMmapper : Couldn't open " << m_fileName
+ << endl;
+ throw Exception(std::string("Couldn't open ")
+ + m_fileName.latin1());
+ }
+
+ //
+ // mmap() file for reading
+ //
+ m_mmappedBuffer = (char*)::mmap(0, m_mmappedSize,
+ PROT_READ, MAP_SHARED, m_fd, 0);
+
+ if (m_mmappedBuffer == (void*) - 1) {
+
+ SEQUENCER_DEBUG << QString("mmap failed : (%1) %2\n").
+ arg(errno).arg(strerror(errno));
+
+ throw Exception("mmap failed");
+ }
+
+ SEQMAN_DEBUG << "ControlBlockMmapper : mmap size : " << m_mmappedSize
+ << " at " << (void*)m_mmappedBuffer << endl;
+
+ // Create new control block on file
+ m_controlBlock = new (m_mmappedBuffer) ControlBlock;
+}
+
+ControlBlockMmapper::~ControlBlockMmapper()
+{
+ ::munmap(m_mmappedBuffer, m_mmappedSize);
+ ::close(m_fd);
+}
+
+}
diff --git a/src/sequencer/ControlBlockMmapper.h b/src/sequencer/ControlBlockMmapper.h
new file mode 100644
index 0000000..55d4c9f
--- /dev/null
+++ b/src/sequencer/ControlBlockMmapper.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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _MMAPPEDCONTROLBLOCK_H_
+#define _MMAPPEDCONTROLBLOCK_H_
+
+#include <qstring.h>
+
+#include "sound/MappedEvent.h"
+#include "sound/ControlBlock.h"
+
+namespace Rosegarden
+{
+
+class ControlBlockMmapper
+{
+public:
+ ControlBlockMmapper(QString fileName);
+ ~ControlBlockMmapper();
+
+ QString getFileName() { return m_fileName; }
+
+ // delegate ControlBlock's interface
+ InstrumentId getInstrumentForTrack(unsigned int trackId)
+ { return m_controlBlock->getInstrumentForTrack(trackId); }
+
+ InstrumentId getInstrumentForEvent(unsigned int dev,
+ unsigned int chan)
+ { return m_controlBlock->getInstrumentForEvent(dev, chan); }
+
+ bool isTrackMuted(unsigned int trackId)
+ { return m_controlBlock->isTrackMuted(trackId); }
+
+ bool isTrackArmed(unsigned int trackId)
+ { return m_controlBlock->isTrackArmed(trackId); }
+
+ InstrumentId getInstrumentForMetronome()
+ { return m_controlBlock->getInstrumentForMetronome(); }
+
+ bool isMetronomeMuted() { return m_controlBlock->isMetronomeMuted(); }
+
+ bool isSolo() { return m_controlBlock->isSolo(); }
+
+ bool isMidiRoutingEnabled()
+ { return m_controlBlock->isMidiRoutingEnabled(); }
+
+ TrackId getSelectedTrack()
+ { return m_controlBlock->getSelectedTrack(); }
+
+ MidiFilter getThruFilter()
+ { return m_controlBlock->getThruFilter(); }
+
+ MidiFilter getRecordFilter()
+ { return m_controlBlock->getRecordFilter(); }
+
+ // for transfer to SequencerMmapper
+ ControlBlock *getControlBlock()
+ { return m_controlBlock; }
+
+protected:
+
+ //--------------- Data members ---------------------------------
+ QString m_fileName;
+ int m_fd;
+ void* m_mmappedBuffer;
+ size_t m_mmappedSize;
+ ControlBlock* m_controlBlock;
+};
+
+}
+
+#endif // _MMAPPEDCONTROLBLOCK_H_
diff --git a/src/sequencer/MmappedSegment.cpp b/src/sequencer/MmappedSegment.cpp
new file mode 100644
index 0000000..57b3dc9
--- /dev/null
+++ b/src/sequencer/MmappedSegment.cpp
@@ -0,0 +1,702 @@
+
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
+
+/*
+ Rosegarden
+ A MIDI and audio sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#include "MmappedSegment.h"
+#include "misc/Debug.h"
+
+#include "sound/MappedComposition.h"
+#include "sound/Midi.h"
+
+//#define DEBUG_META_ITERATOR 1
+//#define DEBUG_PLAYING_AUDIO_FILES 1
+
+namespace Rosegarden
+{
+
+MmappedSegment::MmappedSegment(const QString filename)
+ : m_fd( -1),
+ m_mmappedSize(0),
+ m_mmappedRegion(0),
+ m_mmappedEventBuffer((MappedEvent*)0),
+ m_filename(filename)
+{
+ SEQUENCER_DEBUG << "mmapping " << filename << endl;
+
+ map();
+}
+
+bool MmappedSegment::isMetronome()
+{
+ return (getFileName().contains("metronome", false) > 0);
+}
+
+
+
+void MmappedSegment::map()
+{
+ QFileInfo fInfo(m_filename);
+ if (!fInfo.exists()) {
+ SEQUENCER_DEBUG << "MmappedSegment::map() : file " << m_filename << " doesn't exist\n";
+ throw Exception("file not found");
+ }
+
+ m_mmappedSize = fInfo.size();
+
+ m_fd = ::open(m_filename.latin1(), O_RDWR);
+
+ m_mmappedRegion = ::mmap(0, m_mmappedSize, PROT_READ, MAP_SHARED, m_fd, 0);
+
+ if (m_mmappedRegion == (void*) - 1) {
+
+ SEQUENCER_DEBUG << QString("mmap failed : (%1) %2\n").
+ arg(errno).arg(strerror(errno));
+
+ throw Exception("mmap failed");
+ }
+
+ m_mmappedEventBuffer = (MappedEvent *)((size_t *)m_mmappedRegion + 1);
+
+ SEQUENCER_DEBUG << "MmappedSegment::map() : "
+ << (void*)m_mmappedRegion << "," << m_mmappedSize << endl;
+
+}
+
+MmappedSegment::~MmappedSegment()
+{
+ unmap();
+}
+
+void MmappedSegment::unmap()
+{
+ ::munmap(m_mmappedRegion, m_mmappedSize);
+ ::close(m_fd);
+}
+
+size_t
+MmappedSegment::getNbMappedEvents() const
+{
+ if (m_mmappedRegion || !m_mmappedSize) {
+
+ // The shared memory area consists of one size_t giving the
+ // number of following mapped events, followed by the mapped
+ // events themselves.
+
+ // So this is the number of mapped events that we expect:
+ size_t nominal = *(size_t *)m_mmappedRegion;
+
+ // But we want to be sure not to read off the end of the
+ // shared memory area, so just in case, this is the number of
+ // events that can actually be accommodated in the memory area
+ // as we see it:
+ size_t actual = (m_mmappedSize - sizeof(size_t)) /
+ sizeof(MappedEvent);
+
+ return std::min(nominal, actual);
+
+ } else
+ return 0;
+}
+
+bool MmappedSegment::remap(size_t newSize)
+{
+ SEQUENCER_DEBUG << "remap() from " << m_mmappedSize << " to "
+ << newSize << endl;
+
+ if (m_mmappedSize == newSize) {
+
+ SEQUENCER_DEBUG << "remap() : sizes are identical, remap not forced - "
+ << "nothing to do\n";
+ return false;
+ }
+
+#ifdef linux
+ void *oldRegion = m_mmappedRegion;
+ m_mmappedRegion = (MappedEvent*)::mremap(m_mmappedRegion, m_mmappedSize, newSize, MREMAP_MAYMOVE);
+ if (m_mmappedRegion != oldRegion) {
+ SEQUENCER_DEBUG << "NOTE: buffer moved from " << oldRegion <<
+ " to " << (void *)m_mmappedRegion << endl;
+ }
+#else
+ ::munmap(m_mmappedRegion, m_mmappedSize);
+ m_mmappedRegion = (MappedEvent*)::mmap(0, newSize, PROT_READ, MAP_SHARED, m_fd, 0);
+#endif
+
+ if (m_mmappedRegion == (void*) - 1) {
+
+ SEQUENCER_DEBUG << QString("mremap failed : (%1) %2\n").
+ arg(errno).arg(strerror(errno));
+
+ throw Exception("mremap failed");
+ }
+
+ m_mmappedEventBuffer = (MappedEvent *)((size_t *)m_mmappedRegion + 1);
+ m_mmappedSize = newSize;
+
+ return true;
+}
+
+MmappedSegment::iterator::iterator(MmappedSegment* s)
+ : m_s(s), m_currentEvent(m_s->getBuffer())
+{}
+
+MmappedSegment::iterator& MmappedSegment::iterator::operator=(const iterator& it)
+{
+ if (&it == this)
+ return * this;
+
+ m_s = it.m_s;
+ m_currentEvent = it.m_currentEvent;
+
+ return *this;
+}
+
+MmappedSegment::iterator& MmappedSegment::iterator::operator++()
+{
+ if (!atEnd()) {
+
+ do
+ ++m_currentEvent;
+ while (!atEnd() && (m_currentEvent->getType() == 0));
+ // skip null events - there can be some if the file has been
+ // zeroed out after events have been deleted
+
+ } else {
+
+ SEQUENCER_DEBUG << "MmappedSegment::iterator::operator++() " << this
+ << " - reached end of stream\n";
+
+ }
+
+ return *this;
+}
+
+MmappedSegment::iterator MmappedSegment::iterator::operator++(int)
+{
+ iterator r = *this;
+
+ if (!atEnd()) {
+ do
+ ++m_currentEvent;
+ while (!atEnd() && m_currentEvent->getType() == 0);
+
+ }
+
+ return r;
+}
+
+MmappedSegment::iterator& MmappedSegment::iterator::operator+=(int offset)
+{
+ m_currentEvent += offset;
+
+ if (atEnd()) {
+ m_currentEvent = m_s->getBuffer() + m_s->getNbMappedEvents();
+ }
+
+ return *this;
+}
+
+MmappedSegment::iterator& MmappedSegment::iterator::operator-=(int offset)
+{
+ m_currentEvent -= offset;
+ if (m_currentEvent < m_s->getBuffer()) {
+ m_currentEvent = m_s->getBuffer();
+ }
+
+ return *this;
+}
+
+
+bool MmappedSegment::iterator::operator==(const iterator& it)
+{
+ return (m_currentEvent == it.m_currentEvent) || (atEnd() == it.atEnd());
+}
+
+void MmappedSegment::iterator::reset()
+{
+ m_currentEvent = m_s->getBuffer();
+}
+
+const MappedEvent &MmappedSegment::iterator::operator*()
+{
+ return *m_currentEvent;
+}
+
+bool MmappedSegment::iterator::atEnd() const
+{
+ return (m_currentEvent == 0) ||
+ (m_currentEvent > (m_s->getBuffer() + m_s->getNbMappedEvents() - 1));
+}
+
+//----------------------------------------
+
+MmappedSegmentsMetaIterator::MmappedSegmentsMetaIterator(
+ mmappedsegments& segments,
+ ControlBlockMmapper* controlBlockMmapper)
+ : m_controlBlockMmapper(controlBlockMmapper),
+ m_segments(segments)
+{
+ for (mmappedsegments::iterator i = m_segments.begin();
+ i != m_segments.end(); ++i)
+ m_iterators.push_back(new MmappedSegment::iterator(i->second));
+}
+
+MmappedSegmentsMetaIterator::~MmappedSegmentsMetaIterator()
+{
+ clear();
+}
+
+void MmappedSegmentsMetaIterator::addSegment(MmappedSegment* ms)
+{
+ MmappedSegment::iterator* iter = new MmappedSegment::iterator(ms);
+ moveIteratorToTime(*iter, m_currentTime);
+ m_iterators.push_back(iter);
+}
+
+void MmappedSegmentsMetaIterator::deleteSegment(MmappedSegment* ms)
+{
+ for (segmentiterators::iterator i = m_iterators.begin(); i != m_iterators.end(); ++i) {
+ if ((*i)->getSegment() == ms) {
+ SEQUENCER_DEBUG << "deleteSegment : found segment to delete : "
+ << ms->getFileName() << endl;
+ delete (*i);
+ m_iterators.erase(i);
+ break;
+ }
+ }
+}
+
+void MmappedSegmentsMetaIterator::clear()
+{
+ for (unsigned int i = 0; i < m_iterators.size(); ++i)
+ delete m_iterators[i];
+
+ m_iterators.clear();
+}
+
+void MmappedSegmentsMetaIterator::reset()
+{
+ m_currentTime.sec = m_currentTime.nsec = 0;
+
+ for (segmentiterators::iterator i = m_iterators.begin(); i != m_iterators.end(); ++i) {
+ (*i)->reset();
+ }
+
+}
+
+bool MmappedSegmentsMetaIterator::jumpToTime(const RealTime& startTime)
+{
+ SEQUENCER_DEBUG << "jumpToTime(" << startTime << ")" << endl;
+
+ reset();
+
+ bool res = true;
+
+ m_currentTime = startTime;
+
+ for (segmentiterators::iterator i = m_iterators.begin(); i != m_iterators.end(); ++i)
+ if (!moveIteratorToTime(*(*i), startTime))
+ res = false;
+
+ return res;
+}
+
+bool MmappedSegmentsMetaIterator::moveIteratorToTime(MmappedSegment::iterator& iter,
+ const RealTime& startTime)
+{
+ while ((!iter.atEnd()) &&
+ (iter.peek()->getEventTime() < startTime) &&
+ ((iter.peek()->getEventTime() + iter.peek()->getDuration()) < startTime)
+ ) {
+ ++iter;
+ }
+ bool res = !iter.atEnd();
+
+ return res;
+}
+
+bool MmappedSegmentsMetaIterator::acceptEvent(MappedEvent *evt, bool evtIsFromMetronome)
+{
+ if (evt->getType() == 0)
+ return false; // discard those right away
+
+ if (evtIsFromMetronome) {
+ if (evt->getType() == MappedEvent::MidiSystemMessage &&
+ evt->getData1() == MIDI_TIMING_CLOCK) {
+ /*
+ std::cout << "MmappedSegmentsMetaIterator::acceptEvent - "
+ << "found clock" << std::endl;
+ */
+ return true;
+ }
+
+ return !m_controlBlockMmapper->isMetronomeMuted();
+ }
+
+ // else, evt is not from metronome : first check if we're soloing (i.e. playing only the selected track)
+ if (m_controlBlockMmapper->isSolo())
+ return (evt->getTrackId() == m_controlBlockMmapper->getSelectedTrack());
+
+ // finally we're not soloing, so check if track is muted
+ TrackId track = evt->getTrackId();
+ bool muted = m_controlBlockMmapper->isTrackMuted(evt->getTrackId());
+
+#ifdef DEBUG_META_ITERATOR
+
+ SEQUENCER_DEBUG << "MSMI::acceptEvent: track " << track << " muted status: " << muted << endl;
+#endif
+
+ return !muted;
+}
+
+
+bool
+MmappedSegmentsMetaIterator::fillCompositionWithEventsUntil(bool /*firstFetch*/,
+ MappedComposition* c,
+ const RealTime& startTime,
+ const RealTime& endTime)
+{
+#ifdef DEBUG_META_ITERATOR
+ SEQUENCER_DEBUG << "MSMI::fillCompositionWithEventsUntil " << startTime << " -> " << endTime << endl;
+#endif
+
+ m_currentTime = endTime;
+
+ // keep track of the segments which still have valid events
+ std::vector<bool> validSegments;
+ for (unsigned int i = 0; i < m_segments.size(); ++i)
+ validSegments.push_back(true);
+
+ bool foundOneEvent = false, eventsRemaining = false;
+
+ do {
+ foundOneEvent = false;
+
+ for (unsigned int i = 0; i < m_iterators.size(); ++i) {
+
+ MmappedSegment::iterator* iter = m_iterators[i];
+
+ //std::cerr << "Iterating on Segment #" << i << std::endl;
+
+#ifdef DEBUG_META_ITERATOR
+
+ SEQUENCER_DEBUG << "MSMI::fillCompositionWithEventsUntil : "
+ << "checking segment #" << i << " "
+ << iter->getSegment()->getFileName() << endl;
+#endif
+
+ if (!validSegments[i]) {
+#ifdef DEBUG_META_ITERATOR
+ SEQUENCER_DEBUG << "MSMI::fillCompositionWithEventsUntil : "
+ << "no more events to get for this slice "
+ << "in segment #" << i << endl;
+#endif
+
+ continue; // skip this segment
+ }
+
+ bool evtIsFromMetronome = iter->getSegment()->isMetronome();
+
+ if (iter->atEnd()) {
+#ifdef DEBUG_META_ITERATOR
+ SEQUENCER_DEBUG << "MSMI::fillCompositionWithEventsUntil : "
+ << endTime
+ << " reached end of segment #"
+ << i << endl;
+#endif
+
+ continue;
+ } else if (!evtIsFromMetronome) {
+ eventsRemaining = true;
+ }
+
+ if ((**iter).getEventTime() < endTime) {
+
+ MappedEvent *evt = new MappedEvent(*(*iter));
+
+ // set event's instrument
+ //
+ if (evtIsFromMetronome) {
+
+ evt->setInstrument(m_controlBlockMmapper->
+ getInstrumentForMetronome());
+
+ } else {
+
+ evt->setInstrument(m_controlBlockMmapper->
+ getInstrumentForTrack(evt->getTrackId()));
+
+ }
+
+#ifdef DEBUG_META_ITERATOR
+ SEQUENCER_DEBUG << "MSMI::fillCompositionWithEventsUntil : " << endTime
+ << " inserting evt from segment #"
+ << i
+ << " : trackId: " << evt->getTrackId()
+ << " - inst: " << evt->getInstrument()
+ << " - type: " << evt->getType()
+ << " - time: " << evt->getEventTime()
+ << " - duration: " << evt->getDuration()
+ << " - data1: " << (unsigned int)evt->getData1()
+ << " - data2: " << (unsigned int)evt->getData2()
+ << " - metronome event: " << evtIsFromMetronome
+ << endl;
+#endif
+
+ if (evt->getType() == MappedEvent::TimeSignature) {
+
+ // Process time sig and tempo changes along with
+ // everything else, as the sound driver probably
+ // wants to know when they happen
+
+ c->insert(evt);
+
+ } else if (evt->getType() == MappedEvent::Tempo) {
+
+ c->insert(evt);
+
+ } else if (evt->getType() == MappedEvent::MidiSystemMessage &&
+
+ // #1048388:
+ // Ensure sysex heeds mute status, but ensure
+ // clocks etc still get through
+ evt->getData1() != MIDI_SYSTEM_EXCLUSIVE) {
+
+ c->insert(evt);
+
+ } else if (acceptEvent(evt, evtIsFromMetronome) &&
+
+ ((evt->getEventTime() + evt->getDuration() > startTime) ||
+ (evt->getDuration() == RealTime::zeroTime &&
+ evt->getEventTime() == startTime))) {
+
+ // std::cout << "inserting event" << std::endl;
+
+ /*
+ std::cout << "Inserting event (type = "
+ << evt->getType() << ")" << std::endl;
+ */
+
+
+ c->insert(evt);
+
+ } else {
+
+#ifdef DEBUG_META_ITERATOR
+ std::cout << "MSMI: skipping event"
+ << " - event time = " << evt->getEventTime()
+ << ", duration = " << evt->getDuration()
+ << ", startTime = " << startTime << std::endl;
+#endif
+
+ delete evt;
+ }
+
+ if (!evtIsFromMetronome)
+ foundOneEvent = true;
+ ++(*iter);
+
+ } else {
+ validSegments[i] = false; // no more events to get from this segment
+#ifdef DEBUG_META_ITERATOR
+
+ SEQUENCER_DEBUG << "fillCompositionWithEventsUntil : no more events to get from segment #"
+ << i << endl;
+#endif
+
+ }
+
+ }
+
+ } while (foundOneEvent);
+
+#ifdef DEBUG_META_ITERATOR
+
+ SEQUENCER_DEBUG << "fillCompositionWithEventsUntil : eventsRemaining = " << eventsRemaining << endl;
+#endif
+
+ return eventsRemaining || foundOneEvent;
+}
+
+void MmappedSegmentsMetaIterator::resetIteratorForSegment(const QString& filename)
+{
+ for (segmentiterators::iterator i = m_iterators.begin(); i != m_iterators.end(); ++i) {
+ MmappedSegment::iterator* iter = *i;
+
+ if (iter->getSegment()->getFileName() == filename) {
+ SEQUENCER_DEBUG << "MSMI::resetIteratorForSegment(" << filename << ") : found iterator\n";
+ // delete iterator and create another one
+ MmappedSegment* ms = (*i)->getSegment();
+ delete iter;
+ m_iterators.erase(i);
+ iter = new MmappedSegment::iterator(ms);
+ m_iterators.push_back(iter);
+ moveIteratorToTime(*iter, m_currentTime);
+ break;
+ }
+
+ }
+}
+
+void
+MmappedSegmentsMetaIterator::getAudioEvents(std::vector<MappedEvent> &v)
+{
+ v.clear();
+
+ for (mmappedsegments::iterator i = m_segments.begin();
+ i != m_segments.end(); ++i) {
+
+ MmappedSegment::iterator itr(i->second);
+
+ while (!itr.atEnd()) {
+
+ if ((*itr).getType() != MappedEvent::Audio) {
+ ++itr;
+ continue;
+ }
+
+ MappedEvent evt(*itr);
+ ++itr;
+
+ if (m_controlBlockMmapper->isTrackMuted(evt.getTrackId())) {
+#ifdef DEBUG_PLAYING_AUDIO_FILES
+ std::cout << "MSMI::getAudioEvents - "
+ << "track " << evt.getTrackId() << " is muted" << std::endl;
+#endif
+
+ continue;
+ }
+
+ if (m_controlBlockMmapper->isSolo() == true &&
+ evt.getTrackId() != m_controlBlockMmapper->getSelectedTrack()) {
+#ifdef DEBUG_PLAYING_AUDIO_FILES
+ std::cout << "MSMI::getAudioEvents - "
+ << "track " << evt.getTrackId() << " is not solo track" << std::endl;
+#endif
+
+ continue;
+ }
+
+ v.push_back(evt);
+ }
+ }
+}
+
+
+std::vector<MappedEvent>&
+MmappedSegmentsMetaIterator::getPlayingAudioFiles(const RealTime &
+ songPosition)
+{
+ // Clear playing audio segments
+ //
+ m_playingAudioSegments.clear();
+
+#ifdef DEBUG_PLAYING_AUDIO_FILES
+
+ std::cout << "MSMI::getPlayingAudioFiles" << std::endl;
+#endif
+
+ for (mmappedsegments::iterator i = m_segments.begin();
+ i != m_segments.end(); ++i) {
+
+ MmappedSegment::iterator iter(i->second);
+
+ bool found = false;
+
+ //!!! any point to this loop at all? can found ever fail?
+ for (segmentiterators::iterator sI = m_iterators.begin();
+ sI != m_iterators.end(); ++sI) {
+ if ((*sI)->getSegment() == iter.getSegment())
+ found = true;
+ }
+
+ if (!found)
+ continue;
+
+ while (!iter.atEnd()) {
+ if ((*iter).getType() != MappedEvent::Audio) {
+ ++iter;
+ continue;
+ }
+
+ //std::cout << "CONSTRUCTING MAPPEDEVENT" << std::endl;
+ MappedEvent evt(*iter);
+
+ // Check for this track being muted or soloed
+ //
+ if (m_controlBlockMmapper->isTrackMuted(evt.getTrackId()) == true) {
+#ifdef DEBUG_PLAYING_AUDIO_FILES
+ std::cout << "MSMI::getPlayingAudioFiles - "
+ << "track " << evt.getTrackId() << " is muted" << std::endl;
+#endif
+
+ ++iter;
+ continue;
+ }
+
+ if (m_controlBlockMmapper->isSolo() == true &&
+ evt.getTrackId() != m_controlBlockMmapper->getSelectedTrack()) {
+#ifdef DEBUG_PLAYING_AUDIO_FILES
+ std::cout << "MSMI::getPlayingAudioFiles - "
+ << "track " << evt.getTrackId() << " is not solo track" << std::endl;
+#endif
+
+ ++iter;
+ continue;
+ }
+
+ // If there's an audio event and it should be playing at this time
+ // then flag as such.
+ //
+ if (songPosition > evt.getEventTime() - RealTime(1, 0) &&
+ songPosition < evt.getEventTime() + evt.getDuration()) {
+
+#ifdef DEBUG_PLAYING_AUDIO_FILES
+ std::cout << "MSMI::getPlayingAudioFiles - "
+ << "instrument id = " << evt.getInstrument() << std::endl;
+
+
+ std::cout << "MSMI::getPlayingAudioFiles - "
+ << " id " << evt.getRuntimeSegmentId() << ", audio event time = " << evt.getEventTime() << std::endl;
+ std::cout << "MSMI::getPlayingAudioFiles - "
+ << "audio event duration = " << evt.getDuration() << std::endl;
+
+
+#endif // DEBUG_PLAYING_AUDIO_FILES
+
+ m_playingAudioSegments.push_back(evt);
+ }
+
+ ++iter;
+ }
+
+ //std::cout << "END OF ITERATOR" << std::endl << std::endl;
+
+ }
+
+ return m_playingAudioSegments;
+}
+
+}
+
diff --git a/src/sequencer/MmappedSegment.h b/src/sequencer/MmappedSegment.h
new file mode 100644
index 0000000..32e2ea8
--- /dev/null
+++ b/src/sequencer/MmappedSegment.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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _MMAPPED_SEGMENT_H_
+#define _MMAPPED_SEGMENT_H_
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <sys/mman.h>
+#include <unistd.h>
+#include <errno.h>
+
+#include <iostream>
+
+#include <klocale.h>
+#include <kstandarddirs.h>
+
+#include <dcopclient.h>
+#include <qdatetime.h>
+#include <qstring.h>
+#include <qdir.h>
+#include <qbuffer.h>
+
+#include "ControlBlockMmapper.h"
+#include "sound/MappedInstrument.h"
+
+namespace Rosegarden {
+
+class MappedComposition;
+
+// Seems not to be properly defined under some gcc 2.95 setups
+#ifndef MREMAP_MAYMOVE
+# define MREMAP_MAYMOVE 1
+#endif
+
+/**
+ * An mmap()ed segment
+ */
+class MmappedSegment
+{
+public:
+ MmappedSegment(const QString filename);
+ ~MmappedSegment();
+
+ bool remap(size_t newSize);
+ QString getFileName() const { return m_filename; }
+ bool isMetronome();
+ MappedEvent* getBuffer() { return m_mmappedEventBuffer; }
+ size_t getSize() const { return m_mmappedSize; }
+ size_t getNbMappedEvents() const;
+
+ class iterator
+ {
+ public:
+ iterator(MmappedSegment* s);
+ iterator& operator=(const iterator&);
+ bool operator==(const iterator&);
+ bool operator!=(const iterator& it) { return !operator==(it); }
+
+ bool atEnd() const;
+
+ /// go back to beginning of stream
+ void reset();
+
+ iterator& operator++();
+ iterator operator++(int);
+ iterator& operator+=(int);
+ iterator& operator-=(int);
+
+ const MappedEvent &operator*();
+ const MappedEvent* peek() const { return m_currentEvent; }
+
+ MmappedSegment* getSegment() { return m_s; }
+ const MmappedSegment* getSegment() const { return m_s; }
+
+ private:
+ iterator();
+
+ protected:
+ //--------------- Data members ---------------------------------
+
+ MmappedSegment* m_s;
+ MappedEvent* m_currentEvent;
+ };
+
+protected:
+ void map();
+ void unmap();
+
+ //--------------- Data members ---------------------------------
+ int m_fd;
+ size_t m_mmappedSize;
+// unsigned int m_nbMappedEvents;
+ void *m_mmappedRegion;
+ MappedEvent* m_mmappedEventBuffer;
+ QString m_filename;
+};
+
+class MmappedSegmentsMetaIterator
+{
+public:
+
+ typedef std::map<QString, MmappedSegment*> mmappedsegments;
+
+ MmappedSegmentsMetaIterator(mmappedsegments&,
+ ControlBlockMmapper*);
+ ~MmappedSegmentsMetaIterator();
+
+ /// reset all iterators to beginning
+ void reset();
+ bool jumpToTime(const RealTime&);
+
+ /**
+ * Fill mapped composition with events from current point until
+ * specified time @return true if there are non-metronome events
+ * remaining, false if end of composition was reached
+ */
+ bool fillCompositionWithEventsUntil(bool firstFetch,
+ MappedComposition*,
+ const RealTime& start,
+ const RealTime& end);
+
+ void resetIteratorForSegment(const QString& filename);
+
+ void addSegment(MmappedSegment*);
+ void deleteSegment(MmappedSegment*);
+
+ void getAudioEvents(std::vector<MappedEvent> &);
+
+ // Manipulate a vector of currently mapped audio segments so that we
+ // can cross check them against PlayableAudioFiles (and stop if
+ // necessary). This will account for muting/soloing too I should
+ // hope.
+ //
+ //!!! to be obsoleted, hopefully
+ std::vector<MappedEvent>& getPlayingAudioFiles
+ (const RealTime &songPosition);
+
+protected:
+ bool acceptEvent(MappedEvent*, bool evtIsFromMetronome);
+
+ /// Delete all iterators
+ void clear();
+ bool moveIteratorToTime(MmappedSegment::iterator&,
+ const RealTime&);
+
+ //--------------- Data members ---------------------------------
+
+ ControlBlockMmapper* m_controlBlockMmapper;
+
+ RealTime m_currentTime;
+ mmappedsegments& m_segments;
+
+ typedef std::vector<MmappedSegment::iterator*> segmentiterators;
+ segmentiterators m_iterators;
+
+ std::vector<MappedEvent> m_playingAudioSegments;
+};
+
+}
+
+#endif // _MMAPPED_SEGMENT_H_
diff --git a/src/sequencer/RosegardenSequencerApp.cpp b/src/sequencer/RosegardenSequencerApp.cpp
new file mode 100644
index 0000000..4c26efb
--- /dev/null
+++ b/src/sequencer/RosegardenSequencerApp.cpp
@@ -0,0 +1,1850 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
+
+/*
+ Rosegarden
+ A MIDI and audio sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#include "RosegardenSequencerApp.h"
+#include <kapplication.h>
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <sys/mman.h>
+#include <unistd.h>
+#include <errno.h>
+
+#include <iostream>
+
+#include <klocale.h>
+#include <kstandarddirs.h>
+
+#include <dcopclient.h>
+#include <qdatetime.h>
+#include <qstring.h>
+#include <qdir.h>
+#include <qbuffer.h>
+#include <qvaluevector.h>
+
+#include "misc/Debug.h"
+#include "ControlBlockMmapper.h"
+#include "MmappedSegment.h"
+#include "gui/application/RosegardenDCOP.h"
+#include "sound/ControlBlock.h"
+#include "sound/SoundDriver.h"
+#include "sound/SoundDriverFactory.h"
+#include "sound/MappedInstrument.h"
+#include "base/Profiler.h"
+#include "sound/PluginFactory.h"
+
+namespace Rosegarden
+{
+
+// The default latency and read-ahead values are actually sent
+// down from the GUI every time playback or recording starts
+// so the local values are kind of meaningless.
+//
+//
+RosegardenSequencerApp::RosegardenSequencerApp() :
+ DCOPObject("RosegardenSequencerIface"),
+ m_driver(0),
+ m_transportStatus(STOPPED),
+ m_songPosition(0, 0),
+ m_lastFetchSongPosition(0, 0),
+ m_readAhead(0, 80000000), // default value
+ m_audioMix(0, 60000000), // default value
+ m_audioRead(0, 100000000), // default value
+ m_audioWrite(0, 200000000), // default value
+ m_smallFileSize(128),
+ m_loopStart(0, 0),
+ m_loopEnd(0, 0),
+ m_studio(new MappedStudio()),
+ m_segmentFilesPath(KGlobal::dirs()->resourceDirs("tmp").last()),
+ m_metaIterator(0),
+ m_controlBlockMmapper(0),
+ m_transportToken(1),
+ m_isEndOfCompReached(false)
+{
+ SEQUENCER_DEBUG << "Registering with DCOP server" << endl;
+
+ // Without DCOP we are nothing
+ QCString realAppId = kapp->dcopClient()->registerAs(kapp->name(), false);
+
+ if (realAppId.isNull()) {
+ SEQUENCER_DEBUG << "RosegardenSequencer cannot register "
+ << "with DCOP server" << endl;
+ close();
+ }
+
+ // Initialise the MappedStudio
+ //
+ initialiseStudio();
+
+ // Creating this object also initialises the Rosegarden ALSA/JACK
+ // interface for both playback and recording. MappedStudio
+ // audio faders are also created.
+ //
+ m_driver = SoundDriverFactory::createDriver(m_studio);
+ m_studio->setSoundDriver(m_driver);
+
+ if (!m_driver) {
+ SEQUENCER_DEBUG << "RosegardenSequencer object could not be allocated"
+ << endl;
+ close();
+ }
+
+ m_driver->setAudioBufferSizes(m_audioMix, m_audioRead, m_audioWrite,
+ m_smallFileSize);
+
+ m_driver->setSequencerDataBlock(m_sequencerMapper.getSequencerDataBlock());
+ m_driver->setExternalTransportControl(this);
+
+ // Check for new clients every so often
+ //
+ m_newClientTimer = new QTimer(this);
+ connect(m_newClientTimer, SIGNAL(timeout()),
+ this, SLOT(slotCheckForNewClients()));
+
+ m_newClientTimer->start(3000); // every 3 seconds
+}
+
+RosegardenSequencerApp::~RosegardenSequencerApp()
+{
+ SEQUENCER_DEBUG << "RosegardenSequencer - shutting down" << endl;
+ m_driver->shutdown();
+ delete m_studio;
+ delete m_driver;
+ delete m_controlBlockMmapper;
+}
+
+void
+RosegardenSequencerApp::quit()
+{
+ std::cerr << "RosegardenSequencerApp::quit()" << std::endl;
+
+ close();
+
+ // and break out of the loop next time around
+ m_transportStatus = QUIT;
+}
+
+
+void
+RosegardenSequencerApp::stop()
+{
+ // set our state at this level to STOPPING (pending any
+ // unfinished NOTES)
+ m_transportStatus = STOPPING;
+
+ // report
+ //
+ SEQUENCER_DEBUG << "RosegardenSequencerApp::stop() - stopping" << endl;
+
+ // process pending NOTE OFFs and stop the Sequencer
+ m_driver->stopPlayback();
+
+ // the Sequencer doesn't need to know these once
+ // we've stopped.
+ //
+ m_songPosition.sec = 0;
+ m_songPosition.nsec = 0;
+ m_lastFetchSongPosition.sec = 0;
+ m_lastFetchSongPosition.nsec = 0;
+
+ cleanupMmapData();
+
+ Profiles::getInstance()->dump();
+
+ incrementTransportToken();
+}
+
+// Get a slice of events from the GUI
+//
+void
+RosegardenSequencerApp::fetchEvents(MappedComposition &composition,
+ const RealTime &start,
+ const RealTime &end,
+ bool firstFetch)
+{
+ // Always return nothing if we're stopped
+ //
+ if ( m_transportStatus == STOPPED || m_transportStatus == STOPPING )
+ return ;
+
+ // If we're looping then we should get as much of the rest of
+ // the right hand of the loop as possible and also events from
+ // the beginning of the loop. We can do this in two fetches.
+ // Make sure that we delete all returned pointers when we've
+ // finished with them.
+ //
+ //
+ /*
+ if (isLooping() == true && end >= m_loopEnd)
+ {
+ RealTime loopOverlap = end - m_loopEnd;
+
+ MappedComposition *endLoop = 0;
+
+ if (m_loopEnd > start) {
+ endLoop = getSlice(start, m_loopEnd, firstFetch);
+ }
+
+ if (loopOverlap > RealTime::zeroTime) {
+
+ MappedComposition *beginLoop =
+ getSlice(m_loopStart, m_loopStart + loopOverlap, true);
+
+ // move the start time of the begin section one loop width
+ // into the future and ensure that we keep the clocks level
+ // until this time has passed
+ //
+ beginLoop->moveStartTime(m_loopEnd - m_loopStart);
+
+ if (endLoop) {
+ (*endLoop) = (*endLoop) + (*beginLoop);
+ delete beginLoop;
+ } else {
+ endLoop = beginLoop;
+ }
+ }
+
+ if (endLoop) return endLoop;
+ else return new MappedComposition();
+ }
+ else
+ */
+ getSlice(composition, start, end, firstFetch);
+ applyLatencyCompensation(composition);
+}
+
+
+void
+RosegardenSequencerApp::getSlice(MappedComposition &composition,
+ const RealTime &start,
+ const RealTime &end,
+ bool firstFetch)
+{
+ // SEQUENCER_DEBUG << "RosegardenSequencerApp::getSlice (" << start << " -> " << end << ", " << firstFetch << ")" << endl;
+
+ if (firstFetch || (start < m_lastStartTime)) {
+ SEQUENCER_DEBUG << "[calling jumpToTime on start]" << endl;
+ m_metaIterator->jumpToTime(start);
+ }
+
+ (void)m_metaIterator->fillCompositionWithEventsUntil
+ (firstFetch, &composition, start, end);
+
+ // setEndOfCompReached(eventsRemaining); // don't do that, it breaks recording because
+ // playing stops right after it starts.
+
+ m_lastStartTime = start;
+}
+
+
+void
+RosegardenSequencerApp::applyLatencyCompensation(MappedComposition &composition)
+{
+ RealTime maxLatency = m_driver->getMaximumPlayLatency();
+ if (maxLatency == RealTime::zeroTime)
+ return ;
+
+ for (MappedComposition::iterator i = composition.begin();
+ i != composition.end(); ++i) {
+
+ RealTime instrumentLatency =
+ m_driver->getInstrumentPlayLatency((*i)->getInstrument());
+
+ // std::cerr << "RosegardenSequencerApp::applyLatencyCompensation: maxLatency " << maxLatency << ", instrumentLatency " << instrumentLatency << ", moving " << (*i)->getEventTime() << " to " << (*i)->getEventTime() + maxLatency - instrumentLatency << std::endl;
+
+ (*i)->setEventTime((*i)->getEventTime() +
+ maxLatency - instrumentLatency);
+ }
+}
+
+
+// The first fetch of events from the core/ and initialisation for
+// this session of playback. We fetch up to m_readAhead ahead at
+// first at then top up at each slice.
+//
+bool
+RosegardenSequencerApp::startPlaying()
+{
+ // Fetch up to m_readHead microseconds worth of events
+ //
+ m_lastFetchSongPosition = m_songPosition + m_readAhead;
+
+ // This will reset the Sequencer's internal clock
+ // ready for new playback
+ m_driver->initialisePlayback(m_songPosition);
+
+ m_mC.clear();
+ fetchEvents(m_mC, m_songPosition, m_songPosition + m_readAhead, true);
+
+ // process whether we need to or not as this also processes
+ // the audio queue for us
+ //
+ m_driver->processEventsOut(m_mC, m_songPosition, m_songPosition + m_readAhead);
+
+ std::vector<MappedEvent> audioEvents;
+ m_metaIterator->getAudioEvents(audioEvents);
+ m_driver->initialiseAudioQueue(audioEvents);
+
+ // SEQUENCER_DEBUG << "RosegardenSequencerApp::startPlaying: pausing to simulate high-load environment" << endl;
+ // ::sleep(2);
+
+ // and only now do we signal to start the clock
+ //
+ m_driver->startClocks();
+
+ incrementTransportToken();
+
+ return true; // !isEndOfCompReached();
+}
+
+bool
+RosegardenSequencerApp::keepPlaying()
+{
+ Profiler profiler("RosegardenSequencerApp::keepPlaying");
+
+ m_mC.clear();
+
+ RealTime fetchEnd = m_songPosition + m_readAhead;
+ if (isLooping() && fetchEnd >= m_loopEnd) {
+ fetchEnd = m_loopEnd - RealTime(0, 1);
+ }
+ if (fetchEnd > m_lastFetchSongPosition) {
+ fetchEvents(m_mC, m_lastFetchSongPosition, fetchEnd, false);
+ }
+
+ // Again, process whether we need to or not to keep
+ // the Sequencer up-to-date with audio events
+ //
+ m_driver->processEventsOut(m_mC, m_lastFetchSongPosition, fetchEnd);
+
+ if (fetchEnd > m_lastFetchSongPosition) {
+ m_lastFetchSongPosition = fetchEnd;
+ }
+
+ return true; // !isEndOfCompReached(); - until we sort this out, we don't stop at end of comp.
+}
+
+// Return current Sequencer time in GUI compatible terms
+//
+void
+RosegardenSequencerApp::updateClocks()
+{
+ Profiler profiler("RosegardenSequencerApp::updateClocks");
+
+ m_driver->runTasks();
+
+ checkExternalTransport();
+
+ //SEQUENCER_DEBUG << "RosegardenSequencerApp::updateClocks" << endl;
+
+ // If we're not playing etc. then that's all we need to do
+ //
+ if (m_transportStatus != PLAYING &&
+ m_transportStatus != RECORDING)
+ return ;
+
+ RealTime newPosition = m_driver->getSequencerTime();
+
+ // Go around the loop if we've reached the end
+ //
+ if (isLooping() && newPosition >= m_loopEnd) {
+
+ RealTime oldPosition = m_songPosition;
+
+ // Remove the loop width from the song position and send
+ // this position to the GUI
+ //
+ newPosition = m_songPosition = m_lastFetchSongPosition = m_loopStart;
+
+ m_driver->stopClocks();
+
+ // Reset playback using this jump
+ //
+ m_driver->resetPlayback(oldPosition, m_songPosition);
+
+ m_mC.clear();
+ fetchEvents(m_mC, m_songPosition, m_songPosition + m_readAhead, true);
+
+ m_driver->processEventsOut(m_mC, m_songPosition, m_songPosition + m_readAhead);
+
+ m_driver->startClocks();
+ } else {
+ m_songPosition = newPosition;
+
+ if (m_songPosition <= m_driver->getStartPosition())
+ newPosition = m_driver->getStartPosition();
+ }
+
+ RealTime maxLatency = m_driver->getMaximumPlayLatency();
+ if (maxLatency != RealTime::zeroTime) {
+ // std::cerr << "RosegardenSequencerApp::updateClocks: latency compensation moving " << newPosition << " to " << newPosition - maxLatency << std::endl;
+ newPosition = newPosition - maxLatency;
+ }
+
+ // Remap the position pointer
+ //
+ m_sequencerMapper.updatePositionPointer(newPosition);
+}
+
+void
+RosegardenSequencerApp::notifySequencerStatus()
+{
+ QByteArray data, replyData;
+ QCString replyType;
+ QDataStream arg(data, IO_WriteOnly);
+
+ arg << (int)m_transportStatus;
+
+ if (!kapp->dcopClient()->send(ROSEGARDEN_GUI_APP_NAME,
+ ROSEGARDEN_GUI_IFACE_NAME,
+ "notifySequencerStatus(int)",
+ data)) {
+ SEQUENCER_DEBUG << "RosegardenSequencer::notifySequencerStatus()"
+ << " - can't send to RosegardenGUI client"
+ << endl;
+
+ // Stop the sequencer
+ //
+ stop();
+ }
+}
+
+void
+RosegardenSequencerApp::sleep(const RealTime &rt)
+{
+ m_driver->sleep(rt);
+}
+
+
+// Sets the Sequencer object and this object to the new time
+// from where playback can continue.
+//
+void
+RosegardenSequencerApp::jumpTo(long posSec, long posNsec)
+{
+ SEQUENCER_DEBUG << "RosegardenSequencerApp::jumpTo(" << posSec << ", " << posNsec << ")\n";
+
+ if (posSec < 0 && posNsec < 0)
+ return ;
+
+ m_driver->stopClocks();
+
+ RealTime oldPosition = m_songPosition;
+
+ m_songPosition = m_lastFetchSongPosition = RealTime(posSec, posNsec);
+
+ if (m_sequencerMapper.getSequencerDataBlock()) {
+ m_sequencerMapper.getSequencerDataBlock()->setPositionPointer
+ (m_songPosition);
+ }
+
+ m_driver->resetPlayback(oldPosition, m_songPosition);
+
+ if (m_driver->isPlaying()) {
+
+ // Now prebuffer as in startPlaying:
+
+ m_mC.clear();
+ fetchEvents(m_mC, m_songPosition, m_songPosition + m_readAhead, true);
+
+ // process whether we need to or not as this also processes
+ // the audio queue for us
+ //
+ m_driver->processEventsOut(m_mC, m_songPosition, m_songPosition + m_readAhead);
+ }
+
+ incrementTransportToken();
+
+ // SEQUENCER_DEBUG << "RosegardenSequencerApp::jumpTo: pausing to simulate high-load environment" << endl;
+ // ::sleep(1);
+
+ m_driver->startClocks();
+
+ return ;
+}
+
+// Send the last recorded MIDI block
+//
+void
+RosegardenSequencerApp::processRecordedMidi()
+{
+ MappedComposition *mC = m_driver->getMappedComposition();
+
+ if (mC->empty() || !m_controlBlockMmapper)
+ return ;
+
+ applyFiltering(mC, m_controlBlockMmapper->getRecordFilter(), false);
+ m_sequencerMapper.updateRecordingBuffer(mC);
+
+ if (m_controlBlockMmapper->isMidiRoutingEnabled()) {
+ applyFiltering(mC, m_controlBlockMmapper->getThruFilter(), true);
+ routeEvents(mC, false);
+ }
+}
+
+void
+RosegardenSequencerApp::routeEvents(MappedComposition *mC, bool useSelectedTrack)
+{
+ InstrumentId instrumentId;
+
+ if (useSelectedTrack) {
+ instrumentId = m_controlBlockMmapper->getInstrumentForTrack
+ (m_controlBlockMmapper->getSelectedTrack());
+ for (MappedComposition::iterator i = mC->begin();
+ i != mC->end(); ++i) {
+ (*i)->setInstrument(instrumentId);
+ }
+ } else {
+ for (MappedComposition::iterator i = mC->begin();
+ i != mC->end(); ++i) {
+ instrumentId = m_controlBlockMmapper->getInstrumentForEvent
+ ((*i)->getRecordedDevice(), (*i)->getRecordedChannel());
+ (*i)->setInstrument(instrumentId);
+ }
+ }
+ m_driver->processEventsOut(*mC);
+}
+
+// Send an update
+//
+void
+RosegardenSequencerApp::processRecordedAudio()
+{
+ // Nothing to do here: the recording time is sent back to the GUI
+ // in the sequencer mapper as a normal case.
+}
+
+
+// This method is called during STOPPED or PLAYING operations
+// to mop up any async (unexpected) incoming MIDI or Audio events
+// and forward them to the GUI for display
+//
+void
+RosegardenSequencerApp::processAsynchronousEvents()
+{
+ if (!m_controlBlockMmapper) {
+
+ // If the control block mmapper doesn't exist, we'll just
+ // return here. But we want to ensure we don't check again
+ // immediately, because we're probably waiting for the GUI to
+ // start up.
+
+ static bool lastChecked = false;
+ static struct timeval lastCheckedAt;
+
+ struct timeval tv;
+ (void)gettimeofday(&tv, 0);
+
+ if (lastChecked &&
+ tv.tv_sec == lastCheckedAt.tv_sec) {
+ lastCheckedAt = tv;
+ return ;
+ }
+
+ lastChecked = true;
+ lastCheckedAt = tv;
+
+ try {
+ m_controlBlockMmapper = new ControlBlockMmapper(KGlobal::dirs()->resourceDirs("tmp").last()
+ + "/rosegarden_control_block");
+ } catch (Exception e) {
+ // Assume that the control block simply hasn't been
+ // created yet because the GUI's still starting up.
+ // If there's a real problem with the mmapper, it
+ // will show up in play() instead.
+ return ;
+ }
+ m_sequencerMapper.setControlBlock(m_controlBlockMmapper->getControlBlock());
+ }
+
+ MappedComposition *mC = m_driver->getMappedComposition();
+
+ if (mC->empty()) {
+ m_driver->processPending();
+ return ;
+ }
+
+ // std::cerr << "processAsynchronousEvents: have " << mC->size() << " events" << std::endl;
+
+ QByteArray data;
+ QDataStream arg(data, IO_WriteOnly);
+ arg << mC;
+
+ if (m_controlBlockMmapper->isMidiRoutingEnabled()) {
+ applyFiltering(mC, m_controlBlockMmapper->getThruFilter(), true);
+ routeEvents(mC, true);
+ }
+
+ // std::cerr << "processAsynchronousEvents: sent " << mC->size() << " events" << std::endl;
+
+ if (!kapp->dcopClient()->send(ROSEGARDEN_GUI_APP_NAME,
+ ROSEGARDEN_GUI_IFACE_NAME,
+ "processAsynchronousMidi(MappedComposition)", data)) {
+ SEQUENCER_DEBUG << "RosegardenSequencer::processAsynchronousEvents() - "
+ << "can't call RosegardenGUI client" << endl;
+
+ // Stop the sequencer so we can see if we can try again later
+ //
+ stop();
+ }
+
+ // Process any pending events (Note Offs or Audio) as part of
+ // same procedure.
+ //
+ m_driver->processPending();
+}
+
+
+void
+RosegardenSequencerApp::applyFiltering(MappedComposition *mC,
+ MidiFilter filter,
+ bool filterControlDevice)
+{
+ for (MappedComposition::iterator i = mC->begin();
+ i != mC->end(); ) { // increment in loop
+ MappedComposition::iterator j = i;
+ ++j;
+ if (((*i)->getType() & filter) ||
+ (filterControlDevice && ((*i)->getRecordedDevice() ==
+ Device::CONTROL_DEVICE))) {
+ mC->erase(i);
+ }
+ i = j;
+ }
+}
+
+
+int
+RosegardenSequencerApp::record(const RealTime &time,
+ const RealTime &readAhead,
+ const RealTime &audioMix,
+ const RealTime &audioRead,
+ const RealTime &audioWrite,
+ long smallFileSize,
+ long recordMode)
+{
+ TransportStatus localRecordMode = (TransportStatus) recordMode;
+
+ SEQUENCER_DEBUG << "RosegardenSequencerApp::record - recordMode is " << recordMode << ", transport status is " << m_transportStatus << endl;
+
+ // punch in recording
+ if (m_transportStatus == PLAYING) {
+ if (localRecordMode == STARTING_TO_RECORD) {
+ SEQUENCER_DEBUG << "RosegardenSequencerApp::record: punching in" << endl;
+ localRecordMode = RECORDING; // no need to start playback
+ }
+ }
+
+ // For audio recording we need to retrieve audio
+ // file names from the GUI
+ //
+ if (localRecordMode == STARTING_TO_RECORD ||
+ localRecordMode == RECORDING) {
+ SEQUENCER_DEBUG << "RosegardenSequencerApp::record()"
+ << " - starting to record" << endl;
+
+ QValueVector<InstrumentId> armedInstruments;
+ QValueVector<QString> audioFileNames;
+
+ {
+ QByteArray data, replyData;
+ QCString replyType;
+ QDataStream arg(data, IO_WriteOnly);
+
+ if (!kapp->dcopClient()->call(ROSEGARDEN_GUI_APP_NAME,
+ ROSEGARDEN_GUI_IFACE_NAME,
+ "getArmedInstruments()",
+ data, replyType, replyData, true)) {
+ SEQUENCER_DEBUG << "RosegardenSequencer::record()"
+ << " - can't call RosegardenGUI client for getArmedInstruments"
+ << endl;
+ }
+
+ QDataStream reply(replyData, IO_ReadOnly);
+ if (replyType == "QValueVector<InstrumentId>") {
+ reply >> armedInstruments;
+ } else {
+ SEQUENCER_DEBUG << "RosegardenSequencer::record() - "
+ << "unrecognised type returned for getArmedInstruments" << endl;
+ }
+ }
+
+ QValueVector<InstrumentId> audioInstruments;
+
+ for (unsigned int i = 0; i < armedInstruments.size(); ++i) {
+ if (armedInstruments[i] >= AudioInstrumentBase &&
+ armedInstruments[i] < MidiInstrumentBase) {
+ audioInstruments.push_back(armedInstruments[i]);
+ }
+ }
+
+ if (audioInstruments.size() > 0) {
+
+ QByteArray data, replyData;
+ QCString replyType;
+ QDataStream arg(data, IO_WriteOnly);
+
+ arg << audioInstruments;
+
+ if (!kapp->dcopClient()->call(ROSEGARDEN_GUI_APP_NAME,
+ ROSEGARDEN_GUI_IFACE_NAME,
+ "createRecordAudioFiles(QValueVector<InstrumentId>)",
+ data, replyType, replyData, true)) {
+ SEQUENCER_DEBUG << "RosegardenSequencer::record()"
+ << " - can't call RosegardenGUI client for createNewAudioFiles"
+ << endl;
+ }
+
+ QDataStream reply(replyData, IO_ReadOnly);
+ if (replyType == "QValueVector<QString>") {
+ reply >> audioFileNames;
+ } else {
+ SEQUENCER_DEBUG << "RosegardenSequencer::record() - "
+ << "unrecognised type returned for createNewAudioFiles" << endl;
+ }
+
+ if (audioFileNames.size() != audioInstruments.size()) {
+ std::cerr << "ERROR: RosegardenSequencer::record(): Failed to create correct number of audio files (wanted " << audioInstruments.size() << ", got " << audioFileNames.size() << ")" << std::endl;
+ stop();
+ return 0;
+ }
+ }
+
+ std::vector<InstrumentId> armedInstrumentsVec;
+ std::vector<QString> audioFileNamesVec;
+ for (int i = 0; i < armedInstruments.size(); ++i) {
+ armedInstrumentsVec.push_back(armedInstruments[i]);
+ }
+ for (int i = 0; i < audioFileNames.size(); ++i) {
+ audioFileNamesVec.push_back(audioFileNames[i]);
+ }
+
+ // Get the Sequencer to prepare itself for recording - if
+ // this fails we stop.
+ //
+ if (m_driver->record(RECORD_ON,
+ &armedInstrumentsVec,
+ &audioFileNamesVec) == false) {
+ stop();
+ return 0;
+ }
+ } else {
+ // unrecognised type - return a problem
+ return 0;
+ }
+
+ // Now set the local transport status to the record mode
+ //
+ //
+ m_transportStatus = localRecordMode;
+
+ if (localRecordMode == RECORDING) { // punch in
+ return 1;
+ } else {
+
+ // Ensure that playback is initialised
+ //
+ m_driver->initialisePlayback(m_songPosition);
+
+ return play(time, readAhead, audioMix, audioRead, audioWrite, smallFileSize);
+ }
+}
+
+// We receive a starting time from the GUI which we use as the
+// basis of our first fetch of events from the GUI core. Assuming
+// this works we set our internal state to PLAYING and go ahead
+// and play the piece until we get a signal to stop.
+//
+// DCOP wants us to use an int as a return type instead of a bool.
+//
+int
+RosegardenSequencerApp::play(const RealTime &time,
+ const RealTime &readAhead,
+ const RealTime &audioMix,
+ const RealTime &audioRead,
+ const RealTime &audioWrite,
+ long smallFileSize)
+{
+ if (m_transportStatus == PLAYING ||
+ m_transportStatus == STARTING_TO_PLAY)
+ return true;
+
+ // Check for record toggle (punch out)
+ //
+ if (m_transportStatus == RECORDING) {
+ m_transportStatus = PLAYING;
+ return punchOut();
+ }
+
+ // To play from the given song position sets up the internal
+ // play state to "STARTING_TO_PLAY" which is then caught in
+ // the main event loop
+ //
+ m_songPosition = time;
+
+ if (m_sequencerMapper.getSequencerDataBlock()) {
+ m_sequencerMapper.getSequencerDataBlock()->setPositionPointer
+ (m_songPosition);
+ }
+
+ if (m_transportStatus != RECORDING &&
+ m_transportStatus != STARTING_TO_RECORD) {
+ m_transportStatus = STARTING_TO_PLAY;
+ }
+
+ m_driver->stopClocks();
+
+ // Set up buffer size
+ //
+ m_readAhead = readAhead;
+ if (m_readAhead == RealTime::zeroTime)
+ m_readAhead.sec = 1;
+
+ m_audioMix = audioMix;
+ m_audioRead = audioRead;
+ m_audioWrite = audioWrite;
+ m_smallFileSize = smallFileSize;
+
+ m_driver->setAudioBufferSizes(m_audioMix, m_audioRead, m_audioWrite,
+ m_smallFileSize);
+
+ cleanupMmapData();
+
+ // Map all segments
+ //
+ QDir segmentsDir(m_segmentFilesPath, "segment_*");
+ for (unsigned int i = 0; i < segmentsDir.count(); ++i) {
+ mmapSegment(m_segmentFilesPath + "/" + segmentsDir[i]);
+ }
+
+ QString tmpDir = KGlobal::dirs()->resourceDirs("tmp").last();
+
+ // Map metronome
+ //
+ QString metronomeFileName = tmpDir + "/rosegarden_metronome";
+ QFileInfo metronomeFileInfo(metronomeFileName);
+ if (metronomeFileInfo.exists())
+ mmapSegment(metronomeFileName);
+ else
+ SEQUENCER_DEBUG << "RosegardenSequencerApp::play() - no metronome found\n";
+
+ // Map tempo segment
+ //
+ QString tempoSegmentFileName = tmpDir + "/rosegarden_tempo";
+ QFileInfo tempoSegmentFileInfo(tempoSegmentFileName);
+ if (tempoSegmentFileInfo.exists())
+ mmapSegment(tempoSegmentFileName);
+ else
+ SEQUENCER_DEBUG << "RosegardenSequencerApp::play() - no tempo segment found\n";
+
+ // Map time sig segment
+ //
+ QString timeSigSegmentFileName = tmpDir + "/rosegarden_timesig";
+ QFileInfo timeSigSegmentFileInfo(timeSigSegmentFileName);
+ if (timeSigSegmentFileInfo.exists())
+ mmapSegment(timeSigSegmentFileName);
+ else
+ SEQUENCER_DEBUG << "RosegardenSequencerApp::play() - no time sig segment found\n";
+
+ // Map control block if necessary
+ //
+ if (!m_controlBlockMmapper) {
+ m_controlBlockMmapper = new ControlBlockMmapper(tmpDir + "/rosegarden_control_block");
+ m_sequencerMapper.setControlBlock(m_controlBlockMmapper->getControlBlock());
+ }
+
+ initMetaIterator();
+
+ // report
+ //
+ SEQUENCER_DEBUG << "RosegardenSequencerApp::play() - starting to play\n";
+
+ // Test bits
+ // m_metaIterator = new MmappedSegmentsMetaIterator(m_mmappedSegments);
+ // MappedComposition testCompo;
+ // m_metaIterator->fillCompositionWithEventsUntil(&testCompo,
+ // RealTime(2,0));
+
+ // dumpFirstSegment();
+
+ // keep it simple
+ return true;
+}
+
+int
+RosegardenSequencerApp::punchOut()
+{
+ // Check for record toggle (punch out)
+ //
+ if (m_transportStatus == RECORDING) {
+ m_driver->punchOut();
+ m_transportStatus = PLAYING;
+ return true;
+ }
+ return false;
+}
+
+MmappedSegment* RosegardenSequencerApp::mmapSegment(const QString& file)
+{
+ MmappedSegment* m = 0;
+
+ try {
+ m = new MmappedSegment(file);
+ } catch (Exception e) {
+ SEQUENCER_DEBUG << "RosegardenSequencerApp::mmapSegment() - couldn't map file " << file
+ << " : " << e.getMessage().c_str() << endl;
+ return 0;
+ }
+
+
+ m_mmappedSegments[file] = m;
+ return m;
+}
+
+void RosegardenSequencerApp::initMetaIterator()
+{
+ delete m_metaIterator;
+ m_metaIterator = new MmappedSegmentsMetaIterator(m_mmappedSegments, m_controlBlockMmapper);
+}
+
+void RosegardenSequencerApp::cleanupMmapData()
+{
+ for (MmappedSegmentsMetaIterator::mmappedsegments::iterator i =
+ m_mmappedSegments.begin(); i != m_mmappedSegments.end(); ++i)
+ delete i->second;
+
+ m_mmappedSegments.clear();
+
+ delete m_metaIterator;
+ m_metaIterator = 0;
+}
+
+void RosegardenSequencerApp::remapSegment(const QString& filename, size_t newSize)
+{
+ if (m_transportStatus != PLAYING)
+ return ;
+
+ SEQUENCER_DEBUG << "RosegardenSequencerApp::remapSegment(" << filename << ")\n";
+
+ MmappedSegment* m = m_mmappedSegments[filename];
+ if (m->remap(newSize) && m_metaIterator)
+ m_metaIterator->resetIteratorForSegment(filename);
+}
+
+void RosegardenSequencerApp::addSegment(const QString& filename)
+{
+ if (m_transportStatus != PLAYING)
+ return ;
+
+ SEQUENCER_DEBUG << "MmappedSegment::addSegment(" << filename << ")\n";
+
+ MmappedSegment* m = mmapSegment(filename);
+
+ if (m_metaIterator)
+ m_metaIterator->addSegment(m);
+}
+
+void RosegardenSequencerApp::deleteSegment(const QString& filename)
+{
+ if (m_transportStatus != PLAYING)
+ return ;
+
+ SEQUENCER_DEBUG << "MmappedSegment::deleteSegment(" << filename << ")\n";
+
+ MmappedSegment* m = m_mmappedSegments[filename];
+
+ if (m_metaIterator)
+ m_metaIterator->deleteSegment(m);
+
+ delete m;
+
+ // #932415
+ m_mmappedSegments.erase(filename);
+}
+
+void RosegardenSequencerApp::closeAllSegments()
+{
+ SEQUENCER_DEBUG << "MmappedSegment::closeAllSegments()\n";
+
+ for (MmappedSegmentsMetaIterator::mmappedsegments::iterator
+ i = m_mmappedSegments.begin();
+ i != m_mmappedSegments.end(); ++i) {
+ if (m_metaIterator)
+ m_metaIterator->deleteSegment(i->second);
+
+ delete i->second;
+ }
+
+ m_mmappedSegments.clear();
+
+ m_sequencerMapper.setControlBlock(0);
+ delete m_controlBlockMmapper;
+ m_controlBlockMmapper = 0;
+}
+
+void RosegardenSequencerApp::remapTracks()
+{
+// SEQUENCER_DEBUG << "RosegardenSequencerApp::remapTracks" << endl;
+ std::cout << "RosegardenSequencerApp::remapTracks" << std::endl;
+
+ rationalisePlayingAudio();
+}
+
+// DCOP Wrapper for play(RealTime,
+// RealTime,
+// RealTime)
+//
+//
+int
+RosegardenSequencerApp::play(long timeSec,
+ long timeNSec,
+ long readAheadSec,
+ long readAheadNSec,
+ long audioMixSec,
+ long audioMixNsec,
+ long audioReadSec,
+ long audioReadNsec,
+ long audioWriteSec,
+ long audioWriteNsec,
+ long smallFileSize)
+
+{
+ return play(RealTime(timeSec, timeNSec),
+ RealTime(readAheadSec, readAheadNSec),
+ RealTime(audioMixSec, audioMixNsec),
+ RealTime(audioReadSec, audioReadNsec),
+ RealTime(audioWriteSec, audioWriteNsec),
+ smallFileSize);
+}
+
+
+
+// Wrapper for record(RealTime,
+// RealTime,
+// RealTime,
+// recordMode);
+//
+//
+int
+RosegardenSequencerApp::record(long timeSec,
+ long timeNSec,
+ long readAheadSec,
+ long readAheadNSec,
+ long audioMixSec,
+ long audioMixNsec,
+ long audioReadSec,
+ long audioReadNsec,
+ long audioWriteSec,
+ long audioWriteNsec,
+ long smallFileSize,
+ long recordMode)
+
+{
+ return record(RealTime(timeSec, timeNSec),
+ RealTime(readAheadSec, readAheadNSec),
+ RealTime(audioMixSec, audioMixNsec),
+ RealTime(audioReadSec, audioReadNsec),
+ RealTime(audioWriteSec, audioWriteNsec),
+ smallFileSize,
+ recordMode);
+}
+
+
+void
+RosegardenSequencerApp::setLoop(const RealTime &loopStart,
+ const RealTime &loopEnd)
+{
+ m_loopStart = loopStart;
+ m_loopEnd = loopEnd;
+
+ m_driver->setLoop(loopStart, loopEnd);
+}
+
+
+void
+RosegardenSequencerApp::setLoop(long loopStartSec,
+ long loopStartNSec,
+ long loopEndSec,
+ long loopEndNSec)
+{
+ setLoop(RealTime(loopStartSec, loopStartNSec),
+ RealTime(loopEndSec, loopEndNSec));
+}
+
+
+// Return the status of the sound systems (audio and MIDI)
+//
+unsigned int
+RosegardenSequencerApp::getSoundDriverStatus(const QString &guiVersion)
+{
+ unsigned int driverStatus = m_driver->getStatus();
+ if (guiVersion == VERSION)
+ driverStatus |= VERSION_OK;
+ else {
+ std::cerr << "WARNING: RosegardenSequencerApp::getSoundDriverStatus: "
+ << "GUI version \"" << guiVersion
+ << "\" does not match sequencer version \"" << VERSION
+ << "\"" << std::endl;
+ }
+ return driverStatus;
+}
+
+
+// Add an audio file to the sequencer
+int
+RosegardenSequencerApp::addAudioFile(const QString &fileName, int id)
+{
+ return ((int)m_driver->addAudioFile(fileName.utf8().data(), id));
+}
+
+int
+RosegardenSequencerApp::removeAudioFile(int id)
+{
+ return ((int)m_driver->removeAudioFile(id));
+}
+
+void
+RosegardenSequencerApp::clearAllAudioFiles()
+{
+ m_driver->clearAudioFiles();
+}
+
+void
+RosegardenSequencerApp::setMappedInstrument(int type, unsigned char channel,
+ unsigned int id)
+{
+ InstrumentId mID = (InstrumentId)id;
+ Instrument::InstrumentType mType =
+ (Instrument::InstrumentType)type;
+ MidiByte mChannel = (MidiByte)channel;
+
+ m_driver->setMappedInstrument(
+ new MappedInstrument (mType, mChannel, mID));
+
+}
+
+// Process a MappedComposition sent from Sequencer with
+// immediate effect
+//
+void
+RosegardenSequencerApp::processSequencerSlice(MappedComposition mC)
+{
+ // Use the "now" API
+ //
+ m_driver->processEventsOut(mC);
+}
+
+void
+RosegardenSequencerApp::processMappedEvent(unsigned int id,
+ int type,
+ unsigned char pitch,
+ unsigned char velocity,
+ long absTimeSec,
+ long absTimeNsec,
+ long durationSec,
+ long durationNsec,
+ long audioStartMarkerSec,
+ long audioStartMarkerNSec)
+{
+ MappedEvent *mE =
+ new MappedEvent(
+ (InstrumentId)id,
+ (MappedEvent::MappedEventType)type,
+ (MidiByte)pitch,
+ (MidiByte)velocity,
+ RealTime(absTimeSec, absTimeNsec),
+ RealTime(durationSec, durationNsec),
+ RealTime(audioStartMarkerSec, audioStartMarkerNSec));
+
+ MappedComposition mC;
+
+ // SEQUENCER_DEBUG << "processMappedEvent(data) - sending out single event at time " << mE->getEventTime() << endl;
+
+ /*
+ std::cout << "ID = " << mE->getInstrument() << std::endl;
+ std::cout << "TYPE = " << mE->getType() << std::endl;
+ std::cout << "D1 = " << (int)mE->getData1() << std::endl;
+ std::cout << "D2 = " << (int)mE->getData2() << std::endl;
+ */
+
+ mC.insert(mE);
+
+ m_driver->processEventsOut(mC);
+}
+
+void
+RosegardenSequencerApp::processMappedEvent(MappedEvent mE)
+{
+ MappedComposition mC;
+ mC.insert(new MappedEvent(mE));
+ SEQUENCER_DEBUG << "processMappedEvent(ev) - sending out single event at time " << mE.getEventTime() << endl;
+
+ m_driver->processEventsOut(mC);
+}
+
+// Get the MappedDevice (DCOP wrapped vector of MappedInstruments)
+//
+MappedDevice
+RosegardenSequencerApp::getMappedDevice(unsigned int id)
+{
+ return m_driver->getMappedDevice(id);
+}
+
+unsigned int
+RosegardenSequencerApp::getDevices()
+{
+ return m_driver->getDevices();
+}
+
+int
+RosegardenSequencerApp::canReconnect(int type)
+{
+ return m_driver->canReconnect((Device::DeviceType)type);
+}
+
+unsigned int
+RosegardenSequencerApp::addDevice(int type, unsigned int direction)
+{
+ return m_driver->addDevice((Device::DeviceType)type,
+ (MidiDevice::DeviceDirection)direction);
+}
+
+void
+RosegardenSequencerApp::removeDevice(unsigned int deviceId)
+{
+ m_driver->removeDevice(deviceId);
+}
+
+void
+RosegardenSequencerApp::renameDevice(unsigned int deviceId, QString name)
+{
+ m_driver->renameDevice(deviceId, name);
+}
+
+unsigned int
+RosegardenSequencerApp::getConnections(int type, unsigned int direction)
+{
+ return m_driver->getConnections((Device::DeviceType)type,
+ (MidiDevice::DeviceDirection)direction);
+}
+
+QString
+RosegardenSequencerApp::getConnection(int type, unsigned int direction,
+ unsigned int connectionNo)
+{
+ return m_driver->getConnection((Device::DeviceType)type,
+ (MidiDevice::DeviceDirection)direction,
+ connectionNo);
+}
+
+void
+RosegardenSequencerApp::setConnection(unsigned int deviceId,
+ QString connection)
+{
+ m_driver->setConnection(deviceId, connection);
+}
+
+void
+RosegardenSequencerApp::setPlausibleConnection(unsigned int deviceId,
+ QString connection)
+{
+ m_driver->setPlausibleConnection(deviceId, connection);
+}
+
+unsigned int
+RosegardenSequencerApp::getTimers()
+{
+ return m_driver->getTimers();
+}
+
+QString
+RosegardenSequencerApp::getTimer(unsigned int n)
+{
+ return m_driver->getTimer(n);
+}
+
+QString
+RosegardenSequencerApp::getCurrentTimer()
+{
+ return m_driver->getCurrentTimer();
+}
+
+void
+RosegardenSequencerApp::setCurrentTimer(QString timer)
+{
+ m_driver->setCurrentTimer(timer);
+}
+
+void
+RosegardenSequencerApp::setLowLatencyMode(bool ll)
+{
+ m_driver->setLowLatencyMode(ll);
+}
+
+void
+RosegardenSequencerApp::sequencerAlive()
+{
+ if (!kapp->dcopClient()->
+ isApplicationRegistered(QCString(ROSEGARDEN_GUI_APP_NAME))) {
+ SEQUENCER_DEBUG << "RosegardenSequencerApp::sequencerAlive() - "
+ << "waiting for GUI to register" << endl;
+ return ;
+ }
+
+ QByteArray data;
+
+ if (!kapp->dcopClient()->send(ROSEGARDEN_GUI_APP_NAME,
+ ROSEGARDEN_GUI_IFACE_NAME,
+ "alive()",
+ data)) {
+ SEQUENCER_DEBUG << "RosegardenSequencer::alive()"
+ << " - can't call RosegardenGUI client"
+ << endl;
+ }
+
+ SEQUENCER_DEBUG << "RosegardenSequencerApp::sequencerAlive() - "
+ << "trying to tell GUI that we're alive" << endl;
+}
+
+MappedRealTime
+RosegardenSequencerApp::getAudioPlayLatency()
+{
+ return MappedRealTime(m_driver->getAudioPlayLatency());
+}
+
+MappedRealTime
+RosegardenSequencerApp::getAudioRecordLatency()
+{
+ return MappedRealTime(m_driver->getAudioRecordLatency());
+}
+
+// Initialise the virtual studio with a few audio faders and
+// create a plugin manager. For the moment this is pretty
+// arbitrary but eventually we'll drive this from the gui
+// and rg file "Studio" entries.
+//
+void
+RosegardenSequencerApp::initialiseStudio()
+{
+ // clear down the studio before we start adding anything
+ //
+ m_studio->clear();
+}
+
+
+void
+RosegardenSequencerApp::setMappedProperty(int id,
+ const QString &property,
+ float value)
+{
+
+ // SEQUENCER_DEBUG << "setProperty: id = " << id
+ // << " : property = \"" << property << "\""
+ // << ", value = " << value << endl;
+
+
+ MappedObject *object = m_studio->getObjectById(id);
+
+ if (object)
+ object->setProperty(property, value);
+}
+
+void
+RosegardenSequencerApp::setMappedProperties(const MappedObjectIdList &ids,
+ const MappedObjectPropertyList &properties,
+ const MappedObjectValueList &values)
+{
+ MappedObject *object = 0;
+ MappedObjectId prevId = 0;
+
+ for (size_t i = 0;
+ i < ids.size() && i < properties.size() && i < values.size();
+ ++i) {
+
+ if (i == 0 || ids[i] != prevId) {
+ object = m_studio->getObjectById(ids[i]);
+ prevId = ids[i];
+ }
+
+ if (object) {
+ object->setProperty(properties[i], values[i]);
+ }
+ }
+}
+
+void
+RosegardenSequencerApp::setMappedProperty(int id,
+ const QString &property,
+ const QString &value)
+{
+
+ SEQUENCER_DEBUG << "setProperty: id = " << id
+ << " : property = \"" << property << "\""
+ << ", value = " << value << endl;
+
+
+ MappedObject *object = m_studio->getObjectById(id);
+
+ if (object)
+ object->setProperty(property, value);
+}
+
+void
+RosegardenSequencerApp::setMappedPropertyList(int id, const QString &property,
+ const MappedObjectPropertyList &values)
+{
+ SEQUENCER_DEBUG << "setPropertyList: id = " << id
+ << " : property list size = \"" << values.size()
+ << "\"" << endl;
+
+ MappedObject *object = m_studio->getObjectById(id);
+
+ if (object) {
+ try {
+ object->setPropertyList(property, values);
+ } catch (QString err) {
+ QByteArray data;
+ QDataStream arg(data, IO_WriteOnly);
+ arg << err;
+ kapp->dcopClient()->send(ROSEGARDEN_GUI_APP_NAME,
+ ROSEGARDEN_GUI_IFACE_NAME,
+ "showError(QString)",
+ data);
+ }
+ }
+}
+
+
+int
+RosegardenSequencerApp::getMappedObjectId(int type)
+{
+ int value = -1;
+
+ MappedObject *object =
+ m_studio->getObjectOfType(
+ MappedObject::MappedObjectType(type));
+
+ if (object) {
+ value = int(object->getId());
+ }
+
+ return value;
+}
+
+
+std::vector<QString>
+RosegardenSequencerApp::getPropertyList(int id,
+ const QString &property)
+{
+ std::vector<QString> list;
+
+ MappedObject *object =
+ m_studio->getObjectById(id);
+
+ if (object) {
+ list = object->getPropertyList(property);
+ }
+
+ SEQUENCER_DEBUG << "getPropertyList - return " << list.size()
+ << " items" << endl;
+
+ return list;
+}
+
+std::vector<QString>
+RosegardenSequencerApp::getPluginInformation()
+{
+ std::vector<QString> list;
+
+ PluginFactory::enumerateAllPlugins(list);
+
+ return list;
+}
+
+QString
+RosegardenSequencerApp::getPluginProgram(int id, int bank, int program)
+{
+ MappedObject *object = m_studio->getObjectById(id);
+
+ if (object) {
+ MappedPluginSlot *slot =
+ dynamic_cast<MappedPluginSlot *>(object);
+ if (slot) {
+ return slot->getProgram(bank, program);
+ }
+ }
+
+ return QString();
+}
+
+unsigned long
+RosegardenSequencerApp::getPluginProgram(int id, const QString &name)
+{
+ MappedObject *object = m_studio->getObjectById(id);
+
+ if (object) {
+ MappedPluginSlot *slot =
+ dynamic_cast<MappedPluginSlot *>(object);
+ if (slot) {
+ return slot->getProgram(name);
+ }
+ }
+
+ return 0;
+}
+
+unsigned int
+RosegardenSequencerApp::getSampleRate() const
+{
+ if (m_driver)
+ return m_driver->getSampleRate();
+
+ return 0;
+}
+
+// Creates an object of a type
+//
+int
+RosegardenSequencerApp::createMappedObject(int type)
+{
+ MappedObject *object =
+ m_studio->createObject(
+ MappedObject::MappedObjectType(type));
+
+ if (object) {
+ SEQUENCER_DEBUG << "createMappedObject - type = "
+ << type << ", object id = "
+ << object->getId() << endl;
+ return object->getId();
+ }
+
+ return 0;
+}
+
+// Destroy an object
+//
+int
+RosegardenSequencerApp::destroyMappedObject(int id)
+{
+ return (int(m_studio->destroyObject(MappedObjectId(id))));
+}
+
+// Connect two objects
+//
+void
+RosegardenSequencerApp::connectMappedObjects(int id1, int id2)
+{
+ m_studio->connectObjects(MappedObjectId(id1),
+ MappedObjectId(id2));
+
+ // When this happens we need to resynchronise our audio processing,
+ // and this is the easiest (and most brutal) way to do it.
+ if (m_transportStatus == PLAYING ||
+ m_transportStatus == RECORDING) {
+ RealTime seqTime = m_driver->getSequencerTime();
+ jumpTo(seqTime.sec, seqTime.nsec);
+ }
+}
+
+// Disconnect two objects
+//
+void
+RosegardenSequencerApp::disconnectMappedObjects(int id1, int id2)
+{
+ m_studio->disconnectObjects(MappedObjectId(id1),
+ MappedObjectId(id2));
+}
+
+// Disconnect an object from everything
+//
+void
+RosegardenSequencerApp::disconnectMappedObject(int id)
+{
+ m_studio->disconnectObject(MappedObjectId(id));
+}
+
+
+void
+RosegardenSequencerApp::clearStudio()
+{
+ SEQUENCER_DEBUG << "clearStudio()" << endl;
+ m_studio->clear();
+ m_sequencerMapper.getSequencerDataBlock()->clearTemporaries();
+
+}
+
+void
+RosegardenSequencerApp::setMappedPort(int pluginId,
+ unsigned long portId,
+ float value)
+{
+ MappedObject *object =
+ m_studio->getObjectById(pluginId);
+
+ MappedPluginSlot *slot =
+ dynamic_cast<MappedPluginSlot *>(object);
+
+ if (slot) {
+ slot->setPort(portId, value);
+ } else {
+ SEQUENCER_DEBUG << "no such slot" << endl;
+ }
+}
+
+float
+RosegardenSequencerApp::getMappedPort(int pluginId,
+ unsigned long portId)
+{
+ MappedObject *object =
+ m_studio->getObjectById(pluginId);
+
+ MappedPluginSlot *slot =
+ dynamic_cast<MappedPluginSlot *>(object);
+
+ if (slot) {
+ return slot->getPort(portId);
+ } else {
+ SEQUENCER_DEBUG << "no such slot" << endl;
+ }
+
+ return 0;
+}
+
+void
+RosegardenSequencerApp::slotCheckForNewClients()
+{
+ // Don't do this check if any of these conditions hold
+ //
+ if (m_transportStatus == PLAYING ||
+ m_transportStatus == RECORDING)
+ return ;
+
+ if (m_driver->checkForNewClients()) {
+ SEQUENCER_DEBUG << "client list changed" << endl;
+ }
+}
+
+
+// Set the MIDI Clock period in microseconds
+//
+void
+RosegardenSequencerApp::setQuarterNoteLength(long timeSec, long timeNSec)
+{
+ SEQUENCER_DEBUG << "RosegardenSequencerApp::setQuarterNoteLength"
+ << RealTime(timeSec, timeNSec) << endl;
+
+ m_driver->setMIDIClockInterval(
+ RealTime(timeSec, timeNSec) / 24);
+}
+
+QString
+RosegardenSequencerApp::getStatusLog()
+{
+ return m_driver->getStatusLog();
+}
+
+
+void RosegardenSequencerApp::dumpFirstSegment()
+{
+ SEQUENCER_DEBUG << "Dumping 1st segment data :\n";
+
+ unsigned int i = 0;
+ MmappedSegment* firstMappedSegment = (*(m_mmappedSegments.begin())).second;
+
+ MmappedSegment::iterator it(firstMappedSegment);
+
+ for (; !it.atEnd(); ++it) {
+
+ MappedEvent evt = (*it);
+ SEQUENCER_DEBUG << i << " : inst = " << evt.getInstrument()
+ << " - type = " << evt.getType()
+ << " - data1 = " << (unsigned int)evt.getData1()
+ << " - data2 = " << (unsigned int)evt.getData2()
+ << " - time = " << evt.getEventTime()
+ << " - duration = " << evt.getDuration()
+ << " - audio mark = " << evt.getAudioStartMarker()
+ << endl;
+
+ ++i;
+ }
+
+ SEQUENCER_DEBUG << "Dumping 1st segment data - done\n";
+
+}
+
+
+void
+RosegardenSequencerApp::rationalisePlayingAudio()
+{
+ std::vector<MappedEvent> audioEvents;
+ m_metaIterator->getAudioEvents(audioEvents);
+ m_driver->initialiseAudioQueue(audioEvents);
+}
+
+
+ExternalTransport::TransportToken
+RosegardenSequencerApp::transportChange(TransportRequest request)
+{
+ TransportPair pair(request, RealTime::zeroTime);
+ m_transportRequests.push_back(pair);
+
+ std::cout << "RosegardenSequencerApp::transportChange: " << request << std::endl;
+
+ if (request == TransportNoChange)
+ return m_transportToken;
+ else
+ return m_transportToken + 1;
+}
+
+ExternalTransport::TransportToken
+RosegardenSequencerApp::transportJump(TransportRequest request,
+ RealTime rt)
+{
+ TransportPair pair(request, rt);
+ m_transportRequests.push_back(pair);
+
+ std::cout << "RosegardenSequencerApp::transportJump: " << request << ", " << rt << std::endl;
+
+ if (request == TransportNoChange)
+ return m_transportToken + 1;
+ else
+ return m_transportToken + 2;
+}
+
+bool
+RosegardenSequencerApp::isTransportSyncComplete(TransportToken token)
+{
+ std::cout << "RosegardenSequencerApp::isTransportSyncComplete: token " << token << ", current token " << m_transportToken << std::endl;
+ return m_transportToken >= token;
+}
+
+bool
+RosegardenSequencerApp::checkExternalTransport()
+{
+ bool rv = (!m_transportRequests.empty());
+
+ while (!m_transportRequests.empty()) {
+
+ TransportPair pair = *m_transportRequests.begin();
+ m_transportRequests.pop_front();
+
+ QByteArray data;
+
+ switch (pair.first) {
+
+ case TransportNoChange:
+ break;
+
+ case TransportStop:
+ kapp->dcopClient()->send(ROSEGARDEN_GUI_APP_NAME,
+ ROSEGARDEN_GUI_IFACE_NAME,
+ "stop()",
+ data);
+ break;
+
+ case TransportStart:
+ kapp->dcopClient()->send(ROSEGARDEN_GUI_APP_NAME,
+ ROSEGARDEN_GUI_IFACE_NAME,
+ "play()",
+ data);
+ break;
+
+ case TransportPlay:
+ kapp->dcopClient()->send(ROSEGARDEN_GUI_APP_NAME,
+ ROSEGARDEN_GUI_IFACE_NAME,
+ "play()",
+ data);
+ break;
+
+ case TransportRecord:
+ kapp->dcopClient()->send(ROSEGARDEN_GUI_APP_NAME,
+ ROSEGARDEN_GUI_IFACE_NAME,
+ "record()",
+ data);
+ break;
+
+ case TransportJumpToTime: {
+ QDataStream arg(data, IO_WriteOnly);
+ arg << (int)pair.second.sec;
+ arg << (int)pair.second.usec();
+
+ kapp->dcopClient()->send(ROSEGARDEN_GUI_APP_NAME,
+ ROSEGARDEN_GUI_IFACE_NAME,
+ "jumpToTime(int, int)",
+ data);
+
+ if (m_transportStatus == PLAYING ||
+ m_transportStatus != RECORDING) {
+ jumpTo(pair.second.sec, pair.second.usec() * 1000);
+ }
+
+ incrementTransportToken();
+ break;
+ }
+
+ case TransportStartAtTime: {
+ QDataStream arg(data, IO_WriteOnly);
+ arg << (int)pair.second.sec;
+ arg << (int)pair.second.usec();
+
+ kapp->dcopClient()->send(ROSEGARDEN_GUI_APP_NAME,
+ ROSEGARDEN_GUI_IFACE_NAME,
+ "startAtTime(int, int)",
+ data);
+ break;
+ }
+
+ case TransportStopAtTime: {
+ kapp->dcopClient()->send(ROSEGARDEN_GUI_APP_NAME,
+ ROSEGARDEN_GUI_IFACE_NAME,
+ "stop()",
+ data);
+
+ QDataStream arg(data, IO_WriteOnly);
+ arg << (int)pair.second.sec;
+ arg << (int)pair.second.usec();
+
+ kapp->dcopClient()->send(ROSEGARDEN_GUI_APP_NAME,
+ ROSEGARDEN_GUI_IFACE_NAME,
+ "jumpToTime(int, int)",
+ data);
+ break;
+ }
+ }
+ }
+
+ return rv;
+}
+
+void
+RosegardenSequencerApp::incrementTransportToken()
+{
+ ++m_transportToken;
+ SEQUENCER_DEBUG << "RosegardenSequencerApp::incrementTransportToken: incrementing to " << m_transportToken << endl;
+}
+
+}
+
+#include "RosegardenSequencerApp.moc"
diff --git a/src/sequencer/RosegardenSequencerApp.h b/src/sequencer/RosegardenSequencerApp.h
new file mode 100644
index 0000000..bb72547
--- /dev/null
+++ b/src/sequencer/RosegardenSequencerApp.h
@@ -0,0 +1,531 @@
+
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
+
+/*
+ Rosegarden
+ A MIDI and audio sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _ROSEGARDEN_SEQUENCER_APP_H_
+#define _ROSEGARDEN_SEQUENCER_APP_H_
+
+// RosegardenSequencerApp is the sequencer application for Rosegarden.
+// It owns a Sequencer object which wraps the ALSA
+// and JACK funtionality. At this level we deal with comms with
+// the Rosegarden GUI application, the high level marshalling of data
+// and main event loop of the sequencer. [rwb]
+//
+
+
+// include files for Qt
+#include <qstrlist.h>
+
+// include files for KDE
+#include <kapp.h>
+#include <kmainwindow.h>
+#include <kaccel.h>
+
+#include <qtimer.h>
+
+#include "base/Composition.h"
+#include "gui/application/RosegardenDCOP.h"
+
+#include "RosegardenSequencerIface.h"
+
+#include "sound/MappedComposition.h"
+#include "base/Event.h"
+#include "sound/MappedStudio.h"
+#include "sound/ExternalTransport.h"
+
+#include "MmappedSegment.h"
+#include "SequencerMmapper.h"
+
+#include <deque>
+
+class KURL;
+class KRecentFilesAction;
+
+namespace Rosegarden {
+
+// forward declaration of the RosegardenGUI classes
+class RosegardenGUIDoc;
+class RosegardenGUIView;
+class ControlBlockMmapper;
+
+class MappedInstrument;
+class SoundDriver;
+
+/**
+ * The sequencer application
+ */
+class RosegardenSequencerApp : public KMainWindow,
+ virtual public RosegardenSequencerIface,
+ public ExternalTransport
+{
+ Q_OBJECT
+
+public:
+ RosegardenSequencerApp();
+ ~RosegardenSequencerApp();
+
+ // -------- START OF DCOP INTERFACE METHODS --------
+ //
+ //
+
+
+ // Quit
+ virtual void quit();
+
+ // Based on RealTime timestamps
+ //
+ int play(const RealTime &position,
+ const RealTime &readAhead,
+ const RealTime &audioMix,
+ const RealTime &audioRead,
+ const RealTime &audioWrite,
+ long smallFileSize);
+
+ // recording
+ int record(const RealTime &position,
+ const RealTime &readAhead,
+ const RealTime &audioMix,
+ const RealTime &audioRead,
+ const RealTime &audioWrite,
+ long smallFileSize,
+ long recordMode);
+
+ virtual int punchOut();
+
+ // looping
+ void setLoop(const RealTime &loopStart,
+ const RealTime &loopEnd);
+
+
+ // Play wrapper for DCOP
+ //
+ virtual int play(long timeSec,
+ long timeNsec,
+ long readAheadSec,
+ long readAheadNsec,
+ long audioMixSec,
+ long audioMixNsec,
+ long audioReadSec,
+ long audioReadNsec,
+ long audioWriteSec,
+ long audioWriteNsec,
+ long smallFileSize);
+
+ // Record wrapper for DCOP
+ //
+ virtual int record(long timeSec,
+ long timeNsec,
+ long readAheadSec,
+ long readAheadNsec,
+ long audioMixSec,
+ long audioMixNsec,
+ long audioReadSec,
+ long audioReadNsec,
+ long audioWriteSec,
+ long audioWriteNsec,
+ long smallFileSize,
+ long recordMode);
+
+
+ // Jump to a pointer in the playback (uses longs instead
+ // of RealTime for DCOP)
+ //
+ //
+ virtual void jumpTo(long posSec, long posNsec);
+
+ // Set a loop on the Sequencer
+ //
+ virtual void setLoop(long loopStartSec, long loopStartNsec,
+ long loopEndSec, long loopEndNsec);
+
+ // Return the Sound system status (audio/MIDI)
+ //
+ virtual unsigned int getSoundDriverStatus(const QString &guiVersion);
+
+ // Add and remove Audio files on the sequencer
+ //
+ virtual int addAudioFile(const QString &fileName, int id);
+ virtual int removeAudioFile(int id);
+
+ // Deletes all the audio files and clears down any flapping i/o handles
+ //
+ virtual void clearAllAudioFiles();
+
+ // stops the sequencer
+ //
+ virtual void stop();
+
+ // Set a MappedInstrument at the Sequencer
+ //
+ virtual void setMappedInstrument(int type, unsigned char channel,
+ unsigned int id);
+
+ // The sequencer will process the MappedComposition as soon as it
+ // gets the chance.
+ //
+ virtual void processSequencerSlice(MappedComposition mC);
+
+ // Yeuch!
+ //
+ virtual void processMappedEvent(unsigned int id,
+ int type,
+ unsigned char pitch,
+ unsigned char velocity,
+ long absTimeSec,
+ long absTimeNsec,
+ long durationSec,
+ long durationNsec,
+ long audioStartMarkerSec,
+ long audioStartMarkerNsec);
+
+ // And now do it properly
+ //
+ virtual void processMappedEvent(MappedEvent mE);
+
+ virtual unsigned int getDevices();
+ virtual MappedDevice getMappedDevice(unsigned int id);
+
+ virtual int canReconnect(int deviceType);
+ virtual unsigned int addDevice(int type, unsigned int direction);
+ virtual void removeDevice(unsigned int id);
+ virtual void renameDevice(unsigned int id, QString name);
+ virtual unsigned int getConnections(int type, unsigned int direction);
+ virtual QString getConnection(int type, unsigned int direction,
+ unsigned int connectionNo);
+ virtual void setConnection(unsigned int deviceId, QString connection);
+ virtual void setPlausibleConnection(unsigned int deviceId,
+ QString idealConnection);
+
+ virtual unsigned int getTimers();
+ virtual QString getTimer(unsigned int n);
+ virtual QString getCurrentTimer();
+ virtual void setCurrentTimer(QString timer);
+
+ virtual void setLowLatencyMode(bool);
+
+ // Audio latencies
+ //
+ virtual MappedRealTime getAudioPlayLatency();
+ virtual MappedRealTime getAudioRecordLatency();
+
+ // Set a MappedObject
+ //
+ virtual void setMappedProperty(int id,
+ const QString &property,
+ float value);
+
+ // Set many properties on many MappedObjects
+ //
+ virtual void setMappedProperties(const MappedObjectIdList &ids,
+ const MappedObjectPropertyList &properties,
+ const MappedObjectValueList &values);
+
+ // Set a MappedObject to a string
+ //
+ virtual void setMappedProperty(int id,
+ const QString &property,
+ const QString &value);
+
+ // Set a MappedObject to a property list
+ //
+ virtual void setMappedPropertyList(int id,
+ const QString &property,
+ const MappedObjectPropertyList &values);
+
+ // Get a MappedObject for a type
+ //
+ virtual int getMappedObjectId(int type);
+
+ // Get a Property list from an Object
+ //
+ virtual std::vector<QString> getPropertyList(int id,
+ const QString &property);
+
+ virtual std::vector<QString> getPluginInformation();
+
+ virtual QString getPluginProgram(int id, int bank, int program);
+
+ virtual unsigned long getPluginProgram(int id, const QString &name);
+
+ // Set a plugin port
+ //
+ virtual void setMappedPort(int pluginId,
+ unsigned long portId,
+ float value);
+
+ virtual float getMappedPort(int pluginId,
+ unsigned long portId);
+
+ // Create a MappedObject
+ virtual int createMappedObject(int type);
+
+ // Destroy an object
+ //
+ virtual int destroyMappedObject(int id);
+
+ // Connect two objects
+ //
+ virtual void connectMappedObjects(int id1, int id2);
+
+ // Disconnect two objects
+ //
+ virtual void disconnectMappedObjects(int id1, int id2);
+
+ // Disconnect an object from everything
+ //
+ virtual void disconnectMappedObject(int id);
+
+ // Sample rate
+ //
+ virtual unsigned int getSampleRate() const;
+
+ // Clear the studio
+ //
+ virtual void clearStudio();
+
+ // Debug stuff, to check MmappedSegment::iterator
+ virtual void dumpFirstSegment();
+
+ virtual void remapSegment(const QString& filename, size_t newSize);
+ virtual void addSegment(const QString& filename);
+ virtual void deleteSegment(const QString& filename);
+ virtual void closeAllSegments();
+ virtual void remapTracks();
+
+ // Set Quarter note length
+ //
+ virtual void setQuarterNoteLength(long timeSec, long timeNsec);
+
+ // Get a status report
+ //
+ virtual QString getStatusLog();
+
+ //
+ //
+ //
+ // -------- END OF DCOP INTERFACE --------
+
+
+
+
+ void setStatus(TransportStatus status)
+ { m_transportStatus = status; }
+ TransportStatus getStatus() { return m_transportStatus; }
+
+ // Process the first chunk of Sequencer events
+ bool startPlaying();
+
+ // Process all subsequent events
+ bool keepPlaying();
+
+ // Update internal clock and send GUI position pointer movement
+ void updateClocks();
+
+ bool checkExternalTransport();
+
+ // Sends status changes up to GUI
+ void notifySequencerStatus();
+
+ // Send latest slice information back to GUI for display
+ void notifyVisuals(MappedComposition *mC);
+
+ // These two methods process any pending MIDI or audio
+ // and send them up to the gui for storage and display
+ //
+ void processRecordedMidi();
+ void processRecordedAudio();
+
+ // Called during stopped or playing operation to process
+ // any pending incoming MIDI events that aren't being
+ // recorded (i.e. for display in Transport or on Mixer)
+ //
+ void processAsynchronousEvents();
+
+ // Sleep for the given time, approximately. Called from the main
+ // loop in order to lighten CPU load (i.e. the timing quality of
+ // the sequencer does not depend on this being accurate). A good
+ // implementation of this call would return right away when an
+ // incoming MIDI event needed to be handled.
+ //
+ void sleep(const RealTime &rt);
+
+ // Removes from a MappedComposition the events not matching
+ // the supplied filer.
+ //
+ void applyFiltering(MappedComposition *mC,
+ MidiFilter filter,
+ bool filterControlDevice);
+
+ // This method assigns an Instrument to each MappedEvent
+ // belongin to the MappedComposition, and sends the
+ // transformed events to the driver to be played.
+ //
+ void routeEvents(MappedComposition *mC, bool useSelectedTrack);
+
+ // Are we looping?
+ //
+ bool isLooping() const { return !(m_loopStart == m_loopEnd); }
+
+ // the call itself
+ void sequencerAlive();
+
+ /*
+ // Audio latencies
+ //
+ RealTime getAudioPlaybackLatency()
+ { return m_audioPlayLatency; }
+ void setAudioPlaybackLatency(const RealTime &latency)
+ { m_audioPlayLatency = latency; }
+
+ RealTime getAudioRecordLatency()
+ { return m_audioRecordLatency; }
+ void setAudioRecordLatency(const RealTime &latency)
+ { m_audioRecordLatency = latency; }
+ */
+
+ // Initialise the virtual studio at this end of the link
+ //
+ void initialiseStudio();
+
+
+ // --------- EXTERNAL TRANSPORT INTERFACE METHODS --------
+ //
+ // Whereas the DCOP interface (above) is for the GUI to call to
+ // make the sequencer follow its wishes, this interface is for
+ // external clients to call (via some low-level audio callback)
+ // and requires sychronising with the GUI.
+
+ TransportToken transportChange(TransportRequest);
+ TransportToken transportJump(TransportRequest, RealTime);
+ bool isTransportSyncComplete(TransportToken token);
+ TransportToken getInvalidTransportToken() const { return 0; }
+
+public slots:
+
+ // Check for new clients - on timeout
+ //
+ void slotCheckForNewClients();
+
+protected:
+
+ // get events whilst handling loop
+ //
+ void fetchEvents(MappedComposition &,
+ const RealTime &start,
+ const RealTime &end,
+ bool firstFetch);
+
+ // just get a slice of events between markers
+ //
+ void getSlice(MappedComposition &,
+ const RealTime &start,
+ const RealTime &end,
+ bool firstFetch);
+
+ // adjust event times according to relative instrument latencies
+ //
+ void applyLatencyCompensation(MappedComposition &);
+
+ // mmap-related stuff
+ MmappedSegment* mmapSegment(const QString&);
+ void cleanupMmapData();
+ void initMetaIterator();
+
+ void rationalisePlayingAudio();
+ void setEndOfCompReached(bool e) { m_isEndOfCompReached = e; }
+ bool isEndOfCompReached() { return m_isEndOfCompReached; }
+ void incrementTransportToken();
+
+ //--------------- Data members ---------------------------------
+
+ SoundDriver *m_driver;
+ TransportStatus m_transportStatus;
+
+ // Position pointer
+ RealTime m_songPosition;
+ RealTime m_lastFetchSongPosition;
+
+ RealTime m_readAhead;
+ RealTime m_audioMix;
+ RealTime m_audioRead;
+ RealTime m_audioWrite;
+ int m_smallFileSize;
+
+ /*
+
+ // Not required at the sequencer
+
+ // Two more latencies for audio play and record - when we
+ // use an unsynchronised audio and MIDI system such as
+ // ALSA and JACK we need to use these additional values
+ // to help time-keeping.
+ //
+ RealTime m_audioPlayLatency;
+ RealTime m_audioRecordLatency;
+
+ */
+
+
+ RealTime m_loopStart;
+ RealTime m_loopEnd;
+
+ std::vector<MappedInstrument*> m_instruments;
+
+ // MappedStudio holds all of our session-persistent information -
+ // sliders and what have you. It's also streamable over DCOP
+ // so you can reconstruct it at either end of the link for
+ // presentation, storage etc.
+ //
+ MappedStudio *m_studio;
+
+ // Slice revert storage
+ //
+ RealTime m_oldSliceSize;
+ QTimer *m_sliceTimer;
+
+ // Timer to check for new clients
+ //
+ QTimer *m_newClientTimer;
+
+ // mmap segments
+ //
+ QString m_segmentFilesPath;
+ MmappedSegmentsMetaIterator::mmappedsegments m_mmappedSegments;
+ MmappedSegmentsMetaIterator* m_metaIterator;
+ RealTime m_lastStartTime;
+
+ MappedComposition m_mC;
+ ControlBlockMmapper *m_controlBlockMmapper;
+ SequencerMmapper m_sequencerMapper;
+
+ typedef std::pair<TransportRequest, RealTime> TransportPair;
+ std::deque<TransportPair> m_transportRequests;
+ TransportToken m_transportToken;
+
+ bool m_isEndOfCompReached;
+};
+
+}
+
+#endif // _ROSEGARDEN_SEQUENCER_APP_H_
diff --git a/src/sequencer/RosegardenSequencerIface.h b/src/sequencer/RosegardenSequencerIface.h
new file mode 100644
index 0000000..47c0215
--- /dev/null
+++ b/src/sequencer/RosegardenSequencerIface.h
@@ -0,0 +1,364 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
+
+/*
+ Rosegarden
+ A MIDI and audio sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _ROSEGARDENSEQUENCERIFACE_H_
+#define _ROSEGARDENSEQUENCERIFACE_H_
+
+#include <dcopobject.h>
+// #include <qvaluevector.h>
+// #include <qpair.h>
+
+#include "gui/application/RosegardenDCOP.h"
+
+#include "base/Event.h"
+#include "sound/MappedComposition.h"
+#include "sound/MappedEvent.h"
+#include "base/Instrument.h"
+#include "sound/MappedDevice.h"
+#include "sound/MappedRealTime.h"
+#include "sound/MappedStudio.h"
+#include "sound/MappedCommon.h"
+
+namespace Rosegarden {
+
+class RosegardenSequencerIface : virtual public DCOPObject
+{
+ K_DCOP
+public:
+ k_dcop:
+
+ // close the sequencer
+ //
+ virtual void quit() = 0;
+
+
+
+ // play from a given time with given parameters
+ //
+ virtual int play(long timeSec,
+ long timeNsec,
+ long readAheadSec,
+ long readAheadNsec,
+ long audioMixSec,
+ long audioMixNsec,
+ long audioReadSec,
+ long audioReadNsec,
+ long audioWriteSec,
+ long audioWriteNsec,
+ long smallFileSize) = 0;
+
+ // record from a given time with given parameters
+ //
+ virtual int record(long timeSec,
+ long timeNsec,
+ long readAheadSec,
+ long readAheadNsec,
+ long audioMixSec,
+ long audioMixNsec,
+ long audioReadSec,
+ long audioReadNsec,
+ long audioWriteSec,
+ long audioWriteNsec,
+ long smallFileSize,
+ long recordMode) = 0;
+
+ // stop the sequencer
+ //
+ virtual ASYNC stop() = 0;
+
+ // punch out from recording to playback
+ //
+ virtual int punchOut() = 0;
+
+ // Set the sequencer to a given time
+ //
+ virtual void jumpTo(long posSec, long posNsec) = 0;
+
+ // Set a loop on the sequencer
+ //
+ virtual void setLoop(long loopStartSec,
+ long loopStartNsec,
+ long loopEndSec,
+ long loopEndNsec) = 0;
+
+ // Get the status of the Sequencer
+ //
+ virtual unsigned int getSoundDriverStatus(const QString &guiVersion) = 0;
+
+ // Add and delete audio files on the Sequencer
+ //
+ virtual int addAudioFile(const QString &fileName, int id) = 0;
+ virtual int removeAudioFile(int id) = 0;
+ virtual void clearAllAudioFiles() = 0;
+
+ // Single set function as the MappedInstrument is so lightweight.
+ // Any mods on the GUI are sent only through this method.
+ //
+ virtual void setMappedInstrument(int type,
+ unsigned char channel,
+ unsigned int id) = 0;
+
+ // The GUI can use this method to process an immediate selection
+ // of MappedEvents (Program Changes, SysExs, async Events etc).
+ //
+ virtual void processSequencerSlice(MappedComposition mC) = 0;
+
+
+ // Horrible ugly ugly ugly interface for single MappedEvents
+ // just until we implement the proper MappedEvent interface
+ //
+ virtual void processMappedEvent(unsigned int id,
+ int type,
+ unsigned char pitch,
+ unsigned char velocity,
+ long absTimeSec,
+ long absTimeNsec,
+ long durationSec,
+ long durationNsec,
+ long audioStartMarkerSec,
+ long audioStartMarkerNsec) = 0;
+
+ // The proper implementation
+ //
+ virtual void processMappedEvent(MappedEvent mE) = 0;
+
+ // Return device id following last existing one -- you can treat
+ // this as "number of devices" but there might be some holes if
+ // devices were deleted, which you will recognise because
+ // getMappedDevice(id) will return a device with id NO_DEVICE
+ //
+ virtual unsigned int getDevices() = 0;
+
+ // Return device by number
+ //
+ virtual MappedDevice getMappedDevice(unsigned int id) = 0;
+
+ // Query whether the driver implements device reconnection.
+ // Returns a non-zero value if the addDevice, removeDevice,
+ // getConnections, getConnection and setConnection methods
+ // may be used with devices of the given type.
+ //
+ virtual int canReconnect(int deviceType) = 0;
+
+ // Create a device of the given type and direction (corresponding
+ // to MidiDevice::DeviceDirection enum) and return its id.
+ // The device will have no connection by default. Direction is
+ // currently ignored for non-MIDI devices.
+ // Do not use this unless canReconnect(type) returned true.
+ //
+ virtual unsigned int addDevice(int type, unsigned int direction) = 0;
+
+ // Remove the device of the given id.
+ // Ignored if driver does not permit changing the number of devices
+ // (i.e. if canReconnect(type) would return false when given the
+ // type of the supplied device).
+ //
+ virtual void removeDevice(unsigned int id) = 0;
+
+ // Rename the given device.
+ // Ignored if the driver does not permit this operation.
+ //
+ virtual void renameDevice(unsigned int id, QString name) = 0;
+
+ // Return the number of permissible connections for a device of
+ // the given type and direction (corresponding to MidiDevice::
+ // DeviceDirection enum). Direction is ignored for non-MIDI devices.
+ // Returns zero if devices of this type are non-reconnectable
+ // (i.e. if canReconnect(type) would return false).
+ //
+ virtual unsigned int getConnections(int type, unsigned int direction) = 0;
+
+ // Return one of the set of permissible connections for a device of
+ // the given type and direction (corresponding to MidiDevice::
+ // DeviceDirection enum). Direction is ignored for non-MIDI devices.
+ // Returns the empty string for invalid parameters.
+ //
+ virtual QString getConnection(int type,
+ unsigned int direction,
+ unsigned int connectionNo) = 0;
+
+ // Reconnect a particular device.
+ // Ignored if driver does not permit reconnections or the connection
+ // is not one of the permissible set for that device.
+ //
+ virtual void setConnection(unsigned int deviceId, QString connection) = 0;
+
+ // Reconnect a device to a particular connection or to the closest
+ // thing to that connection currently available (using some heuristic).
+ // Ignored if driver does not permit reconnections.
+ //
+ virtual void setPlausibleConnection(unsigned int deviceId,
+ QString idealConnection) = 0;
+
+ // Return the number of different timers we are capable of
+ // sychronising against. This may return 0 if the driver has no
+ // ability to change the current timer.
+ //
+ virtual unsigned int getTimers() = 0;
+
+ // Return the name of a timer from the available set (where
+ // n is between 0 and the return value from getTimers() - 1).
+ //
+ virtual QString getTimer(unsigned int n) = 0;
+
+ // Return the name of the timer we are currently synchronising
+ // against.
+ //
+ virtual QString getCurrentTimer() = 0;
+
+ // Set the timer we are currently synchronising against.
+ // Invalid arguments are simply ignored.
+ //
+ virtual void setCurrentTimer(QString timer) = 0;
+
+ virtual void setLowLatencyMode(bool lowLatMode) = 0;
+
+ // Fetch audio play latencies
+ //
+ virtual MappedRealTime getAudioPlayLatency() = 0;
+ virtual MappedRealTime getAudioRecordLatency() = 0;
+
+ // Set a property on a MappedObject
+ //
+ virtual void setMappedProperty(int id,
+ const QString &property,
+ float value) = 0;
+
+ // Set many properties on many MappedObjects
+ //
+ virtual void setMappedProperties(const MappedObjectIdList &ids,
+ const MappedObjectPropertyList &properties,
+ const MappedObjectValueList &values) = 0;
+
+ // Set a string property on a MappedObject
+ //
+ virtual void setMappedProperty(int id,
+ const QString &property,
+ const QString &value) = 0;
+
+ // Set a MappedObject to a property list
+ //
+ virtual void setMappedPropertyList(
+ int id,
+ const QString &property,
+ const MappedObjectPropertyList &values) = 0;
+
+ // Get a mapped object id for a object type
+ //
+ virtual int getMappedObjectId(int type) = 0;
+
+ // Get a list of properties of a certain type from an object
+ //
+ virtual std::vector<QString> getPropertyList(int id,
+ const QString &property) = 0;
+
+ // Get a list of available plugins
+ //
+ virtual std::vector<QString> getPluginInformation() = 0;
+
+ // Nasty hack: program name/number mappings are one thing that
+ // mapped object properties can't cope with
+ //
+ virtual QString getPluginProgram(int mappedId, int bank, int program) = 0;
+
+ // Nastier hack: return value is bank << 16 + program
+ //
+ virtual unsigned long getPluginProgram(int mappedId, const QString &name) = 0;
+
+ // Cheat - we can't use a call (getPropertyList) during playback
+ // so we use this method to set port N on plugin X.
+ //
+ virtual void setMappedPort(int pluginIn,
+ unsigned long id,
+ float value) = 0;
+
+ virtual float getMappedPort(int pluginIn,
+ unsigned long id) = 0;
+
+ // Create a (transient, writeable) object
+ //
+ virtual int createMappedObject(int type) = 0;
+
+ // Destroy an object (returns a bool but for KDE2 DCOP compat we
+ // use an int of course).
+ //
+ virtual int destroyMappedObject(int id) = 0;
+
+ // Connect two objects
+ //
+ virtual void connectMappedObjects(int id1, int id2) = 0;
+
+ // Disconnect two objects
+ //
+ virtual void disconnectMappedObjects(int id1, int id2) = 0;
+
+ // Disconnect an object from everything
+ //
+ virtual void disconnectMappedObject(int id) = 0;
+
+ // Driver sample rate
+ //
+ virtual unsigned int getSampleRate() const = 0;
+
+ // Initialise/Reinitialise the studio back down to read only objects
+ // and set to defaults.
+ //
+ virtual void clearStudio() = 0;
+
+ // Allow the GUI to tell the sequence the duration of a quarter
+ // note when the TEMPO changes - this is to allow the sequencer
+ // to generate MIDI clock (at 24 PPQN).
+ //
+ virtual void setQuarterNoteLength(long timeSec, long timeNsec) = 0;
+
+ // Return a (potentially lengthy) human-readable status log
+ //
+ virtual QString getStatusLog() = 0;
+
+ // Debug stuff, to check MmappedSegment::iterator
+ virtual void dumpFirstSegment() = 0;
+
+ /// Remap a segment while playing
+ virtual void remapSegment(const QString& filename, size_t newSize) = 0;
+
+ /// Add a segment while playing
+ virtual void addSegment(const QString& filename) = 0;
+
+ /// Delete a segment while playing
+ virtual void deleteSegment(const QString& filename) = 0;
+
+ /// Close all mmapped segments
+ virtual void closeAllSegments() = 0;
+
+ /** Update mute (etc) statuses while playing. The sequencer handles
+ this automatically (with no need for this call) for MIDI events,
+ but it needs to be prodded when an already-playing audio segment
+ drops in or out.
+ */
+ virtual void remapTracks() = 0;
+};
+
+}
+
+#endif // _ROSEGARDENSEQUENCERIFACE_H_
diff --git a/src/sequencer/SequencerMmapper.cpp b/src/sequencer/SequencerMmapper.cpp
new file mode 100644
index 0000000..3a634ba
--- /dev/null
+++ b/src/sequencer/SequencerMmapper.cpp
@@ -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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <sys/mman.h>
+#include <unistd.h>
+#include <errno.h>
+
+#include <kstddirs.h>
+
+#include <qfile.h>
+
+#include "SequencerMmapper.h"
+#include "misc/Debug.h"
+
+#include "base/RealTime.h"
+#include "base/Exception.h"
+#include "sound/MappedEvent.h"
+#include "sound/MappedComposition.h"
+
+namespace Rosegarden
+{
+
+// Seems not to be properly defined under some gcc 2.95 setups
+#ifndef MREMAP_MAYMOVE
+#define MREMAP_MAYMOVE 1
+#endif
+
+SequencerMmapper::SequencerMmapper():
+ m_fileName(createFileName()),
+ m_fd( -1),
+ m_mmappedBuffer(0),
+ m_mmappedSize(sizeof(SequencerDataBlock))
+{
+ // just in case
+ QFile::remove
+ (m_fileName);
+
+ m_fd = ::open(m_fileName.latin1(),
+ O_RDWR | O_CREAT | O_TRUNC,
+ S_IRUSR | S_IWUSR);
+
+ if (m_fd < 0) {
+ SEQUENCER_DEBUG << "SequencerMmapper : Couldn't open " << m_fileName
+ << endl;
+ throw Exception("Couldn't open " + std::string(m_fileName.data()));
+ }
+
+ setFileSize(m_mmappedSize);
+
+ //
+ // mmap() file for writing
+ //
+ m_mmappedBuffer = ::mmap(0, m_mmappedSize,
+ PROT_READ | PROT_WRITE,
+ MAP_SHARED, m_fd, 0);
+
+ if (m_mmappedBuffer == (void*) - 1) {
+ SEQUENCER_DEBUG <<
+ QString("mmap failed : (%1) %2\n").arg(errno).arg(strerror(errno));
+ throw Exception("mmap failed");
+ }
+
+ SEQUENCER_DEBUG << "SequencerMmapper : mmap size : " << m_mmappedSize
+ << " at " << (void*)m_mmappedBuffer << endl;
+
+ // initialise
+ init();
+}
+
+SequencerMmapper::~SequencerMmapper()
+{
+ ::munmap(m_mmappedBuffer, m_mmappedSize);
+ ::close(m_fd);
+ QFile::remove
+ (m_fileName);
+}
+
+void
+SequencerMmapper::init()
+{
+ SEQUENCER_DEBUG << "SequencerMmapper::init()\n";
+
+ m_sequencerDataBlock = new (m_mmappedBuffer)
+ SequencerDataBlock(true);
+
+ ::msync(m_mmappedBuffer, m_mmappedSize, MS_ASYNC);
+}
+
+void
+SequencerMmapper::setFileSize(size_t size)
+{
+ SEQUENCER_DEBUG << "SequencerMmapper : setting size of "
+ << m_fileName << " to " << size << endl;
+ // rewind
+ ::lseek(m_fd, 0, SEEK_SET);
+
+ // enlarge the file
+ // (seek() to wanted size, then write a byte)
+ //
+ if (::lseek(m_fd, size - 1, SEEK_SET) == -1) {
+ std::cerr << "WARNING: SequencerMmapper : Couldn't lseek in " << m_fileName
+ << " to " << size << std::endl;
+ throw Exception("lseek failed");
+ }
+
+ if (::write(m_fd, "\0", 1) != 1) {
+ std::cerr << "WARNING: SequencerMmapper : Couldn't write byte in "
+ << m_fileName << std::endl;
+ throw Exception("write failed");
+ }
+}
+
+QString
+SequencerMmapper::createFileName()
+{
+ return KGlobal::dirs()->resourceDirs("tmp").last() +
+ "/rosegarden_sequencer_timing_block";
+}
+
+}
+
diff --git a/src/sequencer/SequencerMmapper.h b/src/sequencer/SequencerMmapper.h
new file mode 100644
index 0000000..f35c2a7
--- /dev/null
+++ b/src/sequencer/SequencerMmapper.h
@@ -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-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _SEQUENCERMAPPER_H_
+#define _SEQUENCERMAPPER_H_
+
+#include "sound/SequencerDataBlock.h"
+#include "base/RealTime.h"
+
+namespace Rosegarden
+{
+
+class MappedEvent;
+class MappedComposition;
+
+
+class SequencerMmapper
+{
+public:
+ SequencerMmapper();
+ ~SequencerMmapper();
+
+ void updatePositionPointer(RealTime time) {
+ m_sequencerDataBlock->setPositionPointer(time);
+ }
+
+ void updateVisual(MappedEvent *ev) {
+ m_sequencerDataBlock->setVisual(ev);
+ }
+
+ void updateRecordingBuffer(MappedComposition *mC) {
+ m_sequencerDataBlock->addRecordedEvents(mC);
+ }
+
+ void setTrackLevel(TrackId track, const LevelInfo &info) {
+ m_sequencerDataBlock->setTrackLevel(track, info);
+ }
+
+ void setInstrumentLevel(InstrumentId id,
+ const LevelInfo &info) {
+ m_sequencerDataBlock->setInstrumentLevel(id, info);
+ }
+
+ void setInstrumentRecordLevel(InstrumentId id,
+ const LevelInfo &info) {
+ m_sequencerDataBlock->setInstrumentRecordLevel(id, info);
+ }
+
+ void setSubmasterLevel(int submaster,
+ const LevelInfo &info) {
+ m_sequencerDataBlock->setSubmasterLevel(submaster, info);
+ }
+
+ void setMasterLevel(const LevelInfo &info) {
+ m_sequencerDataBlock->setMasterLevel(info);
+ }
+
+ SequencerDataBlock *getSequencerDataBlock() {
+ return m_sequencerDataBlock;
+ }
+ void setControlBlock(ControlBlock *cb) {
+ m_sequencerDataBlock->setControlBlock(cb);
+ }
+
+protected:
+ void init();
+ void setFileSize(size_t);
+ QString createFileName();
+
+ //--------------- Data members ---------------------------------
+ //
+ QString m_fileName;
+ int m_fd;
+ void* m_mmappedBuffer;
+ size_t m_mmappedSize;
+ SequencerDataBlock *m_sequencerDataBlock;
+};
+
+}
+
+#endif // _SEQUENCERMAPPER_H_
diff --git a/src/sequencer/main.cpp b/src/sequencer/main.cpp
new file mode 100644
index 0000000..aee5bc5
--- /dev/null
+++ b/src/sequencer/main.cpp
@@ -0,0 +1,246 @@
+// -*- c-indentation-style:"stroustrup" c-basic-offset: 4 -*-
+
+/*
+ Rosegarden
+ A sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <bownie@bownie.com>
+
+ 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 "RosegardenSequencerApp.h"
+
+#include <signal.h>
+#include <iostream>
+#include <unistd.h>
+#include <sys/time.h>
+
+#include <kcmdlineargs.h>
+#include <kaboutdata.h>
+#include <klocale.h>
+#include <dcopclient.h>
+
+#include "base/Profiler.h"
+#include "sound/MappedComposition.h"
+
+#include "gui/application/RosegardenDCOP.h"
+#include "misc/Debug.h"
+
+using std::cout;
+using std::cerr;
+using std::endl;
+
+using namespace Rosegarden;
+
+static const char *description = I18N_NOOP("RosegardenSequencer");
+static RosegardenSequencerApp *roseSeq = 0;
+
+static KCmdLineOptions options[] =
+ {
+ // { "+[File]", I18N_NOOP("file to open"), 0 },
+ // INSERT YOUR COMMANDLINE OPTIONS HERE
+ { "+[playback_1 playback_2 capture_1 capture_2]",
+ I18N_NOOP("JACK playback and capture ports"), 0 },
+ { 0, 0, 0 }
+ };
+
+static bool _exiting = false;
+static sigset_t _signals;
+
+static void
+signalHandler(int /*sig*/)
+{
+ _exiting = true;
+ std::cerr << "Is that the time!?" << endl;
+}
+
+int main(int argc, char *argv[])
+{
+ srandom((unsigned int)time(0) * (unsigned int)getpid());
+
+ // Block signals during startup, so that child threads (inheriting
+ // this mask) ignore them; then after startup we can unblock them
+ // for this thread only. This trick picked up from the jackd code.
+ sigemptyset (&_signals);
+ sigaddset(&_signals, SIGHUP);
+ sigaddset(&_signals, SIGINT);
+ sigaddset(&_signals, SIGQUIT);
+ sigaddset(&_signals, SIGPIPE);
+ sigaddset(&_signals, SIGTERM);
+ sigaddset(&_signals, SIGUSR1);
+ sigaddset(&_signals, SIGUSR2);
+ pthread_sigmask(SIG_BLOCK, &_signals, 0);
+
+ KAboutData aboutData( "rosegardensequencer",
+ I18N_NOOP("RosegardenSequencer"),
+ VERSION, description, KAboutData::License_GPL,
+ "(c) 2000-2008, Guillaume Laurent, Chris Cannam, Richard Bown");
+ aboutData.addAuthor("Guillaume Laurent, Chris Cannam, Richard Bown", 0, "glaurent@telegraph-road.org, cannam@all-day-breakfast.com, bownie@bownie.com");
+ KCmdLineArgs::init( argc, argv, &aboutData );
+ KCmdLineArgs::addCmdLineOptions( options ); // Add our own options.
+
+ // Parse cmd line args
+ //
+ /*KCmdLineArgs *args =*/
+ KCmdLineArgs::parsedArgs();
+ KApplication app;
+
+ if (app.isRestored()) {
+ SEQUENCER_DEBUG << "RosegardenSequencer - we're being session-restored - that's not supposed to happen\n";
+ app.quit(); // don't do session restore -- GUI will start a sequencer
+ } else {
+ roseSeq = new RosegardenSequencerApp;
+ }
+
+ QObject::connect(&app, SIGNAL(lastWindowClosed()), &app, SLOT(quit()));
+
+ app.disableSessionManagement(); // we don't want to be
+ // saved/restored by session
+ // management, only run by the GUI
+
+ // Started OK
+ //
+ SEQUENCER_DEBUG << "RosegardenSequencer - started OK" << endl;
+
+ // Register signal handlers and unblock signals
+ //
+ signal(SIGINT, signalHandler);
+ signal(SIGTERM, signalHandler);
+ signal(SIGHUP, signalHandler);
+ signal(SIGQUIT, signalHandler);
+ pthread_sigmask(SIG_UNBLOCK, &_signals, 0);
+
+ // Now we can enter our specialised event loop.
+ // For each pass through we wait for some pending
+ // events. We check status on the way through and
+ // act accordingly. DCOP events fire back and
+ // forth processed in the event loop changing
+ // state and hopefully controlling and providing
+ // feedback. We also put in some sleep time to
+ // make sure the loop doesn't eat up all the
+ // processor - we're not in that much of a rush!
+ //
+ TransportStatus lastSeqStatus = roseSeq->getStatus();
+
+ RealTime sleepTime = RealTime(0, 10000000);
+
+ while (!_exiting && roseSeq && roseSeq->getStatus() != QUIT) {
+
+ bool atLeisure = true;
+
+ switch (roseSeq->getStatus()) {
+ case STARTING_TO_PLAY:
+ if (!roseSeq->startPlaying()) {
+ // send result failed and stop Sequencer
+ roseSeq->setStatus(STOPPING);
+ } else {
+ roseSeq->setStatus(PLAYING);
+ }
+ break;
+
+ case PLAYING:
+ if (!roseSeq->keepPlaying()) {
+ // there's a problem or the piece has
+ // finished - so stop playing
+ roseSeq->setStatus(STOPPING);
+ } else {
+ // process any async events
+ //
+ roseSeq->processAsynchronousEvents();
+ }
+ break;
+
+ case STARTING_TO_RECORD:
+ if (!roseSeq->startPlaying()) {
+ roseSeq->setStatus(STOPPING);
+ } else {
+ roseSeq->setStatus(RECORDING);
+ }
+ break;
+
+ case RECORDING:
+ if (!roseSeq->keepPlaying()) {
+ // there's a problem or the piece has
+ // finished - so stop playing
+ roseSeq->setStatus(STOPPING);
+ } else {
+ // Now process any incoming MIDI events
+ // and return them to the gui
+ //
+ roseSeq->processRecordedMidi();
+
+ // Now process any incoming audio
+ // and return it to the gui
+ //
+ roseSeq->processRecordedAudio();
+
+ // Still process these so we can send up
+ // audio levels as MappedEvents
+ //
+ roseSeq->processAsynchronousEvents();
+ }
+ break;
+
+ case STOPPING:
+ // There's no call to roseSeq to actually process the
+ // stop, because this arises from a DCOP call from the GUI
+ // direct to roseSeq to start with
+ roseSeq->setStatus(STOPPED);
+ SEQUENCER_DEBUG << "RosegardenSequencer - Stopped" << endl;
+ break;
+
+ case RECORDING_ARMED:
+ SEQUENCER_DEBUG << "RosegardenSequencer - "
+ << "Sequencer can't enter \""
+ << "RECORDING_ARMED\" state - "
+ << "internal error"
+ << endl;
+ break;
+
+ case STOPPED:
+ default:
+ roseSeq->processAsynchronousEvents();
+ break;
+ }
+
+ // Update internal clock and send pointer position
+ // change event to GUI - this is the heartbeat of
+ // the Sequencer - it doesn't tick over without
+ // this call.
+ //
+ // Also attempt to send the MIDI clock at this point.
+ //
+ roseSeq->updateClocks();
+
+ // we want to process transport changes immediately
+ if (roseSeq->checkExternalTransport()) {
+ atLeisure = false;
+ } else if (lastSeqStatus != roseSeq->getStatus()) {
+ SEQUENCER_DEBUG << "Sequencer status changed from " << lastSeqStatus << " to " << roseSeq->getStatus() << endl;
+ roseSeq->notifySequencerStatus();
+ lastSeqStatus = roseSeq->getStatus();
+ atLeisure = false;
+ }
+
+ app.processEvents();
+ if (atLeisure)
+ roseSeq->sleep(sleepTime);
+ }
+
+ delete roseSeq;
+
+ std::cerr << "Toodle-pip." << endl;
+ return 0;
+}
+
diff --git a/src/sound/AlsaDriver.cpp b/src/sound/AlsaDriver.cpp
new file mode 100644
index 0000000..9d512d9
--- /dev/null
+++ b/src/sound/AlsaDriver.cpp
@@ -0,0 +1,5476 @@
+// -*- c-indentation-style:"stroustrup" c-basic-offset: 4 -*-
+
+/*
+ Rosegarden
+ A sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <bownie@bownie.com>
+
+ 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 <iostream>
+#include "misc/Debug.h"
+#include <cstdlib>
+#include <cstdio>
+#include <algorithm>
+
+#ifdef HAVE_ALSA
+
+// ALSA
+#include <alsa/asoundlib.h>
+#include <alsa/seq_event.h>
+#include <alsa/version.h>
+#include <alsa/seq.h>
+
+#include "AlsaDriver.h"
+#include "AlsaPort.h"
+#include "ExternalTransport.h"
+#include "MappedInstrument.h"
+#include "Midi.h"
+#include "MappedStudio.h"
+#include "misc/Strings.h"
+#include "MappedCommon.h"
+#include "MappedEvent.h"
+#include "Audit.h"
+#include "AudioPlayQueue.h"
+#include "ExternalTransport.h"
+
+#include <qregexp.h>
+#include <pthread.h>
+
+
+//#define DEBUG_ALSA 1
+//#define DEBUG_PROCESS_MIDI_OUT 1
+//#define DEBUG_PROCESS_SOFT_SYNTH_OUT 1
+//#define MTC_DEBUG 1
+
+// This driver implements MIDI in and out via the ALSA (www.alsa-project.org)
+// sequencer interface.
+
+using std::cerr;
+using std::endl;
+
+static size_t _debug_jack_frame_count = 0;
+
+#define AUTO_TIMER_NAME "(auto)"
+
+
+namespace Rosegarden
+{
+
+#define FAILURE_REPORT_COUNT 256
+static MappedEvent::FailureCode _failureReports[FAILURE_REPORT_COUNT];
+static int _failureReportWriteIndex = 0;
+static int _failureReportReadIndex = 0;
+
+AlsaDriver::AlsaDriver(MappedStudio *studio):
+ SoundDriver(studio,
+ std::string("[ALSA library version ") +
+ std::string(SND_LIB_VERSION_STR) +
+ std::string(", module version ") +
+ getAlsaModuleVersionString() +
+ std::string(", kernel version ") +
+ getKernelVersionString() +
+ "]"),
+ m_client( -1),
+ m_inputPort( -1),
+ m_syncOutputPort( -1),
+ m_controllerPort( -1),
+ m_queue( -1),
+ m_maxClients( -1),
+ m_maxPorts( -1),
+ m_maxQueues( -1),
+ m_midiInputPortConnected(false),
+ m_midiSyncAutoConnect(false),
+ m_alsaPlayStartTime(0, 0),
+ m_alsaRecordStartTime(0, 0),
+ m_loopStartTime(0, 0),
+ m_loopEndTime(0, 0),
+ m_eat_mtc(0),
+ m_looping(false),
+ m_haveShutdown(false)
+#ifdef HAVE_LIBJACK
+ , m_jackDriver(0)
+#endif
+ , m_queueRunning(false)
+ , m_portCheckNeeded(false),
+ m_needJackStart(NeedNoJackStart),
+ m_doTimerChecks(false),
+ m_firstTimerCheck(true),
+ m_timerRatio(0),
+ m_timerRatioCalculated(false)
+
+{
+ Audit audit;
+ audit << "Rosegarden " << VERSION << " - AlsaDriver "
+ << m_name << std::endl;
+}
+
+AlsaDriver::~AlsaDriver()
+{
+ if (!m_haveShutdown) {
+ std::cerr << "WARNING: AlsaDriver::shutdown() was not called before destructor, calling now" << std::endl;
+ shutdown();
+ }
+}
+
+int
+AlsaDriver::checkAlsaError(int rc, const char *
+#ifdef DEBUG_ALSA
+ message
+#endif
+ )
+{
+#ifdef DEBUG_ALSA
+ if (rc < 0) {
+ std::cerr << "AlsaDriver::"
+ << message
+ << ": " << rc
+ << " (" << snd_strerror(rc) << ")"
+ << std::endl;
+ }
+#endif
+ return rc;
+}
+
+void
+AlsaDriver::shutdown()
+{
+#ifdef DEBUG_ALSA
+ std::cerr << "AlsaDriver::~AlsaDriver - shutting down" << std::endl;
+#endif
+
+ processNotesOff(getAlsaTime(), true, true);
+
+#ifdef HAVE_LIBJACK
+ delete m_jackDriver;
+ m_jackDriver = 0;
+#endif
+
+ if (m_midiHandle) {
+#ifdef DEBUG_ALSA
+ std::cerr << "AlsaDriver::shutdown - closing MIDI client" << std::endl;
+#endif
+
+ checkAlsaError(snd_seq_stop_queue(m_midiHandle, m_queue, 0), "shutdown(): stopping queue");
+ checkAlsaError(snd_seq_drain_output(m_midiHandle), "shutdown(): drain output");
+#ifdef DEBUG_ALSA
+
+ std::cerr << "AlsaDriver::shutdown - stopped queue" << std::endl;
+#endif
+
+ snd_seq_close(m_midiHandle);
+#ifdef DEBUG_ALSA
+
+ std::cerr << "AlsaDriver::shutdown - closed MIDI handle" << std::endl;
+#endif
+
+ m_midiHandle = 0;
+ }
+
+ DataBlockRepository::clear();
+
+ m_haveShutdown = true;
+}
+
+void
+AlsaDriver::setLoop(const RealTime &loopStart, const RealTime &loopEnd)
+{
+ m_loopStartTime = loopStart;
+ m_loopEndTime = loopEnd;
+
+ // currently we use this simple test for looping - it might need
+ // to get more sophisticated in the future.
+ //
+ if (m_loopStartTime != m_loopEndTime)
+ m_looping = true;
+ else
+ m_looping = false;
+}
+
+void
+AlsaDriver::getSystemInfo()
+{
+ int err;
+ snd_seq_system_info_t *sysinfo;
+
+ snd_seq_system_info_alloca(&sysinfo);
+
+ if ((err = snd_seq_system_info(m_midiHandle, sysinfo)) < 0) {
+ std::cerr << "System info error: " << snd_strerror(err)
+ << std::endl;
+ reportFailure(MappedEvent::FailureALSACallFailed);
+ m_maxQueues = 0;
+ m_maxClients = 0;
+ m_maxPorts = 0;
+ return ;
+ }
+
+ m_maxQueues = snd_seq_system_info_get_queues(sysinfo);
+ m_maxClients = snd_seq_system_info_get_clients(sysinfo);
+ m_maxPorts = snd_seq_system_info_get_ports(sysinfo);
+}
+
+void
+AlsaDriver::showQueueStatus(int queue)
+{
+ int err, idx, min, max;
+ snd_seq_queue_status_t *status;
+
+ snd_seq_queue_status_alloca(&status);
+ min = queue < 0 ? 0 : queue;
+ max = queue < 0 ? m_maxQueues : queue + 1;
+
+ for (idx = min; idx < max; ++idx) {
+ if ((err = snd_seq_get_queue_status(m_midiHandle, idx, status)) < 0) {
+
+ if (err == -ENOENT)
+ continue;
+
+ std::cerr << "Client " << idx << " info error: "
+ << snd_strerror(err) << std::endl;
+
+ reportFailure(MappedEvent::FailureALSACallFailed);
+ return ;
+ }
+
+#ifdef DEBUG_ALSA
+ std::cerr << "Queue " << snd_seq_queue_status_get_queue(status)
+ << std::endl;
+
+ std::cerr << "Tick = "
+ << snd_seq_queue_status_get_tick_time(status)
+ << std::endl;
+
+ std::cerr << "Realtime = "
+ << snd_seq_queue_status_get_real_time(status)->tv_sec
+ << "."
+ << snd_seq_queue_status_get_real_time(status)->tv_nsec
+ << std::endl;
+
+ std::cerr << "Flags = 0x"
+ << snd_seq_queue_status_get_status(status)
+ << std::endl;
+#endif
+
+ }
+
+}
+
+
+void
+AlsaDriver::generateTimerList()
+{
+ // Enumerate the available timers
+
+ snd_timer_t *timerHandle;
+
+ snd_timer_id_t *timerId;
+ snd_timer_info_t *timerInfo;
+
+ snd_timer_id_alloca(&timerId);
+ snd_timer_info_alloca(&timerInfo);
+
+ snd_timer_query_t *timerQuery;
+ char timerName[64];
+
+ m_timers.clear();
+
+ if (snd_timer_query_open(&timerQuery, "hw", 0) >= 0) {
+
+ snd_timer_id_set_class(timerId, SND_TIMER_CLASS_NONE);
+
+ while (1) {
+
+ if (snd_timer_query_next_device(timerQuery, timerId) < 0)
+ break;
+ if (snd_timer_id_get_class(timerId) < 0)
+ break;
+
+ AlsaTimerInfo info = {
+ snd_timer_id_get_class(timerId),
+ snd_timer_id_get_sclass(timerId),
+ snd_timer_id_get_card(timerId),
+ snd_timer_id_get_device(timerId),
+ snd_timer_id_get_subdevice(timerId),
+ "",
+ 0
+ };
+
+ if (info.card < 0)
+ info.card = 0;
+ if (info.device < 0)
+ info.device = 0;
+ if (info.subdevice < 0)
+ info.subdevice = 0;
+
+ // std::cerr << "got timer: class " << info.clas << std::endl;
+
+ sprintf(timerName, "hw:CLASS=%i,SCLASS=%i,CARD=%i,DEV=%i,SUBDEV=%i",
+ info.clas, info.sclas, info.card, info.device, info.subdevice);
+
+ if (snd_timer_open(&timerHandle, timerName, SND_TIMER_OPEN_NONBLOCK) < 0) {
+ std::cerr << "Failed to open timer: " << timerName << std::endl;
+ continue;
+ }
+
+ if (snd_timer_info(timerHandle, timerInfo) < 0)
+ continue;
+
+ info.name = snd_timer_info_get_name(timerInfo);
+ info.resolution = snd_timer_info_get_resolution(timerInfo);
+ snd_timer_close(timerHandle);
+
+ // std::cerr << "adding timer: " << info.name << std::endl;
+
+ m_timers.push_back(info);
+ }
+
+ snd_timer_query_close(timerQuery);
+ }
+}
+
+
+std::string
+AlsaDriver::getAutoTimer(bool &wantTimerChecks)
+{
+ Audit audit;
+
+ // Look for the apparent best-choice timer.
+
+ if (m_timers.empty())
+ return "";
+
+ // The system RTC timer ought to be good, but it doesn't look like
+ // a very safe choice -- we've seen some system lockups apparently
+ // connected with use of this timer on 2.6 kernels. So we avoid
+ // using that as an auto option.
+
+ // Looks like our most reliable options for timers are, in order:
+ //
+ // 1. System timer if at 1000Hz, with timer checks (i.e. automatic
+ // drift correction against PCM frame count). Only available
+ // when JACK is running.
+ //
+ // 2. PCM playback timer currently in use by JACK (no drift, but
+ // suffers from jitter).
+ //
+ // 3. System timer if at 1000Hz.
+ //
+ // 4. System RTC timer.
+ //
+ // 5. System timer.
+
+ // As of Linux kernel 2.6.13 (?) the default system timer
+ // resolution has been reduced from 1000Hz to 250Hz, giving us
+ // only 4ms accuracy instead of 1ms. This may be better than the
+ // 10ms available from the stock 2.4 kernel, but it's not enough
+ // for really solid MIDI timing. If JACK is running at 44.1 or
+ // 48KHz with a buffer size less than 256 frames, then the PCM
+ // timer will give us less jitter. Even at 256 frames, it may be
+ // preferable in practice just because it's simpler.
+
+ // However, we can't safely choose the PCM timer over the system
+ // timer unless the latter has really awful resolution, because we
+ // don't know for certain which PCM JACK is using. We guess at
+ // hw:0 for the moment, which gives us a stuck timer problem if
+ // it's actually using something else. So if the system timer
+ // runs at 250Hz, we really have to choose it anyway and just give
+ // a warning.
+
+ bool pcmTimerAccepted = false;
+ wantTimerChecks = false; // for most options
+
+ bool rtcCouldBeOK = false;
+
+#ifdef HAVE_LIBJACK
+ if (m_jackDriver) {
+ wantTimerChecks = true;
+ pcmTimerAccepted = true;
+ }
+#endif
+
+ // look for a high frequency system timer
+
+ for (std::vector<AlsaTimerInfo>::iterator i = m_timers.begin();
+ i != m_timers.end(); ++i) {
+ if (i->sclas != SND_TIMER_SCLASS_NONE)
+ continue;
+ if (i->clas == SND_TIMER_CLASS_GLOBAL) {
+ if (i->device == SND_TIMER_GLOBAL_SYSTEM) {
+ long hz = 1000000000 / i->resolution;
+ if (hz >= 750) {
+ return i->name;
+ }
+ }
+ }
+ }
+
+ // Look for the system RTC timer if available. This has been
+ // known to hang some real-time kernels, but reports suggest that
+ // recent kernels are OK. Avoid if the kernel is older than
+ // 2.6.20 or the ALSA driver is older than 1.0.14.
+
+ if (versionIsAtLeast(getAlsaModuleVersionString(),
+ 1, 0, 14) &&
+ versionIsAtLeast(getKernelVersionString(),
+ 2, 6, 20)) {
+
+ rtcCouldBeOK = true;
+
+ for (std::vector<AlsaTimerInfo>::iterator i = m_timers.begin();
+ i != m_timers.end(); ++i) {
+ if (i->sclas != SND_TIMER_SCLASS_NONE) continue;
+ if (i->clas == SND_TIMER_CLASS_GLOBAL) {
+ if (i->device == SND_TIMER_GLOBAL_RTC) {
+ return i->name;
+ }
+ }
+ }
+ }
+
+ // look for the first PCM playback timer; that's all we know about
+ // for now (until JACK becomes able to tell us which PCM it's on)
+
+ if (pcmTimerAccepted) {
+
+ for (std::vector<AlsaTimerInfo>::iterator i = m_timers.begin();
+ i != m_timers.end(); ++i) {
+ if (i->sclas != SND_TIMER_SCLASS_NONE)
+ continue;
+ if (i->clas == SND_TIMER_CLASS_PCM) {
+ if (i->resolution != 0) {
+ long hz = 1000000000 / i->resolution;
+ if (hz >= 750) {
+ wantTimerChecks = false; // pointless with PCM timer
+ return i->name;
+ } else {
+ audit << "PCM timer: inadequate resolution " << i->resolution << std::endl;
+ }
+ }
+ }
+ }
+ }
+
+ // next look for slow, unpopular 100Hz (2.4) or 250Hz (2.6) system timer
+
+ for (std::vector<AlsaTimerInfo>::iterator i = m_timers.begin();
+ i != m_timers.end(); ++i) {
+ if (i->sclas != SND_TIMER_SCLASS_NONE)
+ continue;
+ if (i->clas == SND_TIMER_CLASS_GLOBAL) {
+ if (i->device == SND_TIMER_GLOBAL_SYSTEM) {
+ audit << "Using low-resolution system timer, sending a warning" << std::endl;
+ if (rtcCouldBeOK) {
+ reportFailure(MappedEvent::WarningImpreciseTimerTryRTC);
+ } else {
+ reportFailure(MappedEvent::WarningImpreciseTimer);
+ }
+ return i->name;
+ }
+ }
+ }
+
+ // falling back to something that almost certainly won't work,
+ // if for any reason all of the above failed
+
+ return m_timers.begin()->name;
+}
+
+
+
+void
+AlsaDriver::generatePortList(AlsaPortList *newPorts)
+{
+ Audit audit;
+ AlsaPortList alsaPorts;
+
+ snd_seq_client_info_t *cinfo;
+ snd_seq_port_info_t *pinfo;
+ int client;
+ unsigned int writeCap = SND_SEQ_PORT_CAP_SUBS_WRITE | SND_SEQ_PORT_CAP_WRITE;
+ unsigned int readCap = SND_SEQ_PORT_CAP_SUBS_READ | SND_SEQ_PORT_CAP_READ;
+
+ snd_seq_client_info_alloca(&cinfo);
+ snd_seq_client_info_set_client(cinfo, -1);
+
+ audit << std::endl << " ALSA Client information:"
+ << std::endl << std::endl;
+
+ // Get only the client ports we're interested in and store them
+ // for sorting and then device creation.
+ //
+ while (snd_seq_query_next_client(m_midiHandle, cinfo) >= 0) {
+ client = snd_seq_client_info_get_client(cinfo);
+ snd_seq_port_info_alloca(&pinfo);
+ snd_seq_port_info_set_client(pinfo, client);
+ snd_seq_port_info_set_port(pinfo, -1);
+
+ // Ignore ourselves and the system client
+ //
+ if (client == m_client || client == 0)
+ continue;
+
+ while (snd_seq_query_next_port(m_midiHandle, pinfo) >= 0) {
+ int client = snd_seq_port_info_get_client(pinfo);
+ int port = snd_seq_port_info_get_port(pinfo);
+ unsigned int clientType = snd_seq_client_info_get_type(cinfo);
+ unsigned int portType = snd_seq_port_info_get_type(pinfo);
+ unsigned int capability = snd_seq_port_info_get_capability(pinfo);
+
+
+ if ((((capability & writeCap) == writeCap) ||
+ ((capability & readCap) == readCap)) &&
+ ((capability & SND_SEQ_PORT_CAP_NO_EXPORT) == 0)) {
+ audit << " "
+ << client << ","
+ << port << " - ("
+ << snd_seq_client_info_get_name(cinfo) << ", "
+ << snd_seq_port_info_get_name(pinfo) << ")";
+
+ PortDirection direction;
+
+ if ((capability & SND_SEQ_PORT_CAP_DUPLEX) ||
+ ((capability & SND_SEQ_PORT_CAP_WRITE) &&
+ (capability & SND_SEQ_PORT_CAP_READ))) {
+ direction = Duplex;
+ audit << "\t\t\t(DUPLEX)";
+ } else if (capability & SND_SEQ_PORT_CAP_WRITE) {
+ direction = WriteOnly;
+ audit << "\t\t(WRITE ONLY)";
+ } else {
+ direction = ReadOnly;
+ audit << "\t\t(READ ONLY)";
+ }
+
+ audit << " [ctype " << clientType << ", ptype " << portType << ", cap " << capability << "]";
+
+ // Generate a unique name using the client id
+ //
+ char portId[40];
+ sprintf(portId, "%d:%d ", client, port);
+
+ std::string fullClientName =
+ std::string(snd_seq_client_info_get_name(cinfo));
+
+ std::string fullPortName =
+ std::string(snd_seq_port_info_get_name(pinfo));
+
+ std::string name;
+
+ // If the first part of the client name is the same as the
+ // start of the port name, just use the port name. otherwise
+ // concatenate.
+ //
+ int firstSpace = fullClientName.find(" ");
+
+ // If no space is found then we try to match the whole string
+ //
+ if (firstSpace < 0)
+ firstSpace = fullClientName.length();
+
+ if (firstSpace > 0 &&
+ int(fullPortName.length()) >= firstSpace &&
+ fullPortName.substr(0, firstSpace) ==
+ fullClientName.substr(0, firstSpace)) {
+ name = portId + fullPortName;
+ } else {
+ name = portId + fullClientName + ": " + fullPortName;
+ }
+
+ // Sanity check for length
+ //
+ if (name.length() > 35)
+ name = portId + fullPortName;
+
+ if (direction == WriteOnly) {
+ name += " (write)";
+ } else if (direction == ReadOnly) {
+ name += " (read)";
+ } else if (direction == Duplex) {
+ name += " (duplex)";
+ }
+
+ AlsaPortDescription *portDescription =
+ new AlsaPortDescription(
+ Instrument::Midi,
+ name,
+ client,
+ port,
+ clientType,
+ portType,
+ capability,
+ direction);
+
+ if (newPorts &&
+ (getPortName(ClientPortPair(client, port)) == "")) {
+ newPorts->push_back(portDescription);
+ }
+
+ alsaPorts.push_back(portDescription);
+
+ audit << std::endl;
+ }
+ }
+ }
+
+ audit << std::endl;
+
+ // Ok now sort by duplexicity
+ //
+ std::sort(alsaPorts.begin(), alsaPorts.end(), AlsaPortCmp());
+ m_alsaPorts = alsaPorts;
+}
+
+
+void
+AlsaDriver::generateInstruments()
+{
+ // Reset these before each Instrument hunt
+ //
+ int audioCount = 0;
+ getAudioInstrumentNumbers(m_audioRunningId, audioCount);
+ m_midiRunningId = MidiInstrumentBase;
+
+ // Clear these
+ //
+ m_instruments.clear();
+ m_devices.clear();
+ m_devicePortMap.clear();
+ m_suspendedPortMap.clear();
+
+ AlsaPortList::iterator it = m_alsaPorts.begin();
+ for (; it != m_alsaPorts.end(); it++) {
+ if ((*it)->m_client == m_client) {
+ std::cerr << "(Ignoring own port " << (*it)->m_client
+ << ":" << (*it)->m_port << ")" << std::endl;
+ continue;
+ } else if ((*it)->m_client == 0) {
+ std::cerr << "(Ignoring system port " << (*it)->m_client
+ << ":" << (*it)->m_port << ")" << std::endl;
+ continue;
+ }
+
+ if ((*it)->isWriteable()) {
+ MappedDevice *device = createMidiDevice(*it, MidiDevice::Play);
+ if (!device) {
+#ifdef DEBUG_ALSA
+ std::cerr << "WARNING: Failed to create play device" << std::endl;
+#else
+
+ ;
+#endif
+
+ } else {
+ addInstrumentsForDevice(device);
+ m_devices.push_back(device);
+ }
+ }
+ if ((*it)->isReadable()) {
+ MappedDevice *device = createMidiDevice(*it, MidiDevice::Record);
+ if (!device) {
+#ifdef DEBUG_ALSA
+ std::cerr << "WARNING: Failed to create record device" << std::endl;
+#else
+
+ ;
+#endif
+
+ } else {
+ m_devices.push_back(device);
+ }
+ }
+ }
+
+#ifdef HAVE_DSSI
+ // Create a number of soft synth Instruments
+ //
+ {
+ MappedInstrument *instr;
+ char number[100];
+ InstrumentId first;
+ int count;
+ getSoftSynthInstrumentNumbers(first, count);
+
+ DeviceId ssiDeviceId = getSpareDeviceId();
+
+ if (m_driverStatus & AUDIO_OK) {
+ for (int i = 0; i < count; ++i) {
+ sprintf(number, " #%d", i + 1);
+ std::string name = "Synth plugin" + std::string(number);
+ instr = new MappedInstrument(Instrument::SoftSynth,
+ i,
+ first + i,
+ name,
+ ssiDeviceId);
+ m_instruments.push_back(instr);
+
+ m_studio->createObject(MappedObject::AudioFader,
+ first + i);
+ }
+
+ MappedDevice *device =
+ new MappedDevice(ssiDeviceId,
+ Device::SoftSynth,
+ "Synth plugin",
+ "Soft synth connection");
+ m_devices.push_back(device);
+ }
+ }
+#endif
+
+#ifdef HAVE_LIBJACK
+
+ // Create a number of audio Instruments - these are just
+ // logical Instruments anyway and so we can create as
+ // many as we like and then use them as Tracks.
+ //
+ {
+ MappedInstrument *instr;
+ char number[100];
+ std::string audioName;
+
+ DeviceId audioDeviceId = getSpareDeviceId();
+
+ if (m_driverStatus & AUDIO_OK)
+ {
+ for (int channel = 0; channel < audioCount; ++channel) {
+ sprintf(number, " #%d", channel + 1);
+ audioName = "Audio" + std::string(number);
+ instr = new MappedInstrument(Instrument::Audio,
+ channel,
+ m_audioRunningId,
+ audioName,
+ audioDeviceId);
+ m_instruments.push_back(instr);
+
+ // Create a fader with a matching id - this is the starting
+ // point for all audio faders.
+ //
+ m_studio->createObject(MappedObject::AudioFader,
+ m_audioRunningId);
+
+ /*
+ std::cerr << "AlsaDriver::generateInstruments - "
+ << "added audio fader (id=" << m_audioRunningId
+ << ")" << std::endl;
+ */
+
+ m_audioRunningId++;
+ }
+
+ // Create audio device
+ //
+ MappedDevice *device =
+ new MappedDevice(audioDeviceId,
+ Device::Audio,
+ "Audio",
+ "Audio connection");
+ m_devices.push_back(device);
+ }
+ }
+#endif
+
+}
+
+MappedDevice *
+AlsaDriver::createMidiDevice(AlsaPortDescription *port,
+ MidiDevice::DeviceDirection reqDirection)
+{
+ char deviceName[100];
+ std::string connectionName("");
+ Audit audit;
+
+ static int unknownCounter;
+
+ static int counters[3][2]; // [system/hardware/software][out/in]
+ const int UNKNOWN = -1, SYSTEM = 0, HARDWARE = 1, SOFTWARE = 2;
+ static const char *firstNames[4][2] = {
+ { "MIDI output system device", "MIDI input system device"
+ },
+ { "MIDI external device", "MIDI hardware input device" },
+ { "MIDI software device", "MIDI software input" }
+ };
+ static const char *countedNames[4][2] = {
+ { "MIDI output system device %d", "MIDI input system device %d"
+ },
+ { "MIDI external device %d", "MIDI hardware input device %d" },
+ { "MIDI software device %d", "MIDI software input %d" }
+ };
+
+ static int specificCounters[2];
+ static const char *specificNames[2] = {
+ "MIDI soundcard synth", "MIDI soft synth",
+ };
+ static const char *specificCountedNames[2] = {
+ "MIDI soundcard synth %d", "MIDI soft synth %d",
+ };
+
+ DeviceId deviceId = getSpareDeviceId();
+
+ if (port) {
+
+ if (reqDirection == MidiDevice::Record && !port->isReadable())
+ return 0;
+ if (reqDirection == MidiDevice::Play && !port->isWriteable())
+ return 0;
+
+ int category = UNKNOWN;
+ bool noConnect = false;
+ bool isSynth = false;
+ bool synthKnown = false;
+
+ if (port->m_client < 16) {
+
+ category = SYSTEM;
+ noConnect = true;
+ isSynth = false;
+ synthKnown = true;
+
+ } else {
+
+#ifdef SND_SEQ_PORT_TYPE_HARDWARE
+ if (port->m_portType & SND_SEQ_PORT_TYPE_HARDWARE) {
+ category = HARDWARE;
+ }
+#endif
+#ifdef SND_SEQ_PORT_TYPE_SOFTWARE
+ if (port->m_portType & SND_SEQ_PORT_TYPE_SOFTWARE) {
+ category = SOFTWARE;
+ }
+#endif
+#ifdef SND_SEQ_PORT_TYPE_SYNTHESIZER
+ if (port->m_portType & SND_SEQ_PORT_TYPE_SYNTHESIZER) {
+ isSynth = true;
+ synthKnown = true;
+ }
+#endif
+#ifdef SND_SEQ_PORT_TYPE_APPLICATION
+ if (port->m_portType & SND_SEQ_PORT_TYPE_APPLICATION) {
+ category = SOFTWARE;
+ isSynth = false;
+ synthKnown = true;
+ }
+#endif
+
+ if (category == UNKNOWN) {
+
+ if (port->m_client < 64) {
+
+ if (versionIsAtLeast(getAlsaModuleVersionString(),
+ 1, 0, 11)) {
+
+ category = HARDWARE;
+
+ } else {
+
+ category = SYSTEM;
+ noConnect = true;
+ }
+
+ } else if (port->m_client < 128) {
+
+ category = HARDWARE;
+
+ } else {
+
+ category = SOFTWARE;
+ }
+ }
+ }
+
+ bool haveName = false;
+
+ if (!synthKnown) {
+
+ if (category != SYSTEM && reqDirection == MidiDevice::Play) {
+
+ // We assume GM/GS/XG/MT32 devices are synths.
+
+ bool isSynth = (port->m_portType &
+ (SND_SEQ_PORT_TYPE_MIDI_GM |
+ SND_SEQ_PORT_TYPE_MIDI_GS |
+ SND_SEQ_PORT_TYPE_MIDI_XG |
+ SND_SEQ_PORT_TYPE_MIDI_MT32));
+
+ if (!isSynth &&
+ (port->m_name.find("ynth") < port->m_name.length()))
+ isSynth = true;
+ if (!isSynth &&
+ (port->m_name.find("nstrument") < port->m_name.length()))
+ isSynth = true;
+ if (!isSynth &&
+ (port->m_name.find("VSTi") < port->m_name.length()))
+ isSynth = true;
+
+ } else {
+ isSynth = false;
+ }
+ }
+
+ if (isSynth) {
+ int clientType = (category == SOFTWARE) ? 1 : 0;
+ if (specificCounters[clientType] == 0) {
+ sprintf(deviceName, specificNames[clientType]);
+ ++specificCounters[clientType];
+ } else {
+ sprintf(deviceName,
+ specificCountedNames[clientType],
+ ++specificCounters[clientType]);
+ }
+ haveName = true;
+ }
+
+ if (!haveName) {
+ if (counters[category][reqDirection] == 0) {
+ sprintf(deviceName, firstNames[category][reqDirection]);
+ ++counters[category][reqDirection];
+ } else {
+ sprintf(deviceName,
+ countedNames[category][reqDirection],
+ ++counters[category][reqDirection]);
+ }
+ }
+
+ if (!noConnect) {
+ m_devicePortMap[deviceId] = ClientPortPair(port->m_client,
+ port->m_port);
+ connectionName = port->m_name;
+ }
+
+ audit << "Creating device " << deviceId << " in "
+ << (reqDirection == MidiDevice::Play ? "Play" : "Record")
+ << " mode for connection " << port->m_name
+ << (noConnect ? " (not connecting)" : "")
+ << "\nDefault device name for this device is "
+ << deviceName << std::endl;
+
+ } else { // !port
+
+ sprintf(deviceName, "Anonymous MIDI device %d", ++unknownCounter);
+
+ audit << "Creating device " << deviceId << " in "
+ << (reqDirection == MidiDevice::Play ? "Play" : "Record")
+ << " mode -- no connection available "
+ << "\nDefault device name for this device is "
+ << deviceName << std::endl;
+ }
+
+ if (reqDirection == MidiDevice::Play) {
+
+ QString portName;
+
+ if (QString(deviceName).startsWith("Anonymous MIDI device ")) {
+ portName = QString("out %1")
+ .arg(m_outputPorts.size() + 1);
+ } else {
+ portName = QString("out %1 - %2")
+ .arg(m_outputPorts.size() + 1)
+ .arg(deviceName);
+ }
+
+ int outputPort = checkAlsaError(snd_seq_create_simple_port
+ (m_midiHandle,
+ portName,
+ SND_SEQ_PORT_CAP_READ |
+ SND_SEQ_PORT_CAP_SUBS_READ,
+ SND_SEQ_PORT_TYPE_APPLICATION),
+ "createMidiDevice - can't create output port");
+
+ if (outputPort >= 0) {
+
+ std::cerr << "CREATED OUTPUT PORT " << outputPort << ":" << portName << " for device " << deviceId << std::endl;
+
+ m_outputPorts[deviceId] = outputPort;
+
+ if (port) {
+ if (connectionName != "") {
+ std::cerr << "Connecting my port " << outputPort << " to " << port->m_client << ":" << port->m_port << " on initialisation" << std::endl;
+ snd_seq_connect_to(m_midiHandle,
+ outputPort,
+ port->m_client,
+ port->m_port);
+ if (m_midiSyncAutoConnect) {
+ snd_seq_connect_to(m_midiHandle,
+ m_syncOutputPort,
+ port->m_client,
+ port->m_port);
+ }
+ }
+ std::cerr << "done" << std::endl;
+ }
+ }
+ }
+
+ MappedDevice *device = new MappedDevice(deviceId,
+ Device::Midi,
+ deviceName,
+ connectionName);
+ device->setDirection(reqDirection);
+ return device;
+}
+
+DeviceId
+AlsaDriver::getSpareDeviceId()
+{
+ std::set
+ <DeviceId> ids;
+ for (unsigned int i = 0; i < m_devices.size(); ++i) {
+ ids.insert(m_devices[i]->getId());
+ }
+
+ DeviceId id = 0;
+ while (ids.find(id) != ids.end())
+ ++id;
+ return id;
+}
+
+void
+AlsaDriver::addInstrumentsForDevice(MappedDevice *device)
+{
+ std::string channelName;
+ char number[100];
+
+ for (int channel = 0; channel < 16; ++channel) {
+ // Create MappedInstrument for export to GUI
+ //
+ // name is just number, derive rest from device at gui
+ sprintf(number, "#%d", channel + 1);
+ channelName = std::string(number);
+
+ if (channel == 9)
+ channelName = std::string("#10[D]");
+ MappedInstrument *instr = new MappedInstrument(Instrument::Midi,
+ channel,
+ m_midiRunningId++,
+ channelName,
+ device->getId());
+ m_instruments.push_back(instr);
+ }
+}
+
+
+bool
+AlsaDriver::canReconnect(Device::DeviceType type)
+{
+ return (type == Device::Midi);
+}
+
+DeviceId
+AlsaDriver::addDevice(Device::DeviceType type,
+ MidiDevice::DeviceDirection direction)
+{
+ if (type == Device::Midi) {
+
+ MappedDevice *device = createMidiDevice(0, direction);
+ if (!device) {
+#ifdef DEBUG_ALSA
+ std::cerr << "WARNING: Device creation failed" << std::endl;
+#else
+
+ ;
+#endif
+
+ } else {
+ addInstrumentsForDevice(device);
+ m_devices.push_back(device);
+
+ MappedEvent *mE =
+ new MappedEvent(0, MappedEvent::SystemUpdateInstruments,
+ 0, 0);
+ insertMappedEventForReturn(mE);
+
+ return device->getId();
+ }
+ }
+
+ return Device::NO_DEVICE;
+}
+
+void
+AlsaDriver::removeDevice(DeviceId id)
+{
+ DeviceIntMap::iterator i1 = m_outputPorts.find(id);
+ if (i1 == m_outputPorts.end()) {
+ std::cerr << "WARNING: AlsaDriver::removeDevice: Cannot find device "
+ << id << " in port map" << std::endl;
+ return ;
+ }
+ checkAlsaError( snd_seq_delete_port(m_midiHandle, i1->second),
+ "removeDevice");
+ m_outputPorts.erase(i1);
+
+ for (MappedDeviceList::iterator i = m_devices.end();
+ i != m_devices.begin(); ) {
+
+ --i;
+
+ if ((*i)->getId() == id) {
+ delete *i;
+ m_devices.erase(i);
+ }
+ }
+
+ for (MappedInstrumentList::iterator i = m_instruments.end();
+ i != m_instruments.begin(); ) {
+
+ --i;
+
+ if ((*i)->getDevice() == id) {
+ delete *i;
+ m_instruments.erase(i);
+ }
+ }
+
+ MappedEvent *mE =
+ new MappedEvent(0, MappedEvent::SystemUpdateInstruments,
+ 0, 0);
+ insertMappedEventForReturn(mE);
+}
+
+void
+AlsaDriver::renameDevice(DeviceId id, QString name)
+{
+ DeviceIntMap::iterator i = m_outputPorts.find(id);
+ if (i == m_outputPorts.end()) {
+ std::cerr << "WARNING: AlsaDriver::renameDevice: Cannot find device "
+ << id << " in port map" << std::endl;
+ return ;
+ }
+
+ snd_seq_port_info_t *pinfo;
+ snd_seq_port_info_alloca(&pinfo);
+ snd_seq_get_port_info(m_midiHandle, i->second, pinfo);
+
+ QString oldName = snd_seq_port_info_get_name(pinfo);
+ int sep = oldName.find(" - ");
+
+ QString newName;
+
+ if (name.startsWith("Anonymous MIDI device ")) {
+ if (sep < 0)
+ sep = 0;
+ newName = oldName.left(sep);
+ } else if (sep < 0) {
+ newName = oldName + " - " + name;
+ } else {
+ newName = oldName.left(sep + 3) + name;
+ }
+
+ snd_seq_port_info_set_name(pinfo, newName.data());
+ checkAlsaError(snd_seq_set_port_info(m_midiHandle, i->second, pinfo),
+ "renameDevice");
+
+ for (unsigned int i = 0; i < m_devices.size(); ++i) {
+ if (m_devices[i]->getId() == id) {
+ m_devices[i]->setName(newName.data());
+ break;
+ }
+ }
+
+ std::cerr << "Renamed " << m_client << ":" << i->second << " to " << name << std::endl;
+}
+
+ClientPortPair
+AlsaDriver::getPortByName(std::string name)
+{
+ for (unsigned int i = 0; i < m_alsaPorts.size(); ++i) {
+ if (m_alsaPorts[i]->m_name == name) {
+ return ClientPortPair(m_alsaPorts[i]->m_client,
+ m_alsaPorts[i]->m_port);
+ }
+ }
+ return ClientPortPair( -1, -1);
+}
+
+std::string
+AlsaDriver::getPortName(ClientPortPair port)
+{
+ for (unsigned int i = 0; i < m_alsaPorts.size(); ++i) {
+ if (m_alsaPorts[i]->m_client == port.first &&
+ m_alsaPorts[i]->m_port == port.second) {
+ return m_alsaPorts[i]->m_name;
+ }
+ }
+ return "";
+}
+
+
+unsigned int
+AlsaDriver::getConnections(Device::DeviceType type,
+ MidiDevice::DeviceDirection direction)
+{
+ if (type != Device::Midi)
+ return 0;
+
+ int count = 0;
+ for (unsigned int j = 0; j < m_alsaPorts.size(); ++j) {
+ if ((direction == MidiDevice::Play && m_alsaPorts[j]->isWriteable()) ||
+ (direction == MidiDevice::Record && m_alsaPorts[j]->isReadable())) {
+ ++count;
+ }
+ }
+
+ return count;
+}
+
+QString
+AlsaDriver::getConnection(Device::DeviceType type,
+ MidiDevice::DeviceDirection direction,
+ unsigned int connectionNo)
+{
+ if (type != Device::Midi)
+ return "";
+
+ AlsaPortList tempList;
+ for (unsigned int j = 0; j < m_alsaPorts.size(); ++j) {
+ if ((direction == MidiDevice::Play && m_alsaPorts[j]->isWriteable()) ||
+ (direction == MidiDevice::Record && m_alsaPorts[j]->isReadable())) {
+ tempList.push_back(m_alsaPorts[j]);
+ }
+ }
+
+ if (connectionNo < tempList.size()) {
+ return tempList[connectionNo]->m_name.c_str();
+ }
+
+ return "";
+}
+
+void
+AlsaDriver::setConnectionToDevice(MappedDevice &device, QString connection)
+{
+ ClientPortPair pair( -1, -1);
+ if (connection && connection != "") {
+ pair = getPortByName(connection.data());
+ }
+ setConnectionToDevice(device, connection, pair);
+}
+
+void
+AlsaDriver::setConnectionToDevice(MappedDevice &device, QString connection,
+ const ClientPortPair &pair)
+{
+ QString prevConnection = device.getConnection().c_str();
+ device.setConnection(connection.data());
+
+ if (device.getDirection() == MidiDevice::Play) {
+
+ DeviceIntMap::iterator j = m_outputPorts.find(device.getId());
+
+ if (j != m_outputPorts.end()) {
+
+ if (prevConnection != "") {
+ ClientPortPair prevPair = getPortByName(prevConnection.data());
+ if (prevPair.first >= 0 && prevPair.second >= 0) {
+
+ std::cerr << "Disconnecting my port " << j->second << " from " << prevPair.first << ":" << prevPair.second << " on reconnection" << std::endl;
+ snd_seq_disconnect_to(m_midiHandle,
+ j->second,
+ prevPair.first,
+ prevPair.second);
+
+ if (m_midiSyncAutoConnect) {
+ bool foundElsewhere = false;
+ for (MappedDeviceList::iterator k = m_devices.begin();
+ k != m_devices.end(); ++k) {
+ if ((*k)->getId() != device.getId()) {
+ if ((*k)->getConnection() == prevConnection.data()) {
+ foundElsewhere = true;
+ break;
+ }
+ }
+ }
+ if (!foundElsewhere) {
+ snd_seq_disconnect_to(m_midiHandle,
+ m_syncOutputPort,
+ pair.first,
+ pair.second);
+ }
+ }
+ }
+ }
+
+ if (pair.first >= 0 && pair.second >= 0) {
+ std::cerr << "Connecting my port " << j->second << " to " << pair.first << ":" << pair.second << " on reconnection" << std::endl;
+ snd_seq_connect_to(m_midiHandle,
+ j->second,
+ pair.first,
+ pair.second);
+ if (m_midiSyncAutoConnect) {
+ snd_seq_connect_to(m_midiHandle,
+ m_syncOutputPort,
+ pair.first,
+ pair.second);
+ }
+ }
+ }
+ }
+}
+
+void
+AlsaDriver::setConnection(DeviceId id, QString connection)
+{
+ Audit audit;
+ ClientPortPair port(getPortByName(connection.data()));
+
+ if (port.first != -1 && port.second != -1) {
+
+ m_devicePortMap[id] = port;
+
+ for (unsigned int i = 0; i < m_devices.size(); ++i) {
+
+ if (m_devices[i]->getId() == id) {
+ setConnectionToDevice(*m_devices[i], connection, port);
+
+ MappedEvent *mE =
+ new MappedEvent(0, MappedEvent::SystemUpdateInstruments,
+ 0, 0);
+ insertMappedEventForReturn(mE);
+
+ break;
+ }
+ }
+ }
+}
+
+void
+AlsaDriver::setPlausibleConnection(DeviceId id, QString idealConnection)
+{
+ Audit audit;
+ ClientPortPair port(getPortByName(idealConnection.data()));
+
+ audit << "AlsaDriver::setPlausibleConnection: connection like "
+ << idealConnection << " requested for device " << id << std::endl;
+
+ if (port.first != -1 && port.second != -1) {
+
+ m_devicePortMap[id] = port;
+
+ for (unsigned int i = 0; i < m_devices.size(); ++i) {
+
+ if (m_devices[i]->getId() == id) {
+ setConnectionToDevice(*m_devices[i], idealConnection, port);
+ break;
+ }
+ }
+
+ audit << "AlsaDriver::setPlausibleConnection: exact match available"
+ << std::endl;
+ return ;
+ }
+
+ // What we want is a connection that:
+ //
+ // * is in the right "class" (the 0-63/64-127/128+ range of client id)
+ // * has at least some text in common
+ // * is not yet in use for any device.
+ //
+ // To do this, we exploit our privileged position as part of AlsaDriver
+ // and use our knowledge of how connection strings are made (see
+ // AlsaDriver::generatePortList above) to pick out the relevant parts
+ // of the requested string.
+
+ int client = -1;
+ int colon = idealConnection.find(":");
+ if (colon >= 0)
+ client = idealConnection.left(colon).toInt();
+
+ int portNo = -1;
+ if (client > 0) {
+ QString remainder = idealConnection.mid(colon + 1);
+ int space = remainder.find(" ");
+ if (space >= 0)
+ portNo = remainder.left(space).toInt();
+ }
+
+ int firstSpace = idealConnection.find(" ");
+ int endOfText = idealConnection.find(QRegExp("[^\\w ]"), firstSpace);
+
+ QString text;
+ if (endOfText < 2) {
+ text = idealConnection.mid(firstSpace + 1);
+ } else {
+ text = idealConnection.mid(firstSpace + 1, endOfText - firstSpace - 2);
+ }
+
+ for (int testUsed = 1; testUsed >= 0; --testUsed) {
+
+ for (int testNumbers = 1; testNumbers >= 0; --testNumbers) {
+
+ for (int testName = 1; testName >= 0; --testName) {
+
+ int fitness =
+ (testName << 3) +
+ (testNumbers << 2) +
+ (testUsed << 1) + 1;
+
+ for (unsigned int i = 0; i < m_alsaPorts.size(); ++i) {
+
+ AlsaPortDescription *port = m_alsaPorts[i];
+
+ if (client > 0) {
+
+ if (port->m_client / 64 != client / 64)
+ continue;
+
+ if (testNumbers) {
+ // We always check the client class (above).
+ // But we also prefer to have something in
+ // common with client or port number, at least
+ // for ports that aren't used elsewhere
+ // already. We don't check both because the
+ // chances are the entire string would already
+ // have matched if both figures did; instead
+ // we check the port if it's > 0 (handy for
+ // e.g. matching the MIDI synth port on a
+ // multi-port soundcard) and the client
+ // otherwise.
+ if (portNo > 0) {
+ if (port->m_port != portNo)
+ continue;
+ } else {
+ if (port->m_client != client)
+ continue;
+ }
+ }
+ }
+
+ if (testName && text != "" &&
+ !QString(port->m_name.c_str()).contains(text))
+ continue;
+
+ if (testUsed) {
+ bool used = false;
+ for (DevicePortMap::iterator dpmi = m_devicePortMap.begin();
+ dpmi != m_devicePortMap.end(); ++dpmi) {
+ if (dpmi->second.first == port->m_client &&
+ dpmi->second.second == port->m_port) {
+ used = true;
+ break;
+ }
+ }
+ if (used)
+ continue;
+ }
+
+ // OK, this one will do
+
+ audit << "AlsaDriver::setPlausibleConnection: fuzzy match "
+ << port->m_name << " available with fitness "
+ << fitness << std::endl;
+
+ m_devicePortMap[id] = ClientPortPair(port->m_client, port->m_port);
+
+ for (unsigned int i = 0; i < m_devices.size(); ++i) {
+
+ if (m_devices[i]->getId() == id) {
+ setConnectionToDevice(*m_devices[i],
+ port->m_name.c_str(),
+ m_devicePortMap[id]);
+
+ // in this case we don't request a device resync,
+ // because this is only invoked at times such as
+ // file load when the GUI is well aware that the
+ // whole situation is in upheaval anyway
+
+ return ;
+ }
+ }
+ }
+ }
+ }
+ }
+
+ audit << "AlsaDriver::setPlausibleConnection: nothing suitable available"
+ << std::endl;
+}
+
+
+void
+AlsaDriver::checkTimerSync(size_t frames)
+{
+ if (!m_doTimerChecks)
+ return ;
+
+#ifdef HAVE_LIBJACK
+
+ if (!m_jackDriver || !m_queueRunning || frames == 0 ||
+ (getMTCStatus() == TRANSPORT_SLAVE)) {
+ m_firstTimerCheck = true;
+ return ;
+ }
+
+ static RealTime startAlsaTime;
+ static size_t startJackFrames = 0;
+ static size_t lastJackFrames = 0;
+
+ size_t nowJackFrames = m_jackDriver->getFramesProcessed();
+ RealTime nowAlsaTime = getAlsaTime();
+
+ if (m_firstTimerCheck ||
+ (nowJackFrames <= lastJackFrames) ||
+ (nowAlsaTime <= startAlsaTime)) {
+
+ startAlsaTime = nowAlsaTime;
+ startJackFrames = nowJackFrames;
+ lastJackFrames = nowJackFrames;
+
+ m_firstTimerCheck = false;
+ return ;
+ }
+
+ RealTime jackDiff = RealTime::frame2RealTime
+ (nowJackFrames - startJackFrames,
+ m_jackDriver->getSampleRate());
+
+ RealTime alsaDiff = nowAlsaTime - startAlsaTime;
+
+ if (alsaDiff > RealTime(10, 0)) {
+
+#ifdef DEBUG_ALSA
+ if (!m_playing) {
+ std::cout << "\nALSA:" << startAlsaTime << "\t->" << nowAlsaTime << "\nJACK: " << startJackFrames << "\t\t-> " << nowJackFrames << std::endl;
+ std::cout << "ALSA diff: " << alsaDiff << "\nJACK diff: " << jackDiff << std::endl;
+ }
+#endif
+
+ double ratio = (jackDiff - alsaDiff) / alsaDiff;
+
+ if (fabs(ratio) > 0.1) {
+#ifdef DEBUG_ALSA
+ if (!m_playing) {
+ std::cout << "Ignoring excessive ratio " << ratio
+ << ", hoping for a more likely result next time"
+ << std::endl;
+ }
+#endif
+
+ } else if (fabs(ratio) > 0.000001) {
+
+#ifdef DEBUG_ALSA
+ if (alsaDiff > RealTime::zeroTime && jackDiff > RealTime::zeroTime) {
+ if (!m_playing) {
+ if (jackDiff < alsaDiff) {
+ std::cout << "<<<< ALSA timer is faster by " << 100.0 * ((alsaDiff - jackDiff) / alsaDiff) << "% (1/" << int(1.0 / ratio) << ")" << std::endl;
+ } else {
+ std::cout << ">>>> JACK timer is faster by " << 100.0 * ((jackDiff - alsaDiff) / alsaDiff) << "% (1/" << int(1.0 / ratio) << ")" << std::endl;
+ }
+ }
+ }
+#endif
+
+ m_timerRatio = ratio;
+ m_timerRatioCalculated = true;
+ }
+
+ m_firstTimerCheck = true;
+ }
+#endif
+}
+
+
+unsigned int
+AlsaDriver::getTimers()
+{
+ return m_timers.size() + 1; // one extra for auto
+}
+
+QString
+AlsaDriver::getTimer(unsigned int n)
+{
+ if (n == 0)
+ return AUTO_TIMER_NAME;
+ else
+ return m_timers[n -1].name.c_str();
+}
+
+QString
+AlsaDriver::getCurrentTimer()
+{
+ return m_currentTimer.c_str();
+}
+
+void
+AlsaDriver::setCurrentTimer(QString timer)
+{
+ Audit audit;
+
+ if (timer == getCurrentTimer())
+ return ;
+
+ std::cerr << "AlsaDriver::setCurrentTimer(" << timer << ")" << std::endl;
+
+ std::string name(timer.data());
+
+ if (name == AUTO_TIMER_NAME) {
+ name = getAutoTimer(m_doTimerChecks);
+ } else {
+ m_doTimerChecks = false;
+ }
+ m_timerRatioCalculated = false;
+
+ // Stop and restart the queue around the timer change. We don't
+ // call stopClocks/startClocks here because they do the wrong
+ // thing if we're currently playing and on the JACK transport.
+
+ m_queueRunning = false;
+ checkAlsaError(snd_seq_stop_queue(m_midiHandle, m_queue, NULL), "setCurrentTimer(): stopping queue");
+ checkAlsaError(snd_seq_drain_output(m_midiHandle), "setCurrentTimer(): draining output to stop queue");
+
+ snd_seq_event_t event;
+ snd_seq_ev_clear(&event);
+ snd_seq_real_time_t z = { 0, 0 };
+ snd_seq_ev_set_queue_pos_real(&event, m_queue, &z);
+ snd_seq_ev_set_direct(&event);
+ checkAlsaError(snd_seq_control_queue(m_midiHandle, m_queue, SND_SEQ_EVENT_SETPOS_TIME,
+ 0, &event), "setCurrentTimer(): control queue");
+ checkAlsaError(snd_seq_drain_output(m_midiHandle), "setCurrentTimer(): draining output to control queue");
+ m_alsaPlayStartTime = RealTime::zeroTime;
+
+ for (unsigned int i = 0; i < m_timers.size(); ++i) {
+ if (m_timers[i].name == name) {
+
+ snd_seq_queue_timer_t *timer;
+ snd_timer_id_t *timerid;
+
+ snd_seq_queue_timer_alloca(&timer);
+ snd_seq_get_queue_timer(m_midiHandle, m_queue, timer);
+
+ snd_timer_id_alloca(&timerid);
+ snd_timer_id_set_class(timerid, m_timers[i].clas);
+ snd_timer_id_set_sclass(timerid, m_timers[i].sclas);
+ snd_timer_id_set_card(timerid, m_timers[i].card);
+ snd_timer_id_set_device(timerid, m_timers[i].device);
+ snd_timer_id_set_subdevice(timerid, m_timers[i].subdevice);
+
+ snd_seq_queue_timer_set_id(timer, timerid);
+ snd_seq_set_queue_timer(m_midiHandle, m_queue, timer);
+
+ if (m_doTimerChecks) {
+ audit << " Current timer set to \"" << name << "\" with timer checks"
+ << std::endl;
+ } else {
+ audit << " Current timer set to \"" << name << "\""
+ << std::endl;
+ }
+
+ if (m_timers[i].clas == SND_TIMER_CLASS_GLOBAL &&
+ m_timers[i].device == SND_TIMER_GLOBAL_SYSTEM) {
+ long hz = 1000000000 / m_timers[i].resolution;
+ if (hz < 900) {
+ audit << " WARNING: using system timer with only "
+ << hz << "Hz resolution!" << std::endl;
+ }
+ }
+
+ break;
+ }
+ }
+
+#ifdef HAVE_LIBJACK
+ if (m_jackDriver)
+ m_jackDriver->prebufferAudio();
+#endif
+
+ checkAlsaError(snd_seq_continue_queue(m_midiHandle, m_queue, NULL), "checkAlsaError(): continue queue");
+ checkAlsaError(snd_seq_drain_output(m_midiHandle), "setCurrentTimer(): draining output to continue queue");
+ m_queueRunning = true;
+
+ m_firstTimerCheck = true;
+}
+
+bool
+AlsaDriver::initialise()
+{
+ bool result = true;
+
+ initialiseAudio();
+ result = initialiseMidi();
+
+ return result;
+}
+
+
+
+// Set up queue, client and port
+//
+bool
+AlsaDriver::initialiseMidi()
+{
+ Audit audit;
+
+ // Create a non-blocking handle.
+ //
+ if (snd_seq_open(&m_midiHandle,
+ "default",
+ SND_SEQ_OPEN_DUPLEX,
+ SND_SEQ_NONBLOCK) < 0) {
+ audit << "AlsaDriver::initialiseMidi - "
+ << "couldn't open sequencer - " << snd_strerror(errno)
+ << " - perhaps you need to modprobe snd-seq-midi."
+ << std::endl;
+ reportFailure(MappedEvent::FailureALSACallFailed);
+ return false;
+ }
+
+ snd_seq_set_client_name(m_midiHandle, "rosegarden");
+
+ if ((m_client = snd_seq_client_id(m_midiHandle)) < 0) {
+#ifdef DEBUG_ALSA
+ std::cerr << "AlsaDriver::initialiseMidi - can't create client"
+ << std::endl;
+#endif
+
+ return false;
+ }
+
+ // Create a queue
+ //
+ if ((m_queue = snd_seq_alloc_named_queue(m_midiHandle,
+ "Rosegarden queue")) < 0) {
+#ifdef DEBUG_ALSA
+ std::cerr << "AlsaDriver::initialiseMidi - can't allocate queue"
+ << std::endl;
+#endif
+
+ return false;
+ }
+
+ // Create the input port
+ //
+ snd_seq_port_info_t *pinfo;
+
+ snd_seq_port_info_alloca(&pinfo);
+ snd_seq_port_info_set_capability(pinfo,
+ SND_SEQ_PORT_CAP_WRITE |
+ SND_SEQ_PORT_CAP_SUBS_WRITE );
+ snd_seq_port_info_set_type(pinfo, SND_SEQ_PORT_TYPE_APPLICATION);
+ snd_seq_port_info_set_midi_channels(pinfo, 16);
+ /* we want to know when the events got delivered to us */
+ snd_seq_port_info_set_timestamping(pinfo, 1);
+ snd_seq_port_info_set_timestamp_real(pinfo, 1);
+ snd_seq_port_info_set_timestamp_queue(pinfo, m_queue);
+ snd_seq_port_info_set_name(pinfo, "record in");
+
+ if (checkAlsaError(snd_seq_create_port(m_midiHandle, pinfo),
+ "initialiseMidi - can't create input port") < 0)
+ return false;
+ m_inputPort = snd_seq_port_info_get_port(pinfo);
+
+ // Subscribe the input port to the ALSA Announce port
+ // to receive notifications when clients, ports and subscriptions change
+ snd_seq_connect_from( m_midiHandle, m_inputPort,
+ SND_SEQ_CLIENT_SYSTEM, SND_SEQ_PORT_SYSTEM_ANNOUNCE );
+
+ m_midiInputPortConnected = true;
+
+ // Set the input queue size
+ //
+ if (snd_seq_set_client_pool_output(m_midiHandle, 2000) < 0 ||
+ snd_seq_set_client_pool_input(m_midiHandle, 2000) < 0 ||
+ snd_seq_set_client_pool_output_room(m_midiHandle, 2000) < 0) {
+#ifdef DEBUG_ALSA
+ std::cerr << "AlsaDriver::initialiseMidi - "
+ << "can't modify pool parameters"
+ << std::endl;
+#endif
+
+ return false;
+ }
+
+ // Create sync output now as well
+ m_syncOutputPort = checkAlsaError(snd_seq_create_simple_port
+ (m_midiHandle,
+ "sync out",
+ SND_SEQ_PORT_CAP_READ |
+ SND_SEQ_PORT_CAP_SUBS_READ,
+ SND_SEQ_PORT_TYPE_APPLICATION),
+ "initialiseMidi - can't create sync output port");
+
+ // and port for hardware controller
+ m_controllerPort = checkAlsaError(snd_seq_create_simple_port
+ (m_midiHandle,
+ "external controller",
+ SND_SEQ_PORT_CAP_READ |
+ SND_SEQ_PORT_CAP_WRITE |
+ SND_SEQ_PORT_CAP_SUBS_READ |
+ SND_SEQ_PORT_CAP_SUBS_WRITE,
+ SND_SEQ_PORT_TYPE_APPLICATION),
+ "initialiseMidi - can't create controller port");
+
+ getSystemInfo();
+
+ generatePortList();
+ generateInstruments();
+
+ // Modify status with MIDI success
+ //
+ m_driverStatus |= MIDI_OK;
+
+ generateTimerList();
+ setCurrentTimer(AUTO_TIMER_NAME);
+
+ // Start the timer
+ if (checkAlsaError(snd_seq_start_queue(m_midiHandle, m_queue, NULL),
+ "initialiseMidi(): couldn't start queue") < 0) {
+ reportFailure(MappedEvent::FailureALSACallFailed);
+ return false;
+ }
+
+ m_queueRunning = true;
+
+ // process anything pending
+ checkAlsaError(snd_seq_drain_output(m_midiHandle), "initialiseMidi(): couldn't drain output");
+
+ audit << "AlsaDriver::initialiseMidi - initialised MIDI subsystem"
+ << std::endl << std::endl;
+
+ return true;
+}
+
+// We don't even attempt to use ALSA audio. We just use JACK instead.
+// See comment at the top of this file and jackProcess() for further
+// information on how we use this.
+//
+void
+AlsaDriver::initialiseAudio()
+{
+#ifdef HAVE_LIBJACK
+ m_jackDriver = new JackDriver(this);
+
+ if (m_jackDriver->isOK()) {
+ m_driverStatus |= AUDIO_OK;
+ } else {
+ delete m_jackDriver;
+ m_jackDriver = 0;
+ }
+#endif
+}
+
+void
+AlsaDriver::initialisePlayback(const RealTime &position)
+{
+#ifdef DEBUG_ALSA
+ std::cerr << "\n\nAlsaDriver - initialisePlayback" << std::endl;
+#endif
+
+ // now that we restart the queue at each play, the origin is always zero
+ m_alsaPlayStartTime = RealTime::zeroTime;
+ m_playStartPosition = position;
+
+ m_startPlayback = true;
+
+ m_mtcFirstTime = -1;
+ m_mtcSigmaE = 0;
+ m_mtcSigmaC = 0;
+
+ if (getMMCStatus() == TRANSPORT_MASTER) {
+ sendMMC(127, MIDI_MMC_PLAY, true, "");
+ m_eat_mtc = 0;
+ }
+
+ if (getMTCStatus() == TRANSPORT_MASTER) {
+ insertMTCFullFrame(position);
+ }
+
+ // If MIDI Sync is enabled then adjust for the MIDI Clock to
+ // synchronise the sequencer with the clock.
+ //
+ if (getMIDISyncStatus() == TRANSPORT_MASTER) {
+ // Send the Song Position Pointer for MIDI CLOCK positioning
+ //
+ // Get time from current alsa time to start of alsa timing -
+ // add the initial starting point and divide by the MIDI Beat
+ // length. The SPP is is the MIDI Beat upon which to start the song.
+ // Songs are always assumed to start on a MIDI Beat of 0. Each MIDI
+ // Beat spans 6 MIDI Clocks. In other words, each MIDI Beat is a 16th
+ // note (since there are 24 MIDI Clocks in a quarter note).
+ //
+ long spp =
+ long(((getAlsaTime() - m_alsaPlayStartTime + m_playStartPosition) /
+ m_midiClockInterval) / 6.0 );
+
+ // Ok now we have the new SPP - stop the transport and restart with the
+ // new value.
+ //
+ sendSystemDirect(SND_SEQ_EVENT_STOP, NULL);
+
+ signed int args = spp;
+ sendSystemDirect(SND_SEQ_EVENT_SONGPOS, &args);
+
+ // Now send the START/CONTINUE
+ //
+ if (m_playStartPosition == RealTime::zeroTime)
+ sendSystemQueued(SND_SEQ_EVENT_START, "",
+ m_alsaPlayStartTime);
+ else
+ sendSystemQueued(SND_SEQ_EVENT_CONTINUE, "",
+ m_alsaPlayStartTime);
+ }
+
+#ifdef HAVE_LIBJACK
+ if (m_jackDriver) {
+ m_needJackStart = NeedJackStart;
+ }
+#endif
+}
+
+
+void
+AlsaDriver::stopPlayback()
+{
+#ifdef DEBUG_ALSA
+ std::cerr << "\n\nAlsaDriver - stopPlayback" << std::endl;
+#endif
+
+ if (getMIDISyncStatus() == TRANSPORT_MASTER) {
+ sendSystemDirect(SND_SEQ_EVENT_STOP, NULL);
+ }
+
+ if (getMMCStatus() == TRANSPORT_MASTER) {
+ sendMMC(127, MIDI_MMC_STOP, true, "");
+ //<VN> need to throw away the next MTC event
+ m_eat_mtc = 3;
+ }
+
+ allNotesOff();
+ m_playing = false;
+
+#ifdef HAVE_LIBJACK
+ if (m_jackDriver) {
+ m_jackDriver->stopTransport();
+ m_needJackStart = NeedNoJackStart;
+ }
+#endif
+
+ // Flush the output and input queues
+ //
+ snd_seq_remove_events_t *info;
+ snd_seq_remove_events_alloca(&info);
+ snd_seq_remove_events_set_condition(info, SND_SEQ_REMOVE_INPUT |
+ SND_SEQ_REMOVE_OUTPUT);
+ snd_seq_remove_events(m_midiHandle, info);
+
+ // send sounds-off to all play devices
+ //
+ for (MappedDeviceList::iterator i = m_devices.begin(); i != m_devices.end(); ++i) {
+ if ((*i)->getDirection() == MidiDevice::Play) {
+ sendDeviceController((*i)->getId(),
+ MIDI_CONTROLLER_SUSTAIN, 0);
+ sendDeviceController((*i)->getId(),
+ MIDI_CONTROLLER_ALL_NOTES_OFF, 0);
+ }
+ }
+
+ punchOut();
+
+ stopClocks(); // Resets ALSA timer to zero
+
+ clearAudioQueue();
+
+ startClocksApproved(); // restarts ALSA timer without starting JACK transport
+}
+
+void
+AlsaDriver::punchOut()
+{
+#ifdef DEBUG_ALSA
+ std::cerr << "AlsaDriver::punchOut" << std::endl;
+#endif
+
+#ifdef HAVE_LIBJACK
+ // Close any recording file
+ if (m_recordStatus == RECORD_ON) {
+ for (InstrumentSet::const_iterator i = m_recordingInstruments.begin();
+ i != m_recordingInstruments.end(); ++i) {
+
+ InstrumentId id = *i;
+
+ if (id >= AudioInstrumentBase &&
+ id < MidiInstrumentBase) {
+
+ AudioFileId auid = 0;
+ if (m_jackDriver && m_jackDriver->closeRecordFile(id, auid)) {
+
+#ifdef DEBUG_ALSA
+ std::cerr << "AlsaDriver::stopPlayback: sending back to GUI for instrument " << id << std::endl;
+#endif
+
+ // Create event to return to gui to say that we've
+ // completed an audio file and we can generate a
+ // preview for it now.
+ //
+ // nasty hack -- don't have right audio id here, and
+ // the sequencer will wipe out the instrument id and
+ // replace it with currently-selected one in gui --
+ // so use audio id slot to pass back instrument id
+ // and handle accordingly in gui
+ try {
+ MappedEvent *mE =
+ new MappedEvent(id,
+ MappedEvent::AudioGeneratePreview,
+ id % 256,
+ id / 256);
+
+ // send completion event
+ insertMappedEventForReturn(mE);
+ } catch (...) {
+ ;
+ }
+ }
+ }
+ }
+ }
+#endif
+
+ // Change recorded state if any set
+ //
+ if (m_recordStatus == RECORD_ON)
+ m_recordStatus = RECORD_OFF;
+
+ m_recordingInstruments.clear();
+}
+
+void
+AlsaDriver::resetPlayback(const RealTime &oldPosition, const RealTime &position)
+{
+#ifdef DEBUG_ALSA
+ std::cerr << "\n\nAlsaDriver - resetPlayback(" << oldPosition << "," << position << ")" << std::endl;
+#endif
+
+ if (getMMCStatus() == TRANSPORT_MASTER) {
+ unsigned char t_sec = (unsigned char) position.sec % 60;
+ unsigned char t_min = (unsigned char) (position.sec / 60) % 60;
+ unsigned char t_hrs = (unsigned char) (position.sec / 3600);
+#define STUPID_BROKEN_EQUIPMENT
+#ifdef STUPID_BROKEN_EQUIPMENT
+ // Some recorders assume you are talking in 30fps...
+ unsigned char t_frm = (unsigned char) (position.nsec / 33333333U);
+ unsigned char t_sbf = (unsigned char) ((position.nsec / 333333U) % 100U);
+#else
+ // We always send at 25fps, it's the easiest to avoid rounding problems
+ unsigned char t_frm = (unsigned char) (position.nsec / 40000000U);
+ unsigned char t_sbf = (unsigned char) ((position.nsec / 400000U) % 100U);
+#endif
+
+ std::cerr << "\n Jump using MMC LOCATE to" << position << std::endl;
+ std::cerr << "\t which is " << int(t_hrs) << ":" << int(t_min) << ":" << int(t_sec) << "." << int(t_frm) << "." << int(t_sbf) << std::endl;
+ unsigned char locateDataArr[7] = {
+ 0x06,
+ 0x01,
+ 0x60 + t_hrs, // (30fps flag) + hh
+ t_min, // mm
+ t_sec, // ss
+ t_frm, // frames
+ t_sbf // subframes
+ };
+
+ sendMMC(127, MIDI_MMC_LOCATE, true, std::string((const char *) locateDataArr, 7));
+ }
+
+ RealTime formerStartPosition = m_playStartPosition;
+
+ m_playStartPosition = position;
+ m_alsaPlayStartTime = getAlsaTime();
+
+ // Reset note offs to correct positions
+ //
+ RealTime jump = position - oldPosition;
+
+#ifdef DEBUG_PROCESS_MIDI_OUT
+ std::cerr << "Currently " << m_noteOffQueue.size() << " in note off queue" << std::endl;
+#endif
+
+ // modify the note offs that exist as they're relative to the
+ // playStartPosition terms.
+ //
+ for (NoteOffQueue::iterator i = m_noteOffQueue.begin();
+ i != m_noteOffQueue.end(); ++i) {
+
+ // if we're fast forwarding then we bring the note off closer
+ if (jump >= RealTime::zeroTime) {
+
+ RealTime endTime = formerStartPosition + (*i)->getRealTime();
+
+#ifdef DEBUG_PROCESS_MIDI_OUT
+ std::cerr << "Forward jump of " << jump << ": adjusting note off from "
+ << (*i)->getRealTime() << " (absolute " << endTime
+ << ") to ";
+#endif
+ (*i)->setRealTime(endTime - position);
+#ifdef DEBUG_PROCESS_MIDI_OUT
+ std::cerr << (*i)->getRealTime() << std::endl;
+#endif
+ } else // we're rewinding - kill the note immediately
+ {
+#ifdef DEBUG_PROCESS_MIDI_OUT
+ std::cerr << "Rewind by " << jump << ": setting note off to zero" << std::endl;
+#endif
+ (*i)->setRealTime(RealTime::zeroTime);
+ }
+ }
+
+ pushRecentNoteOffs();
+ processNotesOff(getAlsaTime(), true);
+ checkAlsaError(snd_seq_drain_output(m_midiHandle), "resetPlayback(): draining");
+
+ // Ensure we clear down output queue on reset - in the case of
+ // MIDI clock where we might have a long queue of events already
+ // posted.
+ //
+ snd_seq_remove_events_t *info;
+ snd_seq_remove_events_alloca(&info);
+ snd_seq_remove_events_set_condition(info, SND_SEQ_REMOVE_OUTPUT);
+ snd_seq_remove_events(m_midiHandle, info);
+
+ if (getMTCStatus() == TRANSPORT_MASTER) {
+ m_mtcFirstTime = -1;
+ m_mtcSigmaE = 0;
+ m_mtcSigmaC = 0;
+ insertMTCFullFrame(position);
+ }
+
+#ifdef HAVE_LIBJACK
+ if (m_jackDriver) {
+ m_jackDriver->clearSynthPluginEvents();
+ m_needJackStart = NeedJackReposition;
+ }
+#endif
+}
+
+void
+AlsaDriver::setMIDIClockInterval(RealTime interval)
+{
+#ifdef DEBUG_ALSA
+ std::cerr << "AlsaDriver::setMIDIClockInterval(" << interval << ")" << endl;
+#endif
+
+ // Reset the value
+ //
+ SoundDriver::setMIDIClockInterval(interval);
+
+ // Return if the clock isn't enabled
+ //
+ if (!m_midiClockEnabled)
+ return ;
+
+ if (false) // don't remove any events quite yet
+ {
+
+ // Remove all queued events (although we should filter this
+ // down to just the clock events.
+ //
+ snd_seq_remove_events_t *info;
+ snd_seq_remove_events_alloca(&info);
+
+ //if (snd_seq_type_check(SND_SEQ_EVENT_CLOCK, SND_SEQ_EVFLG_CONTROL))
+ //snd_seq_remove_events_set_event_type(info,
+ snd_seq_remove_events_set_condition(info, SND_SEQ_REMOVE_OUTPUT);
+ snd_seq_remove_events_set_event_type(info, SND_SEQ_EVFLG_CONTROL);
+ std::cout << "AlsaDriver::setMIDIClockInterval - "
+ << "MIDI CLOCK TYPE IS CONTROL" << std::endl;
+ snd_seq_remove_events(m_midiHandle, info);
+ }
+
+}
+
+
+void
+AlsaDriver::pushRecentNoteOffs()
+{
+#ifdef DEBUG_PROCESS_MIDI_OUT
+ std::cerr << "AlsaDriver::pushRecentNoteOffs: have " << m_recentNoteOffs.size() << " in queue" << std::endl;
+#endif
+
+ for (NoteOffQueue::iterator i = m_recentNoteOffs.begin();
+ i != m_recentNoteOffs.end(); ++i) {
+ (*i)->setRealTime(RealTime::zeroTime);
+ m_noteOffQueue.insert(*i);
+ }
+
+ m_recentNoteOffs.clear();
+}
+
+void
+AlsaDriver::cropRecentNoteOffs(const RealTime &t)
+{
+ while (!m_recentNoteOffs.empty()) {
+ NoteOffEvent *ev = *m_recentNoteOffs.begin();
+#ifdef DEBUG_PROCESS_MIDI_OUT
+ std::cerr << "AlsaDriver::cropRecentNoteOffs: " << ev->getRealTime() << " vs " << t << std::endl;
+#endif
+ if (ev->getRealTime() >= t) break;
+ delete ev;
+ m_recentNoteOffs.erase(m_recentNoteOffs.begin());
+ }
+}
+
+void
+AlsaDriver::weedRecentNoteOffs(unsigned int pitch, MidiByte channel,
+ InstrumentId instrument)
+{
+ for (NoteOffQueue::iterator i = m_recentNoteOffs.begin();
+ i != m_recentNoteOffs.end(); ++i) {
+ if ((*i)->getPitch() == pitch &&
+ (*i)->getChannel() == channel &&
+ (*i)->getInstrument() == instrument) {
+#ifdef DEBUG_PROCESS_MIDI_OUT
+ std::cerr << "AlsaDriver::weedRecentNoteOffs: deleting one" << std::endl;
+#endif
+ delete *i;
+ m_recentNoteOffs.erase(i);
+ break;
+ }
+ }
+}
+
+void
+AlsaDriver::allNotesOff()
+{
+ snd_seq_event_t event;
+ ClientPortPair outputDevice;
+ RealTime offTime;
+
+ // drop any pending notes
+ snd_seq_drop_output_buffer(m_midiHandle);
+ snd_seq_drop_output(m_midiHandle);
+
+ // prepare the event
+ snd_seq_ev_clear(&event);
+ offTime = getAlsaTime();
+
+ for (NoteOffQueue::iterator it = m_noteOffQueue.begin();
+ it != m_noteOffQueue.end(); ++it) {
+ // Set destination according to connection for instrument
+ //
+ outputDevice = getPairForMappedInstrument((*it)->getInstrument());
+ if (outputDevice.first < 0 || outputDevice.second < 0)
+ continue;
+
+ snd_seq_ev_set_subs(&event);
+
+ // Set source according to port for device
+ //
+ int src = getOutputPortForMappedInstrument((*it)->getInstrument());
+ if (src < 0)
+ continue;
+ snd_seq_ev_set_source(&event, src);
+
+ snd_seq_ev_set_noteoff(&event,
+ (*it)->getChannel(),
+ (*it)->getPitch(),
+ 127);
+
+ //snd_seq_event_output(m_midiHandle, &event);
+ int error = snd_seq_event_output_direct(m_midiHandle, &event);
+
+ if (error < 0) {
+#ifdef DEBUG_ALSA
+ std::cerr << "AlsaDriver::allNotesOff - "
+ << "can't send event" << std::endl;
+#endif
+
+ }
+
+ delete(*it);
+ }
+
+ m_noteOffQueue.erase(m_noteOffQueue.begin(), m_noteOffQueue.end());
+
+ /*
+ std::cerr << "AlsaDriver::allNotesOff - "
+ << " queue size = " << m_noteOffQueue.size() << std::endl;
+ */
+
+ // flush
+ checkAlsaError(snd_seq_drain_output(m_midiHandle), "allNotesOff(): draining");
+}
+
+void
+AlsaDriver::processNotesOff(const RealTime &time, bool now, bool everything)
+{
+ if (m_noteOffQueue.empty()) {
+ return;
+ }
+
+ snd_seq_event_t event;
+
+ ClientPortPair outputDevice;
+ RealTime offTime;
+
+ // prepare the event
+ snd_seq_ev_clear(&event);
+
+ RealTime alsaTime = getAlsaTime();
+
+#ifdef DEBUG_PROCESS_MIDI_OUT
+ std::cerr << "AlsaDriver::processNotesOff(" << time << "): alsaTime = " << alsaTime << ", now = " << now << std::endl;
+#endif
+
+ while (m_noteOffQueue.begin() != m_noteOffQueue.end()) {
+
+ NoteOffEvent *ev = *m_noteOffQueue.begin();
+
+ if (ev->getRealTime() > time) {
+#ifdef DEBUG_PROCESS_MIDI_OUT
+ std::cerr << "Note off time " << ev->getRealTime() << " is beyond current time " << time << std::endl;
+#endif
+ if (!everything) break;
+ }
+
+#ifdef DEBUG_PROCESS_MIDI_OUT
+ std::cerr << "AlsaDriver::processNotesOff(" << time << "): found event at " << ev->getRealTime() << ", instr " << ev->getInstrument() << ", channel " << int(ev->getChannel()) << ", pitch " << int(ev->getPitch()) << std::endl;
+#endif
+
+ bool isSoftSynth = (ev->getInstrument() >= SoftSynthInstrumentBase);
+
+ offTime = ev->getRealTime();
+ if (offTime < RealTime::zeroTime) offTime = RealTime::zeroTime;
+ bool scheduled = (offTime > alsaTime) && !now;
+ if (!scheduled) offTime = RealTime::zeroTime;
+
+ snd_seq_real_time_t alsaOffTime = { offTime.sec,
+ offTime.nsec };
+
+ snd_seq_ev_set_noteoff(&event,
+ ev->getChannel(),
+ ev->getPitch(),
+ 127);
+
+ if (!isSoftSynth) {
+
+ snd_seq_ev_set_subs(&event);
+
+ // Set source according to instrument
+ //
+ int src = getOutputPortForMappedInstrument(ev->getInstrument());
+ if (src < 0) {
+ std::cerr << "note off has no output port (instr = " << ev->getInstrument() << ")" << std::endl;
+ delete ev;
+ m_noteOffQueue.erase(m_noteOffQueue.begin());
+ continue;
+ }
+
+ snd_seq_ev_set_source(&event, src);
+
+ snd_seq_ev_set_subs(&event);
+
+ snd_seq_ev_schedule_real(&event, m_queue, 0, &alsaOffTime);
+
+ if (scheduled) {
+ snd_seq_event_output(m_midiHandle, &event);
+ } else {
+ snd_seq_event_output_direct(m_midiHandle, &event);
+ }
+
+ } else {
+
+ event.time.time = alsaOffTime;
+
+ processSoftSynthEventOut(ev->getInstrument(), &event, now);
+ }
+
+ if (!now) {
+ m_recentNoteOffs.insert(ev);
+ } else {
+ delete ev;
+ }
+ m_noteOffQueue.erase(m_noteOffQueue.begin());
+ }
+
+ // We don't flush the queue here, as this is called nested from
+ // processMidiOut, which does the flushing
+
+#ifdef DEBUG_PROCESS_MIDI_OUT
+ std::cerr << "AlsaDriver::processNotesOff - "
+ << " queue size now: " << m_noteOffQueue.size() << std::endl;
+#endif
+}
+
+// Get the queue time and convert it to RealTime for the gui
+// to use.
+//
+RealTime
+AlsaDriver::getSequencerTime()
+{
+ RealTime t(0, 0);
+
+ t = getAlsaTime() + m_playStartPosition - m_alsaPlayStartTime;
+
+ // std::cerr << "AlsaDriver::getSequencerTime: alsa time is "
+ // << getAlsaTime() << ", start time is " << m_alsaPlayStartTime << ", play start position is " << m_playStartPosition << endl;
+
+ return t;
+}
+
+// Gets the time of the ALSA queue
+//
+RealTime
+AlsaDriver::getAlsaTime()
+{
+ RealTime sequencerTime(0, 0);
+
+ snd_seq_queue_status_t *status;
+ snd_seq_queue_status_alloca(&status);
+
+ if (snd_seq_get_queue_status(m_midiHandle, m_queue, status) < 0) {
+#ifdef DEBUG_ALSA
+ std::cerr << "AlsaDriver::getAlsaTime - can't get queue status"
+ << std::endl;
+#endif
+
+ return sequencerTime;
+ }
+
+ sequencerTime.sec = snd_seq_queue_status_get_real_time(status)->tv_sec;
+ sequencerTime.nsec = snd_seq_queue_status_get_real_time(status)->tv_nsec;
+
+ // std::cerr << "AlsaDriver::getAlsaTime: alsa time is " << sequencerTime << std::endl;
+
+ return sequencerTime;
+}
+
+
+// Get all pending input events and turn them into a MappedComposition.
+//
+//
+MappedComposition*
+AlsaDriver::getMappedComposition()
+{
+ m_recordComposition.clear();
+
+ while (_failureReportReadIndex != _failureReportWriteIndex) {
+ MappedEvent::FailureCode code = _failureReports[_failureReportReadIndex];
+ // std::cerr << "AlsaDriver::reportFailure(" << code << ")" << std::endl;
+ MappedEvent *mE = new MappedEvent
+ (0, MappedEvent::SystemFailure, code, 0);
+ m_returnComposition.insert(mE);
+ _failureReportReadIndex =
+ (_failureReportReadIndex + 1) % FAILURE_REPORT_COUNT;
+ }
+
+ if (!m_returnComposition.empty()) {
+ for (MappedComposition::iterator i = m_returnComposition.begin();
+ i != m_returnComposition.end(); ++i) {
+ m_recordComposition.insert(new MappedEvent(**i));
+ }
+ m_returnComposition.clear();
+ }
+
+ // If the input port hasn't connected we shouldn't poll it
+ //
+ if (m_midiInputPortConnected == false) {
+ return &m_recordComposition;
+ }
+
+ RealTime eventTime(0, 0);
+
+ snd_seq_event_t *event;
+
+ while (snd_seq_event_input(m_midiHandle, &event) > 0) {
+
+ unsigned int channel = (unsigned int)event->data.note.channel;
+ unsigned int chanNoteKey = ( channel << 8 ) +
+ (unsigned int) event->data.note.note;
+
+ bool fromController = false;
+
+ if (event->dest.client == m_client &&
+ event->dest.port == m_controllerPort) {
+#ifdef DEBUG_ALSA
+ std::cerr << "Received an external controller event" << std::endl;
+#endif
+
+ fromController = true;
+ }
+
+ unsigned int deviceId = Device::NO_DEVICE;
+
+ if (fromController) {
+ deviceId = Device::CONTROL_DEVICE;
+ } else {
+ for (MappedDeviceList::iterator i = m_devices.begin();
+ i != m_devices.end(); ++i) {
+ ClientPortPair pair(m_devicePortMap[(*i)->getId()]);
+ if (((*i)->getDirection() == MidiDevice::Record) &&
+ ( pair.first == event->source.client ) &&
+ ( pair.second == event->source.port )) {
+ deviceId = (*i)->getId();
+ break;
+ }
+ }
+ }
+
+ eventTime.sec = event->time.time.tv_sec;
+ eventTime.nsec = event->time.time.tv_nsec;
+ eventTime = eventTime - m_alsaRecordStartTime + m_playStartPosition;
+
+#ifdef DEBUG_ALSA
+ if (!fromController) {
+ std::cerr << "Received normal event: type " << int(event->type) << ", chan " << channel << ", note " << int(event->data.note.note) << ", time " << eventTime << std::endl;
+ }
+#endif
+
+ switch (event->type) {
+ case SND_SEQ_EVENT_NOTE:
+ case SND_SEQ_EVENT_NOTEON:
+ if (fromController)
+ continue;
+ if (event->data.note.velocity > 0) {
+ MappedEvent *mE = new MappedEvent();
+ mE->setPitch(event->data.note.note);
+ mE->setVelocity(event->data.note.velocity);
+ mE->setEventTime(eventTime);
+ mE->setRecordedChannel(channel);
+ mE->setRecordedDevice(deviceId);
+
+ // Negative duration - we need to hear the NOTE ON
+ // so we must insert it now with a negative duration
+ // and pick and mix against the following NOTE OFF
+ // when we create the recorded segment.
+ //
+ mE->setDuration(RealTime( -1, 0));
+
+ // Create a copy of this when we insert the NOTE ON -
+ // keeping a copy alive on the m_noteOnMap.
+ //
+ // We shake out the two NOTE Ons after we've recorded
+ // them.
+ //
+ m_recordComposition.insert(new MappedEvent(mE));
+ m_noteOnMap[deviceId][chanNoteKey] = mE;
+
+ break;
+ }
+
+ case SND_SEQ_EVENT_NOTEOFF:
+ if (fromController)
+ continue;
+
+ if (m_noteOnMap[deviceId][chanNoteKey] != 0) {
+
+ // Set duration correctly on the NOTE OFF
+ //
+ MappedEvent *mE = m_noteOnMap[deviceId][chanNoteKey];
+ RealTime duration = eventTime - mE->getEventTime();
+
+#ifdef DEBUG_ALSA
+ std::cerr << "NOTE OFF: found NOTE ON at " << mE->getEventTime() << std::endl;
+#endif
+
+ if (duration < RealTime::zeroTime) {
+ duration = RealTime::zeroTime;
+ mE->setEventTime(eventTime);
+ }
+
+ // Velocity 0 - NOTE OFF. Set duration correctly
+ // for recovery later.
+ //
+ mE->setVelocity(0);
+ mE->setDuration(duration);
+
+ // force shut off of note
+ m_recordComposition.insert(mE);
+
+ // reset the reference
+ //
+ m_noteOnMap[deviceId][chanNoteKey] = 0;
+
+ }
+ break;
+
+ case SND_SEQ_EVENT_KEYPRESS: {
+ if (fromController)
+ continue;
+
+ // Fix for 632964 by Pedro Lopez-Cabanillas (20030523)
+ //
+ MappedEvent *mE = new MappedEvent();
+ mE->setType(MappedEvent::MidiKeyPressure);
+ mE->setEventTime(eventTime);
+ mE->setData1(event->data.note.note);
+ mE->setData2(event->data.note.velocity);
+ mE->setRecordedChannel(channel);
+ mE->setRecordedDevice(deviceId);
+ m_recordComposition.insert(mE);
+ }
+ break;
+
+ case SND_SEQ_EVENT_CONTROLLER: {
+ MappedEvent *mE = new MappedEvent();
+ mE->setType(MappedEvent::MidiController);
+ mE->setEventTime(eventTime);
+ mE->setData1(event->data.control.param);
+ mE->setData2(event->data.control.value);
+ mE->setRecordedChannel(channel);
+ mE->setRecordedDevice(deviceId);
+ m_recordComposition.insert(mE);
+ }
+ break;
+
+ case SND_SEQ_EVENT_PGMCHANGE: {
+ MappedEvent *mE = new MappedEvent();
+ mE->setType(MappedEvent::MidiProgramChange);
+ mE->setEventTime(eventTime);
+ mE->setData1(event->data.control.value);
+ mE->setRecordedChannel(channel);
+ mE->setRecordedDevice(deviceId);
+ m_recordComposition.insert(mE);
+
+ }
+ break;
+
+ case SND_SEQ_EVENT_PITCHBEND: {
+ if (fromController)
+ continue;
+
+ // Fix for 711889 by Pedro Lopez-Cabanillas (20030523)
+ //
+ int s = event->data.control.value + 8192;
+ int d1 = (s >> 7) & 0x7f; // data1 = MSB
+ int d2 = s & 0x7f; // data2 = LSB
+ MappedEvent *mE = new MappedEvent();
+ mE->setType(MappedEvent::MidiPitchBend);
+ mE->setEventTime(eventTime);
+ mE->setData1(d1);
+ mE->setData2(d2);
+ mE->setRecordedChannel(channel);
+ mE->setRecordedDevice(deviceId);
+ m_recordComposition.insert(mE);
+ }
+ break;
+
+ case SND_SEQ_EVENT_CHANPRESS: {
+ if (fromController)
+ continue;
+
+ // Fixed by Pedro Lopez-Cabanillas (20030523)
+ //
+ int s = event->data.control.value & 0x7f;
+ MappedEvent *mE = new MappedEvent();
+ mE->setType(MappedEvent::MidiChannelPressure);
+ mE->setEventTime(eventTime);
+ mE->setData1(s);
+ mE->setRecordedChannel(channel);
+ mE->setRecordedDevice(deviceId);
+ m_recordComposition.insert(mE);
+ }
+ break;
+
+ case SND_SEQ_EVENT_SYSEX:
+
+ if (fromController)
+ continue;
+
+ if (!testForMTCSysex(event) &&
+ !testForMMCSysex(event)) {
+
+ // Bundle up the data into a block on the MappedEvent
+ //
+ std::string data;
+ char *ptr = (char*)(event->data.ext.ptr);
+ for (unsigned int i = 0; i < event->data.ext.len; ++i)
+ data += *(ptr++);
+
+#ifdef DEBUG_ALSA
+
+ if ((MidiByte)(data[1]) == MIDI_SYSEX_RT) {
+ std::cerr << "REALTIME SYSEX" << endl;
+ for (unsigned int ii = 0; ii < event->data.ext.len; ++ii) {
+ printf("B %d = %02x\n", ii, ((char*)(event->data.ext.ptr))[ii]);
+ }
+ } else {
+ std::cerr << "NON-REALTIME SYSEX" << endl;
+ for (unsigned int ii = 0; ii < event->data.ext.len; ++ii) {
+ printf("B %d = %02x\n", ii, ((char*)(event->data.ext.ptr))[ii]);
+ }
+ }
+#endif
+
+ MappedEvent *mE = new MappedEvent();
+ mE->setType(MappedEvent::MidiSystemMessage);
+ mE->setData1(MIDI_SYSTEM_EXCLUSIVE);
+ mE->setRecordedDevice(deviceId);
+ // chop off SYX and EOX bytes from data block
+ // Fix for 674731 by Pedro Lopez-Cabanillas (20030601)
+ DataBlockRepository::setDataBlockForEvent(mE, data.substr(1, data.length() - 2));
+ mE->setEventTime(eventTime);
+ m_recordComposition.insert(mE);
+ }
+ break;
+
+
+ case SND_SEQ_EVENT_SENSING: // MIDI device is still there
+ break;
+
+ case SND_SEQ_EVENT_QFRAME:
+ if (fromController)
+ continue;
+ if (getMTCStatus() == TRANSPORT_SLAVE) {
+ handleMTCQFrame(event->data.control.value, eventTime);
+ }
+ break;
+
+ case SND_SEQ_EVENT_CLOCK:
+#ifdef DEBUG_ALSA
+
+ std::cerr << "AlsaDriver::getMappedComposition - "
+ << "got realtime MIDI clock" << std::endl;
+#endif
+
+ break;
+
+ case SND_SEQ_EVENT_START:
+ if ((getMIDISyncStatus() == TRANSPORT_SLAVE) && !isPlaying()) {
+ ExternalTransport *transport = getExternalTransportControl();
+ if (transport) {
+ transport->transportJump(ExternalTransport::TransportStopAtTime,
+ RealTime::zeroTime);
+ transport->transportChange(ExternalTransport::TransportStart);
+ }
+ }
+#ifdef DEBUG_ALSA
+ std::cerr << "AlsaDriver::getMappedComposition - "
+ << "START" << std::endl;
+#endif
+
+ break;
+
+ case SND_SEQ_EVENT_CONTINUE:
+ if ((getMIDISyncStatus() == TRANSPORT_SLAVE) && !isPlaying()) {
+ ExternalTransport *transport = getExternalTransportControl();
+ if (transport) {
+ transport->transportChange(ExternalTransport::TransportPlay);
+ }
+ }
+#ifdef DEBUG_ALSA
+ std::cerr << "AlsaDriver::getMappedComposition - "
+ << "CONTINUE" << std::endl;
+#endif
+
+ break;
+
+ case SND_SEQ_EVENT_STOP:
+ if ((getMIDISyncStatus() == TRANSPORT_SLAVE) && isPlaying()) {
+ ExternalTransport *transport = getExternalTransportControl();
+ if (transport) {
+ transport->transportChange(ExternalTransport::TransportStop);
+ }
+ }
+#ifdef DEBUG_ALSA
+ std::cerr << "AlsaDriver::getMappedComposition - "
+ << "STOP" << std::endl;
+#endif
+
+ break;
+
+ case SND_SEQ_EVENT_SONGPOS:
+#ifdef DEBUG_ALSA
+
+ std::cerr << "AlsaDriver::getMappedComposition - "
+ << "SONG POSITION" << std::endl;
+#endif
+
+ break;
+
+ // these cases are handled by checkForNewClients
+ //
+ case SND_SEQ_EVENT_CLIENT_START:
+ case SND_SEQ_EVENT_CLIENT_EXIT:
+ case SND_SEQ_EVENT_CLIENT_CHANGE:
+ case SND_SEQ_EVENT_PORT_START:
+ case SND_SEQ_EVENT_PORT_EXIT:
+ case SND_SEQ_EVENT_PORT_CHANGE:
+ case SND_SEQ_EVENT_PORT_SUBSCRIBED:
+ case SND_SEQ_EVENT_PORT_UNSUBSCRIBED:
+ m_portCheckNeeded = true;
+#ifdef DEBUG_ALSA
+
+ std::cerr << "AlsaDriver::getMappedComposition - "
+ << "got announce event ("
+ << int(event->type) << ")" << std::endl;
+#endif
+
+ break;
+ case SND_SEQ_EVENT_TICK:
+ default:
+#ifdef DEBUG_ALSA
+
+ std::cerr << "AlsaDriver::getMappedComposition - "
+ << "got unhandled MIDI event type from ALSA sequencer"
+ << "(" << int(event->type) << ")" << std::endl;
+#endif
+
+ break;
+
+
+ }
+ }
+
+ if (getMTCStatus() == TRANSPORT_SLAVE && isPlaying()) {
+#ifdef MTC_DEBUG
+ std::cerr << "seq time is " << getSequencerTime() << ", last MTC receive "
+ << m_mtcLastReceive << ", first time " << m_mtcFirstTime << std::endl;
+#endif
+
+ if (m_mtcFirstTime == 0) { // have received _some_ MTC quarter-frame info
+ RealTime seqTime = getSequencerTime();
+ if (m_mtcLastReceive < seqTime &&
+ seqTime - m_mtcLastReceive > RealTime(0, 500000000L)) {
+ ExternalTransport *transport = getExternalTransportControl();
+ if (transport) {
+ transport->transportJump(ExternalTransport::TransportStopAtTime,
+ m_mtcLastEncoded);
+ }
+ }
+ }
+ }
+
+ return &m_recordComposition;
+}
+
+static int lock_count = 0;
+
+void
+AlsaDriver::handleMTCQFrame(unsigned int data_byte, RealTime the_time)
+{
+ if (getMTCStatus() != TRANSPORT_SLAVE)
+ return ;
+
+ switch (data_byte & 0xF0) {
+ /* Frame */
+ case 0x00:
+ /*
+ * Reset everything
+ */
+ m_mtcReceiveTime = the_time;
+ m_mtcFrames = data_byte & 0x0f;
+ m_mtcSeconds = 0;
+ m_mtcMinutes = 0;
+ m_mtcHours = 0;
+ m_mtcSMPTEType = 0;
+
+ break;
+
+ case 0x10:
+ m_mtcFrames |= (data_byte & 0x0f) << 4;
+ break;
+
+ /* Seconds */
+ case 0x20:
+ m_mtcSeconds = data_byte & 0x0f;
+ break;
+ case 0x30:
+ m_mtcSeconds |= (data_byte & 0x0f) << 4;
+ break;
+
+ /* Minutes */
+ case 0x40:
+ m_mtcMinutes = data_byte & 0x0f;
+ break;
+ case 0x50:
+ m_mtcMinutes |= (data_byte & 0x0f) << 4;
+ break;
+
+ /* Hours and SMPTE type */
+ case 0x60:
+ m_mtcHours = data_byte & 0x0f;
+ break;
+
+ case 0x70: {
+ m_mtcHours |= (data_byte & 0x01) << 4;
+ m_mtcSMPTEType = (data_byte & 0x06) >> 1;
+
+ int fps = 30;
+ if (m_mtcSMPTEType == 0)
+ fps = 24;
+ else if (m_mtcSMPTEType == 1)
+ fps = 25;
+
+ /*
+ * Ok, got all the bits now
+ * (Assuming time is rolling forward)
+ */
+
+ /* correct for 2-frame lag */
+ m_mtcFrames += 2;
+ if (m_mtcFrames >= fps) {
+ m_mtcFrames -= fps;
+ if (++m_mtcSeconds == 60) {
+ m_mtcSeconds = 0;
+ if (++m_mtcMinutes == 60) {
+ m_mtcMinutes = 0;
+ ++m_mtcHours;
+ }
+ }
+ }
+
+#ifdef MTC_DEBUG
+ printf("RG MTC: Got a complete sequence: %02d:%02d:%02d.%02d (type %d)\n",
+ m_mtcHours,
+ m_mtcMinutes,
+ m_mtcSeconds,
+ m_mtcFrames,
+ m_mtcSMPTEType);
+#endif
+
+ /* compute encoded time */
+ m_mtcEncodedTime.sec = m_mtcSeconds +
+ m_mtcMinutes * 60 +
+ m_mtcHours * 60 * 60;
+
+ switch (fps) {
+ case 24:
+ m_mtcEncodedTime.nsec = (int)
+ ((125000000UL * (unsigned)m_mtcFrames) / (unsigned) 3);
+ break;
+ case 25:
+ m_mtcEncodedTime.nsec = (int)
+ (40000000UL * (unsigned)m_mtcFrames);
+ break;
+ case 30:
+ default:
+ m_mtcEncodedTime.nsec = (int)
+ ((100000000UL * (unsigned)m_mtcFrames) / (unsigned) 3);
+ break;
+ }
+
+ /*
+ * We only mess with the clock if we are playing
+ */
+ if (m_playing) {
+#ifdef MTC_DEBUG
+ std::cerr << "RG MTC: Tstamp " << m_mtcEncodedTime;
+ std::cerr << " Received @ " << m_mtcReceiveTime << endl;
+#endif
+
+ calibrateMTC();
+
+ RealTime t_diff = m_mtcEncodedTime - m_mtcReceiveTime;
+#ifdef MTC_DEBUG
+
+ std::cerr << "Diff: " << t_diff << endl;
+#endif
+
+ /* -ve diff means ALSA time ahead of MTC time */
+
+ if (t_diff.sec > 0) {
+ tweakSkewForMTC(60000);
+ } else if (t_diff.sec < 0) {
+ tweakSkewForMTC( -60000);
+ } else {
+ /* "small" diff - use adaptive technique */
+ tweakSkewForMTC(t_diff.nsec / 1400);
+ if ((t_diff.nsec / 1000000) == 0) {
+ if (++lock_count == 3) {
+ printf("Got a lock @ %02d:%02d:%02d.%02d (type %d)\n",
+ m_mtcHours,
+ m_mtcMinutes,
+ m_mtcSeconds,
+ m_mtcFrames,
+ m_mtcSMPTEType);
+ }
+ } else {
+ lock_count = 0;
+ }
+ }
+
+ } else if (m_eat_mtc > 0) {
+#ifdef MTC_DEBUG
+ std::cerr << "MTC: Received quarter frame just after issuing MMC stop - ignore it" << std::endl;
+#endif
+
+ --m_eat_mtc;
+ } else {
+ /* If we're not playing, we should be. */
+#ifdef MTC_DEBUG
+ std::cerr << "MTC: Received quarter frame while not playing - starting now" << std::endl;
+#endif
+
+ ExternalTransport *transport = getExternalTransportControl();
+ if (transport) {
+ transport->transportJump
+ (ExternalTransport::TransportStartAtTime,
+ m_mtcEncodedTime);
+ }
+ }
+
+ break;
+ }
+
+ /* Oh dear, demented device! */
+ default:
+ break;
+ }
+}
+
+void
+AlsaDriver::insertMTCFullFrame(RealTime time)
+{
+ snd_seq_event_t event;
+
+ snd_seq_ev_clear(&event);
+ snd_seq_ev_set_source(&event, m_syncOutputPort);
+ snd_seq_ev_set_subs(&event);
+
+ m_mtcEncodedTime = time;
+ m_mtcSeconds = m_mtcEncodedTime.sec % 60;
+ m_mtcMinutes = (m_mtcEncodedTime.sec / 60) % 60;
+ m_mtcHours = (m_mtcEncodedTime.sec / 3600);
+
+ // We always send at 25fps, it's the easiest to avoid rounding problems
+ m_mtcFrames = (unsigned)m_mtcEncodedTime.nsec / 40000000U;
+
+ time = time + m_alsaPlayStartTime - m_playStartPosition;
+ snd_seq_real_time_t atime = { time.sec, time.nsec };
+
+ unsigned char data[10] =
+ { MIDI_SYSTEM_EXCLUSIVE,
+ MIDI_SYSEX_RT, 127, 1, 1,
+ 0, 0, 0, 0,
+ MIDI_END_OF_EXCLUSIVE };
+
+ data[5] = ((unsigned char)m_mtcHours & 0x1f) + (1 << 5); // 1 indicates 25fps
+ data[6] = (unsigned char)m_mtcMinutes;
+ data[7] = (unsigned char)m_mtcSeconds;
+ data[8] = (unsigned char)m_mtcFrames;
+
+ snd_seq_ev_schedule_real(&event, m_queue, 0, &atime);
+ snd_seq_ev_set_sysex(&event, 10, data);
+
+ checkAlsaError(snd_seq_event_output(m_midiHandle, &event),
+ "insertMTCFullFrame event send");
+
+ if (m_queueRunning) {
+ checkAlsaError(snd_seq_drain_output(m_midiHandle), "insertMTCFullFrame drain");
+ }
+}
+
+void
+AlsaDriver::insertMTCQFrames(RealTime sliceStart, RealTime sliceEnd)
+{
+ if (sliceStart == RealTime::zeroTime && sliceEnd == RealTime::zeroTime) {
+ // not a real slice
+ return ;
+ }
+
+ // We send at 25fps, it's the easiest to avoid rounding problems
+ RealTime twoFrames(0, 80000000U);
+ RealTime quarterFrame(0, 10000000U);
+ int fps = 25;
+
+#ifdef MTC_DEBUG
+
+ std::cout << "AlsaDriver::insertMTCQFrames(" << sliceStart << ","
+ << sliceEnd << "): first time " << m_mtcFirstTime << std::endl;
+#endif
+
+ RealTime t;
+
+ if (m_mtcFirstTime != 0) { // first time through, reset location
+ m_mtcEncodedTime = sliceStart;
+ t = sliceStart;
+ m_mtcFirstTime = 0;
+ } else {
+ t = m_mtcEncodedTime + quarterFrame;
+ }
+
+ m_mtcSeconds = m_mtcEncodedTime.sec % 60;
+ m_mtcMinutes = (m_mtcEncodedTime.sec / 60) % 60;
+ m_mtcHours = (m_mtcEncodedTime.sec / 3600);
+ m_mtcFrames = (unsigned)m_mtcEncodedTime.nsec / 40000000U; // 25fps
+
+ std::string bytes = " ";
+
+ int type = 0;
+
+ while (m_mtcEncodedTime < sliceEnd) {
+
+ snd_seq_event_t event;
+ snd_seq_ev_clear(&event);
+ snd_seq_ev_set_source(&event, m_syncOutputPort);
+ snd_seq_ev_set_subs(&event);
+
+#ifdef MTC_DEBUG
+
+ std::cout << "Sending MTC quarter frame at " << t << std::endl;
+#endif
+
+ unsigned char c = (type << 4);
+
+ switch (type) {
+ case 0:
+ c += ((unsigned char)m_mtcFrames & 0x0f);
+ break;
+ case 1:
+ c += (((unsigned char)m_mtcFrames & 0xf0) >> 4);
+ break;
+ case 2:
+ c += ((unsigned char)m_mtcSeconds & 0x0f);
+ break;
+ case 3:
+ c += (((unsigned char)m_mtcSeconds & 0xf0) >> 4);
+ break;
+ case 4:
+ c += ((unsigned char)m_mtcMinutes & 0x0f);
+ break;
+ case 5:
+ c += (((unsigned char)m_mtcMinutes & 0xf0) >> 4);
+ break;
+ case 6:
+ c += ((unsigned char)m_mtcHours & 0x0f);
+ break;
+ case 7: // hours high nibble + smpte type
+ c += (m_mtcHours >> 4) & 0x01;
+ c += (1 << 1); // type 1 indicates 25fps
+ break;
+ }
+
+ RealTime scheduleTime = t + m_alsaPlayStartTime - m_playStartPosition;
+ snd_seq_real_time_t atime = { scheduleTime.sec, scheduleTime.nsec };
+
+ event.type = SND_SEQ_EVENT_QFRAME;
+ event.data.control.value = c;
+
+ snd_seq_ev_schedule_real(&event, m_queue, 0, &atime);
+
+ checkAlsaError(snd_seq_event_output(m_midiHandle, &event),
+ "insertMTCQFrames sending qframe event");
+
+ if (++type == 8) {
+ m_mtcFrames += 2;
+ if (m_mtcFrames >= fps) {
+ m_mtcFrames -= fps;
+ if (++m_mtcSeconds == 60) {
+ m_mtcSeconds = 0;
+ if (++m_mtcMinutes == 60) {
+ m_mtcMinutes = 0;
+ ++m_mtcHours;
+ }
+ }
+ }
+ m_mtcEncodedTime = t;
+ type = 0;
+ }
+
+ t = t + quarterFrame;
+ }
+}
+
+bool
+AlsaDriver::testForMTCSysex(const snd_seq_event_t *event)
+{
+ if (getMTCStatus() != TRANSPORT_SLAVE)
+ return false;
+
+ // At this point, and possibly for the foreseeable future, the only
+ // sysex we're interested in is full-frame transport location
+
+#ifdef MTC_DEBUG
+
+ std::cerr << "MTC: testing sysex of length " << event->data.ext.len << ":" << std::endl;
+ for (int i = 0; i < event->data.ext.len; ++i) {
+ std::cerr << (int)*((unsigned char *)event->data.ext.ptr + i) << " ";
+ }
+ std::cerr << endl;
+#endif
+
+ if (event->data.ext.len != 10)
+ return false;
+
+ unsigned char *ptr = (unsigned char *)(event->data.ext.ptr);
+
+ if (*ptr++ != MIDI_SYSTEM_EXCLUSIVE)
+ return false;
+ if (*ptr++ != MIDI_SYSEX_RT)
+ return false;
+ if (*ptr++ > 127)
+ return false;
+
+ // 01 01 for MTC full frame
+
+ if (*ptr++ != 1)
+ return false;
+ if (*ptr++ != 1)
+ return false;
+
+ int htype = *ptr++;
+ int min = *ptr++;
+ int sec = *ptr++;
+ int frame = *ptr++;
+
+ if (*ptr != MIDI_END_OF_EXCLUSIVE)
+ return false;
+
+ int hour = (htype & 0x1f);
+ int type = (htype & 0xe0) >> 5;
+
+ m_mtcFrames = frame;
+ m_mtcSeconds = sec;
+ m_mtcMinutes = min;
+ m_mtcHours = hour;
+ m_mtcSMPTEType = type;
+
+ int fps = 30;
+ if (m_mtcSMPTEType == 0)
+ fps = 24;
+ else if (m_mtcSMPTEType == 1)
+ fps = 25;
+
+ m_mtcEncodedTime.sec = sec + min * 60 + hour * 60 * 60;
+
+ switch (fps) {
+ case 24:
+ m_mtcEncodedTime.nsec = (int)
+ ((125000000UL * (unsigned)m_mtcFrames) / (unsigned) 3);
+ break;
+ case 25:
+ m_mtcEncodedTime.nsec = (int)
+ (40000000UL * (unsigned)m_mtcFrames);
+ break;
+ case 30:
+ default:
+ m_mtcEncodedTime.nsec = (int)
+ ((100000000UL * (unsigned)m_mtcFrames) / (unsigned) 3);
+ break;
+ }
+
+#ifdef MTC_DEBUG
+ std::cerr << "MTC: MTC sysex found (frame type " << type
+ << "), jumping to " << m_mtcEncodedTime << std::endl;
+#endif
+
+ ExternalTransport *transport = getExternalTransportControl();
+ if (transport) {
+ transport->transportJump
+ (ExternalTransport::TransportJumpToTime,
+ m_mtcEncodedTime);
+ }
+
+ return true;
+}
+
+static int last_factor = 0;
+static int bias_factor = 0;
+
+void
+AlsaDriver::calibrateMTC()
+{
+ if (m_mtcFirstTime < 0)
+ return ;
+ else if (m_mtcFirstTime > 0) {
+ --m_mtcFirstTime;
+ m_mtcSigmaC = 0;
+ m_mtcSigmaE = 0;
+ } else {
+ RealTime diff_e = m_mtcEncodedTime - m_mtcLastEncoded;
+ RealTime diff_c = m_mtcReceiveTime - m_mtcLastReceive;
+
+#ifdef MTC_DEBUG
+
+ printf("RG MTC: diffs %d %d %d\n", diff_c.nsec, diff_e.nsec, m_mtcSkew);
+#endif
+
+ m_mtcSigmaE += ((long long int) diff_e.nsec) * m_mtcSkew;
+ m_mtcSigmaC += diff_c.nsec;
+
+
+ int t_bias = (m_mtcSigmaE / m_mtcSigmaC) - 0x10000;
+
+#ifdef MTC_DEBUG
+
+ printf("RG MTC: sigmas %lld %lld %d\n", m_mtcSigmaE, m_mtcSigmaC, t_bias);
+#endif
+
+ bias_factor = t_bias;
+ }
+
+ m_mtcLastReceive = m_mtcReceiveTime;
+ m_mtcLastEncoded = m_mtcEncodedTime;
+
+}
+
+void
+AlsaDriver::tweakSkewForMTC(int factor)
+{
+ if (factor > 50000) {
+ factor = 50000;
+ } else if (factor < -50000) {
+ factor = -50000;
+ } else if (factor == last_factor) {
+ return ;
+ } else {
+ if (m_mtcFirstTime == -1)
+ m_mtcFirstTime = 5;
+ }
+ last_factor = factor;
+
+ snd_seq_queue_tempo_t *q_ptr;
+ snd_seq_queue_tempo_alloca(&q_ptr);
+
+ snd_seq_get_queue_tempo( m_midiHandle, m_queue, q_ptr);
+
+ unsigned int t_skew = snd_seq_queue_tempo_get_skew(q_ptr);
+#ifdef MTC_DEBUG
+
+ std::cerr << "RG MTC: skew: " << t_skew;
+#endif
+
+ t_skew = 0x10000 + factor + bias_factor;
+
+#ifdef MTC_DEBUG
+
+ std::cerr << " changed to " << factor << "+" << bias_factor << endl;
+#endif
+
+ snd_seq_queue_tempo_set_skew(q_ptr, t_skew);
+ snd_seq_set_queue_tempo( m_midiHandle, m_queue, q_ptr);
+
+ m_mtcSkew = t_skew;
+}
+
+bool
+AlsaDriver::testForMMCSysex(const snd_seq_event_t *event)
+{
+ if (getMMCStatus() != TRANSPORT_SLAVE)
+ return false;
+
+ if (event->data.ext.len != 6)
+ return false;
+
+ unsigned char *ptr = (unsigned char *)(event->data.ext.ptr);
+
+ if (*ptr++ != MIDI_SYSTEM_EXCLUSIVE)
+ return false;
+ if (*ptr++ != MIDI_SYSEX_RT)
+ return false;
+ if (*ptr++ > 127)
+ return false;
+ if (*ptr++ != MIDI_SYSEX_RT_COMMAND)
+ return false;
+
+ int instruction = *ptr++;
+
+ if (*ptr != MIDI_END_OF_EXCLUSIVE)
+ return false;
+
+ if (instruction == MIDI_MMC_PLAY ||
+ instruction == MIDI_MMC_DEFERRED_PLAY) {
+ ExternalTransport *transport = getExternalTransportControl();
+ if (transport) {
+ transport->transportChange(ExternalTransport::TransportPlay);
+ }
+ } else if (instruction == MIDI_MMC_STOP) {
+ ExternalTransport *transport = getExternalTransportControl();
+ if (transport) {
+ transport->transportChange(ExternalTransport::TransportStop);
+ }
+ }
+
+ return true;
+}
+
+void
+AlsaDriver::processMidiOut(const MappedComposition &mC,
+ const RealTime &sliceStart,
+ const RealTime &sliceEnd)
+{
+ RealTime outputTime;
+ RealTime outputStopTime;
+ MappedInstrument *instrument;
+ ClientPortPair outputDevice;
+ MidiByte channel;
+ snd_seq_event_t event;
+
+ // special case for unqueued events
+ bool now = (sliceStart == RealTime::zeroTime && sliceEnd == RealTime::zeroTime);
+
+ if (!now) {
+ // This 0.5 sec is arbitrary, but it must be larger than the
+ // sequencer's read-ahead
+ RealTime diff = RealTime::fromSeconds(0.5);
+ RealTime cutoff = sliceStart - diff;
+ cropRecentNoteOffs(cutoff - m_playStartPosition + m_alsaPlayStartTime);
+ }
+
+ // These won't change in this slice
+ //
+ snd_seq_ev_clear(&event);
+
+ if ((mC.begin() != mC.end()) && getSequencerDataBlock()) {
+ getSequencerDataBlock()->setVisual(*mC.begin());
+ }
+
+#ifdef DEBUG_PROCESS_MIDI_OUT
+ std::cerr << "AlsaDriver::processMidiOut(" << sliceStart << "," << sliceEnd
+ << "), " << mC.size() << " events, now is " << now << std::endl;
+#endif
+
+ // NB the MappedComposition is implicitly ordered by time (std::multiset)
+
+ for (MappedComposition::const_iterator i = mC.begin(); i != mC.end(); ++i) {
+ if ((*i)->getType() >= MappedEvent::Audio)
+ continue;
+
+ bool isControllerOut = ((*i)->getRecordedDevice() ==
+ Device::CONTROL_DEVICE);
+
+ bool isSoftSynth = (!isControllerOut &&
+ ((*i)->getInstrument() >= SoftSynthInstrumentBase));
+
+ outputTime = (*i)->getEventTime() - m_playStartPosition +
+ m_alsaPlayStartTime;
+
+ if (now && !m_playing && m_queueRunning) {
+ // stop queue to ensure exact timing and make sure the
+ // event gets through right now
+#ifdef DEBUG_PROCESS_MIDI_OUT
+ std::cerr << "processMidiOut: stopping queue for now-event" << std::endl;
+#endif
+
+ checkAlsaError(snd_seq_stop_queue(m_midiHandle, m_queue, NULL), "processMidiOut(): stop queue");
+ checkAlsaError(snd_seq_drain_output(m_midiHandle), "processMidiOut(): draining");
+ }
+
+ RealTime alsaTimeNow = getAlsaTime();
+
+ if (now) {
+ if (!m_playing) {
+ outputTime = alsaTimeNow;
+ } else if (outputTime < alsaTimeNow) {
+ outputTime = alsaTimeNow + RealTime(0, 10000000);
+ }
+ }
+
+#ifdef DEBUG_PROCESS_MIDI_OUT
+ std::cerr << "processMidiOut[" << now << "]: event is at " << outputTime << " (" << outputTime - alsaTimeNow << " ahead of queue time), type " << int((*i)->getType()) << ", duration " << (*i)->getDuration() << std::endl;
+#endif
+
+ if (!m_queueRunning && outputTime < alsaTimeNow) {
+ RealTime adjust = alsaTimeNow - outputTime;
+ if ((*i)->getDuration() > RealTime::zeroTime) {
+ if ((*i)->getDuration() <= adjust) {
+#ifdef DEBUG_PROCESS_MIDI_OUT
+ std::cerr << "processMidiOut[" << now << "]: too late for this event, abandoning it" << std::endl;
+#endif
+
+ continue;
+ } else {
+#ifdef DEBUG_PROCESS_MIDI_OUT
+ std::cerr << "processMidiOut[" << now << "]: pushing event forward and reducing duration by " << adjust << std::endl;
+#endif
+
+ (*i)->setDuration((*i)->getDuration() - adjust);
+ }
+ } else {
+#ifdef DEBUG_PROCESS_MIDI_OUT
+ std::cerr << "processMidiOut[" << now << "]: pushing zero-duration event forward by " << adjust << std::endl;
+#endif
+
+ }
+ outputTime = alsaTimeNow;
+ }
+
+ processNotesOff(outputTime, now);
+
+#ifdef HAVE_LIBJACK
+
+ if (m_jackDriver) {
+ size_t frameCount = m_jackDriver->getFramesProcessed();
+ size_t elapsed = frameCount - _debug_jack_frame_count;
+ RealTime rt = RealTime::frame2RealTime(elapsed, m_jackDriver->getSampleRate());
+ rt = rt - getAlsaTime();
+#ifdef DEBUG_PROCESS_MIDI_OUT
+
+ std::cerr << "processMidiOut[" << now << "]: JACK time is " << rt << " ahead of ALSA time" << std::endl;
+#endif
+
+ }
+#endif
+
+ // Second and nanoseconds for ALSA
+ //
+ snd_seq_real_time_t time = { outputTime.sec, outputTime.nsec };
+
+ if (!isSoftSynth) {
+
+#ifdef DEBUG_PROCESS_MIDI_OUT
+ std::cout << "processMidiOut[" << now << "]: instrument " << (*i)->getInstrument() << std::endl;
+ std::cout << "pitch: " << (int)(*i)->getPitch() << ", velocity " << (int)(*i)->getVelocity() << ", duration " << (*i)->getDuration() << std::endl;
+#endif
+
+ snd_seq_ev_set_subs(&event);
+
+ // Set source according to port for device
+ //
+ int src;
+
+ if (isControllerOut) {
+ src = m_controllerPort;
+ } else {
+ src = getOutputPortForMappedInstrument((*i)->getInstrument());
+ }
+
+ if (src < 0) continue;
+ snd_seq_ev_set_source(&event, src);
+
+ snd_seq_ev_schedule_real(&event, m_queue, 0, &time);
+
+ } else {
+ event.time.time = time;
+ }
+
+ instrument = getMappedInstrument((*i)->getInstrument());
+
+ // set the stop time for Note Off
+ //
+ outputStopTime = outputTime + (*i)->getDuration()
+ - RealTime(0, 1); // notch it back 1nsec just to ensure
+ // correct ordering against any other
+ // note-ons at the same nominal time
+ bool needNoteOff = false;
+
+ if (isControllerOut) {
+ channel = (*i)->getRecordedChannel();
+#ifdef DEBUG_ALSA
+
+ std::cerr << "processMidiOut() - Event of type " << (int)((*i)->getType()) << " (data1 " << (int)(*i)->getData1() << ", data2 " << (int)(*i)->getData2() << ") for external controller channel " << (int)channel << std::endl;
+#endif
+
+ } else if (instrument != 0) {
+ channel = instrument->getChannel();
+ } else {
+#ifdef DEBUG_ALSA
+ std::cerr << "processMidiOut() - No instrument for event of type "
+ << (int)(*i)->getType() << " at " << (*i)->getEventTime()
+ << std::endl;
+#endif
+
+ channel = 0;
+ }
+
+ switch ((*i)->getType()) {
+
+ case MappedEvent::MidiNoteOneShot:
+ {
+ snd_seq_ev_set_noteon(&event,
+ channel,
+ (*i)->getPitch(),
+ (*i)->getVelocity());
+ needNoteOff = true;
+
+ if (!isSoftSynth && getSequencerDataBlock()) {
+ LevelInfo info;
+ info.level = (*i)->getVelocity();
+ info.levelRight = 0;
+ getSequencerDataBlock()->setInstrumentLevel
+ ((*i)->getInstrument(), info);
+ }
+
+ weedRecentNoteOffs((*i)->getPitch(), channel, (*i)->getInstrument());
+ }
+ break;
+
+ case MappedEvent::MidiNote:
+ // We always use plain NOTE ON here, not ALSA
+ // time+duration notes, because we have our own NOTE
+ // OFF stack (which will be augmented at the bottom of
+ // this function) and we want to ensure it gets used
+ // for the purposes of e.g. soft synths
+ //
+ if ((*i)->getVelocity() > 0) {
+ snd_seq_ev_set_noteon(&event,
+ channel,
+ (*i)->getPitch(),
+ (*i)->getVelocity());
+
+ if (!isSoftSynth && getSequencerDataBlock()) {
+ LevelInfo info;
+ info.level = (*i)->getVelocity();
+ info.levelRight = 0;
+ getSequencerDataBlock()->setInstrumentLevel
+ ((*i)->getInstrument(), info);
+ }
+
+ weedRecentNoteOffs((*i)->getPitch(), channel, (*i)->getInstrument());
+ } else {
+ snd_seq_ev_set_noteoff(&event,
+ channel,
+ (*i)->getPitch(),
+ (*i)->getVelocity());
+ }
+
+ break;
+
+ case MappedEvent::MidiProgramChange:
+ snd_seq_ev_set_pgmchange(&event,
+ channel,
+ (*i)->getData1());
+ break;
+
+ case MappedEvent::MidiKeyPressure:
+ snd_seq_ev_set_keypress(&event,
+ channel,
+ (*i)->getData1(),
+ (*i)->getData2());
+ break;
+
+ case MappedEvent::MidiChannelPressure:
+ snd_seq_ev_set_chanpress(&event,
+ channel,
+ (*i)->getData1());
+ break;
+
+ case MappedEvent::MidiPitchBend: {
+ int d1 = (int)((*i)->getData1());
+ int d2 = (int)((*i)->getData2());
+ int value = ((d1 << 7) | d2) - 8192;
+
+ // keep within -8192 to +8192
+ //
+ // if (value & 0x4000)
+ // value -= 0x8000;
+
+ snd_seq_ev_set_pitchbend(&event,
+ channel,
+ value);
+ }
+ break;
+
+ case MappedEvent::MidiSystemMessage: {
+ switch ((*i)->getData1()) {
+ case MIDI_SYSTEM_EXCLUSIVE: {
+ char out[2];
+ sprintf(out, "%c", MIDI_SYSTEM_EXCLUSIVE);
+ std::string data = out;
+
+ data += DataBlockRepository::getDataBlockForEvent((*i));
+
+ sprintf(out, "%c", MIDI_END_OF_EXCLUSIVE);
+ data += out;
+
+ snd_seq_ev_set_sysex(&event,
+ data.length(),
+ (char*)(data.c_str()));
+ }
+ break;
+
+ case MIDI_TIMING_CLOCK: {
+ RealTime rt =
+ RealTime(time.tv_sec, time.tv_nsec);
+
+ /*
+ std::cerr << "AlsaDriver::processMidiOut - "
+ << "send clock @ " << rt << std::endl;
+ */
+
+ sendSystemQueued(SND_SEQ_EVENT_CLOCK, "", rt);
+
+ continue;
+
+ }
+ break;
+
+ default:
+ std::cerr << "AlsaDriver::processMidiOut - "
+ << "unrecognised system message"
+ << std::endl;
+ break;
+ }
+ }
+ break;
+
+ case MappedEvent::MidiController:
+ snd_seq_ev_set_controller(&event,
+ channel,
+ (*i)->getData1(),
+ (*i)->getData2());
+ break;
+
+ case MappedEvent::Audio:
+ case MappedEvent::AudioCancel:
+ case MappedEvent::AudioLevel:
+ case MappedEvent::AudioStopped:
+ case MappedEvent::SystemUpdateInstruments:
+ case MappedEvent::SystemJackTransport: //???
+ case MappedEvent::SystemMMCTransport:
+ case MappedEvent::SystemMIDIClock:
+ case MappedEvent::SystemMIDISyncAuto:
+ break;
+
+ default:
+ case MappedEvent::InvalidMappedEvent:
+#ifdef DEBUG_ALSA
+
+ std::cerr << "AlsaDriver::processMidiOut - "
+ << "skipping unrecognised or invalid MappedEvent type"
+ << std::endl;
+#endif
+
+ continue;
+ }
+
+ if (isSoftSynth) {
+
+ processSoftSynthEventOut((*i)->getInstrument(), &event, now);
+
+ } else {
+ checkAlsaError(snd_seq_event_output(m_midiHandle, &event),
+ "processMidiOut(): output queued");
+
+ if (now) {
+ if (m_queueRunning && !m_playing) {
+ // restart queue
+#ifdef DEBUG_PROCESS_MIDI_OUT
+ std::cerr << "processMidiOut: restarting queue after now-event" << std::endl;
+#endif
+
+ checkAlsaError(snd_seq_continue_queue(m_midiHandle, m_queue, NULL), "processMidiOut(): continue queue");
+ }
+ checkAlsaError(snd_seq_drain_output(m_midiHandle), "processMidiOut(): draining");
+ }
+ }
+
+ // Add note to note off stack
+ //
+ if (needNoteOff) {
+ NoteOffEvent *noteOffEvent =
+ new NoteOffEvent(outputStopTime, // already calculated
+ (*i)->getPitch(),
+ channel,
+ (*i)->getInstrument());
+
+#ifdef DEBUG_ALSA
+
+ std::cerr << "Adding NOTE OFF at " << outputStopTime
+ << std::endl;
+#endif
+
+ m_noteOffQueue.insert(noteOffEvent);
+ }
+ }
+
+ processNotesOff(sliceEnd - m_playStartPosition + m_alsaPlayStartTime, now);
+
+ if (getMTCStatus() == TRANSPORT_MASTER) {
+ insertMTCQFrames(sliceStart, sliceEnd);
+ }
+
+ if (m_queueRunning) {
+
+ if (now && !m_playing) {
+ // just to be sure
+#ifdef DEBUG_PROCESS_MIDI_OUT
+ std::cerr << "processMidiOut: restarting queue after all now-events" << std::endl;
+#endif
+
+ checkAlsaError(snd_seq_continue_queue(m_midiHandle, m_queue, NULL), "processMidiOut(): continue queue");
+ }
+
+#ifdef DEBUG_PROCESS_MIDI_OUT
+ // std::cerr << "processMidiOut: m_queueRunning " << m_queueRunning
+ // << ", now " << now << std::endl;
+#endif
+ checkAlsaError(snd_seq_drain_output(m_midiHandle), "processMidiOut(): draining");
+ }
+}
+
+void
+AlsaDriver::processSoftSynthEventOut(InstrumentId id, const snd_seq_event_t *ev, bool now)
+{
+#ifdef DEBUG_PROCESS_SOFT_SYNTH_OUT
+ std::cerr << "AlsaDriver::processSoftSynthEventOut: instrument " << id << ", now " << now << std::endl;
+#endif
+
+#ifdef HAVE_LIBJACK
+
+ if (!m_jackDriver)
+ return ;
+ RunnablePluginInstance *synthPlugin = m_jackDriver->getSynthPlugin(id);
+
+ if (synthPlugin) {
+
+ RealTime t(ev->time.time.tv_sec, ev->time.time.tv_nsec);
+
+ if (now)
+ t = RealTime::zeroTime;
+ else
+ t = t + m_playStartPosition - m_alsaPlayStartTime;
+
+#ifdef DEBUG_PROCESS_SOFT_SYNTH_OUT
+
+ std::cerr << "AlsaDriver::processSoftSynthEventOut: event time " << t << std::endl;
+#endif
+
+ synthPlugin->sendEvent(t, ev);
+
+ if (now) {
+#ifdef DEBUG_PROCESS_SOFT_SYNTH_OUT
+ std::cerr << "AlsaDriver::processSoftSynthEventOut: setting haveAsyncAudioEvent" << std::endl;
+#endif
+
+ m_jackDriver->setHaveAsyncAudioEvent();
+ }
+ }
+#endif
+}
+
+void
+AlsaDriver::startClocks()
+{
+ int result;
+
+#ifdef DEBUG_ALSA
+
+ std::cerr << "AlsaDriver::startClocks" << std::endl;
+#endif
+
+ if (m_needJackStart) {
+#ifdef DEBUG_ALSA
+ std::cerr << "AlsaDriver::startClocks: Need JACK start (m_playing = " << m_playing << ")" << std::endl;
+#endif
+
+ }
+
+#ifdef HAVE_LIBJACK
+
+ // New JACK transport scheme: The initialisePlayback,
+ // resetPlayback and stopPlayback methods set m_needJackStart, and
+ // then this method checks it and calls the appropriate JACK
+ // transport start or relocate method, which calls back on
+ // startClocksApproved when ready. (Previously this method always
+ // called the JACK transport start method, so we couldn't handle
+ // moving the pointer when not playing, and we had to stop the
+ // transport explicitly from resetPlayback when repositioning
+ // during playback.)
+
+ if (m_jackDriver) {
+
+ // Don't need any locks on this, except for those that the
+ // driver methods take and hold for themselves
+
+ if (m_needJackStart != NeedNoJackStart) {
+ if (m_needJackStart == NeedJackStart ||
+ m_playing) {
+#ifdef DEBUG_ALSA
+ std::cerr << "AlsaDriver::startClocks: playing, prebuffer audio" << std::endl;
+#endif
+
+ m_jackDriver->prebufferAudio();
+ } else {
+#ifdef DEBUG_ALSA
+ std::cerr << "AlsaDriver::startClocks: prepare audio only" << std::endl;
+#endif
+
+ m_jackDriver->prepareAudio();
+ }
+ bool rv;
+ if (m_needJackStart == NeedJackReposition) {
+ rv = m_jackDriver->relocateTransport();
+ } else {
+ rv = m_jackDriver->startTransport();
+ if (!rv) {
+#ifdef DEBUG_ALSA
+ std::cerr << "AlsaDriver::startClocks: Waiting for startClocksApproved" << std::endl;
+#endif
+ // need to wait for transport sync
+ _debug_jack_frame_count = m_jackDriver->getFramesProcessed();
+ return ;
+ }
+ }
+ }
+ }
+#endif
+
+ // Restart the timer
+ if ((result = snd_seq_continue_queue(m_midiHandle, m_queue, NULL)) < 0) {
+ std::cerr << "AlsaDriver::startClocks - couldn't start queue - "
+ << snd_strerror(result)
+ << std::endl;
+ reportFailure(MappedEvent::FailureALSACallFailed);
+ }
+
+#ifdef DEBUG_ALSA
+ std::cerr << "AlsaDriver::startClocks: started clocks" << std::endl;
+#endif
+
+ m_queueRunning = true;
+
+#ifdef HAVE_LIBJACK
+
+ if (m_jackDriver) {
+ _debug_jack_frame_count = m_jackDriver->getFramesProcessed();
+ }
+#endif
+
+ // process pending MIDI events
+ checkAlsaError(snd_seq_drain_output(m_midiHandle), "startClocks(): draining");
+}
+
+void
+AlsaDriver::startClocksApproved()
+{
+#ifdef DEBUG_ALSA
+ std::cerr << "AlsaDriver::startClocks: startClocksApproved" << std::endl;
+#endif
+
+ //!!!
+ m_needJackStart = NeedNoJackStart;
+ startClocks();
+ return ;
+
+ int result;
+
+ // Restart the timer
+ if ((result = snd_seq_continue_queue(m_midiHandle, m_queue, NULL)) < 0) {
+ std::cerr << "AlsaDriver::startClocks - couldn't start queue - "
+ << snd_strerror(result)
+ << std::endl;
+ reportFailure(MappedEvent::FailureALSACallFailed);
+ }
+
+ m_queueRunning = true;
+
+ // process pending MIDI events
+ checkAlsaError(snd_seq_drain_output(m_midiHandle), "startClocksApproved(): draining");
+}
+
+void
+AlsaDriver::stopClocks()
+{
+#ifdef DEBUG_ALSA
+ std::cerr << "AlsaDriver::stopClocks" << std::endl;
+#endif
+
+ if (checkAlsaError(snd_seq_stop_queue(m_midiHandle, m_queue, NULL), "stopClocks(): stopping queue") < 0) {
+ reportFailure(MappedEvent::FailureALSACallFailed);
+ }
+ checkAlsaError(snd_seq_drain_output(m_midiHandle), "stopClocks(): draining output to stop queue");
+
+ m_queueRunning = false;
+
+ // We used to call m_jackDriver->stop() from here, but we no
+ // longer do -- it's now called from stopPlayback() so as to
+ // handle repositioning during playback (when stopClocks is
+ // necessary but stopPlayback and m_jackDriver->stop() are not).
+
+ snd_seq_event_t event;
+ snd_seq_ev_clear(&event);
+ snd_seq_real_time_t z = { 0, 0 };
+ snd_seq_ev_set_queue_pos_real(&event, m_queue, &z);
+ snd_seq_ev_set_direct(&event);
+ checkAlsaError(snd_seq_control_queue(m_midiHandle, m_queue, SND_SEQ_EVENT_SETPOS_TIME,
+ 0, &event), "stopClocks(): setting zpos to queue");
+ // process that
+ checkAlsaError(snd_seq_drain_output(m_midiHandle), "stopClocks(): draining output to zpos queue");
+
+#ifdef DEBUG_ALSA
+
+ std::cerr << "AlsaDriver::stopClocks: ALSA time now is " << getAlsaTime() << std::endl;
+#endif
+
+ m_alsaPlayStartTime = RealTime::zeroTime;
+}
+
+
+void
+AlsaDriver::processEventsOut(const MappedComposition &mC)
+{
+ processEventsOut(mC, RealTime::zeroTime, RealTime::zeroTime);
+}
+
+void
+AlsaDriver::processEventsOut(const MappedComposition &mC,
+ const RealTime &sliceStart,
+ const RealTime &sliceEnd)
+{
+ // special case for unqueued events
+ bool now = (sliceStart == RealTime::zeroTime && sliceEnd == RealTime::zeroTime);
+
+ if (m_startPlayback) {
+ m_startPlayback = false;
+ // This only records whether we're playing in principle,
+ // not whether the clocks are actually ticking. Contrariwise,
+ // areClocksRunning tells us whether the clocks are ticking
+ // but not whether we're actually playing (the clocks go even
+ // when we're not). Check both if you want to know whether
+ // we're really rolling.
+ m_playing = true;
+
+ if (getMTCStatus() == TRANSPORT_SLAVE) {
+ tweakSkewForMTC(0);
+ }
+ }
+
+ AudioFile *audioFile = 0;
+ bool haveNewAudio = false;
+
+ // insert audio events if we find them
+ for (MappedComposition::const_iterator i = mC.begin(); i != mC.end(); ++i) {
+#ifdef HAVE_LIBJACK
+
+ // Play an audio file
+ //
+ if ((*i)->getType() == MappedEvent::Audio) {
+ if (!m_jackDriver)
+ continue;
+
+ // This is used for handling asynchronous
+ // (i.e. unexpected) audio events only
+
+ if ((*i)->getEventTime() > RealTime( -120, 0)) {
+ // Not an asynchronous event
+ continue;
+ }
+
+ // Check for existence of file - if the sequencer has died
+ // and been restarted then we're not always loaded up with
+ // the audio file references we should have. In the future
+ // we could make this just get the gui to reload our files
+ // when (or before) this fails.
+ //
+ audioFile = getAudioFile((*i)->getAudioID());
+
+ if (audioFile) {
+ MappedAudioFader *fader =
+ dynamic_cast<MappedAudioFader*>
+ (getMappedStudio()->getAudioFader((*i)->getInstrument()));
+
+ if (!fader) {
+ std::cerr << "WARNING: AlsaDriver::processEventsOut: no fader for audio instrument " << (*i)->getInstrument() << std::endl;
+ continue;
+ }
+
+ unsigned int channels = fader->getPropertyList(
+ MappedAudioFader::Channels)[0].toInt();
+
+ RealTime bufferLength = getAudioReadBufferLength();
+ int bufferFrames = RealTime::realTime2Frame
+ (bufferLength, m_jackDriver->getSampleRate());
+ if (bufferFrames % m_jackDriver->getBufferSize()) {
+ bufferFrames /= m_jackDriver->getBufferSize();
+ bufferFrames ++;
+ bufferFrames *= m_jackDriver->getBufferSize();
+ }
+
+ //#define DEBUG_PLAYING_AUDIO
+#ifdef DEBUG_PLAYING_AUDIO
+ std::cout << "Creating playable audio file: id " << audioFile->getId() << ", event time " << (*i)->getEventTime() << ", time now " << getAlsaTime() << ", start marker " << (*i)->getAudioStartMarker() << ", duration " << (*i)->getDuration() << ", instrument " << (*i)->getInstrument() << " channels " << channels << std::endl;
+
+ std::cout << "Read buffer length is " << bufferLength << " (" << bufferFrames << " frames)" << std::endl;
+#endif
+
+ PlayableAudioFile *paf = 0;
+
+ try {
+ paf = new PlayableAudioFile((*i)->getInstrument(),
+ audioFile,
+ getSequencerTime() +
+ (RealTime(1, 0) / 4),
+ (*i)->getAudioStartMarker(),
+ (*i)->getDuration(),
+ bufferFrames,
+ getSmallFileSize() * 1024,
+ channels,
+ m_jackDriver->getSampleRate());
+ } catch (...) {
+ continue;
+ }
+
+ if ((*i)->isAutoFading()) {
+ paf->setAutoFade(true);
+ paf->setFadeInTime((*i)->getFadeInTime());
+ paf->setFadeOutTime((*i)->getFadeInTime());
+
+ //#define DEBUG_AUTOFADING
+#ifdef DEBUG_AUTOFADING
+
+ std::cout << "PlayableAudioFile is AUTOFADING - "
+ << "in = " << (*i)->getFadeInTime()
+ << ", out = " << (*i)->getFadeOutTime()
+ << std::endl;
+#endif
+
+ }
+#ifdef DEBUG_AUTOFADING
+ else {
+ std::cout << "PlayableAudioFile has no AUTOFADE"
+ << std::endl;
+ }
+#endif
+
+
+ // segment runtime id
+ paf->setRuntimeSegmentId((*i)->getRuntimeSegmentId());
+
+ m_audioQueue->addUnscheduled(paf);
+
+ haveNewAudio = true;
+ } else {
+#ifdef DEBUG_ALSA
+ std::cerr << "AlsaDriver::processEventsOut - "
+ << "can't find audio file reference"
+ << std::endl;
+
+ std::cerr << "AlsaDriver::processEventsOut - "
+ << "try reloading the current Rosegarden file"
+ << std::endl;
+#else
+
+ ;
+#endif
+
+ }
+ }
+
+ // Cancel a playing audio file preview (this is predicated on
+ // runtime segment ID and optionally start time)
+ //
+ if ((*i)->getType() == MappedEvent::AudioCancel) {
+ cancelAudioFile(*i);
+ }
+
+#endif // HAVE_LIBJACK
+
+ if ((*i)->getType() == MappedEvent::SystemMIDIClock) {
+ switch ((int)(*i)->getData1()) {
+ case 0:
+ m_midiClockEnabled = false;
+#ifdef DEBUG_ALSA
+
+ std::cerr << "AlsaDriver::processEventsOut - "
+ << "Rosegarden MIDI CLOCK, START and STOP DISABLED"
+ << std::endl;
+#endif
+
+ setMIDISyncStatus(TRANSPORT_OFF);
+ break;
+
+ case 1:
+ m_midiClockEnabled = true;
+#ifdef DEBUG_ALSA
+
+ std::cerr << "AlsaDriver::processEventsOut - "
+ << "Rosegarden send MIDI CLOCK, START and STOP ENABLED"
+ << std::endl;
+#endif
+
+ setMIDISyncStatus(TRANSPORT_MASTER);
+ break;
+
+ case 2:
+ m_midiClockEnabled = false;
+#ifdef DEBUG_ALSA
+
+ std::cerr << "AlsaDriver::processEventsOut - "
+ << "Rosegarden accept START and STOP ENABLED"
+ << std::endl;
+#endif
+
+ setMIDISyncStatus(TRANSPORT_SLAVE);
+ break;
+ }
+ }
+
+ if ((*i)->getType() == MappedEvent::SystemMIDISyncAuto) {
+ if ((*i)->getData1()) {
+ m_midiSyncAutoConnect = true;
+#ifdef DEBUG_ALSA
+
+ std::cerr << "AlsaDriver::processEventsOut - "
+ << "Rosegarden MIDI SYNC AUTO ENABLED"
+ << std::endl;
+#endif
+
+ for (DevicePortMap::iterator dpmi = m_devicePortMap.begin();
+ dpmi != m_devicePortMap.end(); ++dpmi) {
+ snd_seq_connect_to(m_midiHandle,
+ m_syncOutputPort,
+ dpmi->second.first,
+ dpmi->second.second);
+ }
+ } else {
+ m_midiSyncAutoConnect = false;
+#ifdef DEBUG_ALSA
+
+ std::cerr << "AlsaDriver::processEventsOut - "
+ << "Rosegarden MIDI SYNC AUTO DISABLED"
+ << std::endl;
+#endif
+
+ }
+ }
+
+#ifdef HAVE_LIBJACK
+
+ // Set the JACK transport
+ if ((*i)->getType() == MappedEvent::SystemJackTransport) {
+ bool enabled = false;
+ bool master = false;
+
+ switch ((int)(*i)->getData1()) {
+ case 2:
+ master = true;
+ enabled = true;
+#ifdef DEBUG_ALSA
+
+ std::cerr << "AlsaDriver::processEventsOut - "
+ << "Rosegarden to follow JACK transport and request JACK timebase master role (not yet implemented)"
+ << std::endl;
+#endif
+
+ break;
+
+ case 1:
+ enabled = true;
+#ifdef DEBUG_ALSA
+
+ std::cerr << "AlsaDriver::processEventsOut - "
+ << "Rosegarden to follow JACK transport"
+ << std::endl;
+#endif
+
+ break;
+
+ case 0:
+ default:
+#ifdef DEBUG_ALSA
+
+ std::cerr << "AlsaDriver::processEventsOut - "
+ << "Rosegarden to ignore JACK transport"
+ << std::endl;
+#endif
+
+ break;
+ }
+
+ if (m_jackDriver) {
+ m_jackDriver->setTransportEnabled(enabled);
+ m_jackDriver->setTransportMaster(master);
+ }
+ }
+#endif // HAVE_LIBJACK
+
+
+ if ((*i)->getType() == MappedEvent::SystemMMCTransport) {
+ switch ((int)(*i)->getData1()) {
+ case 1:
+#ifdef DEBUG_ALSA
+
+ std::cerr << "AlsaDriver::processEventsOut - "
+ << "Rosegarden is MMC MASTER"
+ << std::endl;
+#endif
+
+ setMMCStatus(TRANSPORT_MASTER);
+ break;
+
+ case 2:
+#ifdef DEBUG_ALSA
+
+ std::cerr << "AlsaDriver::processEventsOut - "
+ << "Rosegarden is MMC SLAVE"
+ << std::endl;
+#endif
+
+ setMMCStatus(TRANSPORT_SLAVE);
+ break;
+
+ case 0:
+ default:
+#ifdef DEBUG_ALSA
+
+ std::cerr << "AlsaDriver::processEventsOut - "
+ << "Rosegarden MMC Transport DISABLED"
+ << std::endl;
+#endif
+
+ setMMCStatus(TRANSPORT_OFF);
+ break;
+ }
+ }
+
+ if ((*i)->getType() == MappedEvent::SystemMTCTransport) {
+ switch ((int)(*i)->getData1()) {
+ case 1:
+#ifdef DEBUG_ALSA
+
+ std::cerr << "AlsaDriver::processEventsOut - "
+ << "Rosegarden is MTC MASTER"
+ << std::endl;
+#endif
+
+ setMTCStatus(TRANSPORT_MASTER);
+ tweakSkewForMTC(0);
+ m_mtcFirstTime = -1;
+ break;
+
+ case 2:
+#ifdef DEBUG_ALSA
+
+ std::cerr << "AlsaDriver::processEventsOut - "
+ << "Rosegarden is MTC SLAVE"
+ << std::endl;
+#endif
+
+ setMTCStatus(TRANSPORT_SLAVE);
+ m_mtcFirstTime = -1;
+ break;
+
+ case 0:
+ default:
+#ifdef DEBUG_ALSA
+
+ std::cerr << "AlsaDriver::processEventsOut - "
+ << "Rosegarden MTC Transport DISABLED"
+ << std::endl;
+#endif
+
+ setMTCStatus(TRANSPORT_OFF);
+ m_mtcFirstTime = -1;
+ break;
+ }
+ }
+
+ if ((*i)->getType() == MappedEvent::SystemRecordDevice) {
+ DeviceId recordDevice =
+ (DeviceId)((*i)->getData1());
+ bool conn = (bool) ((*i)->getData2());
+
+ // Unset connections
+ //
+ // unsetRecordDevices();
+
+ // Special case to set for all record ports
+ //
+ if (recordDevice == Device::ALL_DEVICES) {
+ /* set all record devices */
+#ifdef DEBUG_ALSA
+ std::cerr << "AlsaDriver::processEventsOut - "
+ << "set all record devices - not implemented"
+ << std::endl;
+#endif
+
+ /*
+ MappedDeviceList::iterator it = m_devices.begin();
+ std::vector<int> ports;
+ std::vector<int>::iterator pIt;
+
+ for (; it != m_devices.end(); ++it)
+ {
+ std::cout << "DEVICE = " << (*it)->getName() << " - DIR = "
+ << (*it)->getDirection() << endl;
+ // ignore ports we can't connect to
+ if ((*it)->getDirection() == MidiDevice::WriteOnly) continue;
+
+ std::cout << "PORTS = " << ports.size() << endl;
+ ports = (*it)->getPorts();
+ for (pIt = ports.begin(); pIt != ports.end(); ++pIt)
+ {
+ setRecordDevice((*it)->getClient(), *pIt);
+ }
+ }
+ */
+ } else {
+ // Otherwise just for the one device and port
+ //
+ setRecordDevice(recordDevice, conn);
+ }
+ }
+
+ if ((*i)->getType() == MappedEvent::SystemAudioPortCounts) {
+ // never actually used, I think?
+ }
+
+ if ((*i)->getType() == MappedEvent::SystemAudioPorts) {
+#ifdef HAVE_LIBJACK
+ if (m_jackDriver) {
+ int data = (*i)->getData1();
+ m_jackDriver->setAudioPorts(data & MappedEvent::FaderOuts,
+ data & MappedEvent::SubmasterOuts);
+ }
+#else
+#ifdef DEBUG_ALSA
+ std::cerr << "AlsaDriver::processEventsOut - "
+ << "MappedEvent::SystemAudioPorts - no audio subsystem"
+ << std::endl;
+#endif
+#endif
+
+ }
+
+ if ((*i)->getType() == MappedEvent::SystemAudioFileFormat) {
+#ifdef HAVE_LIBJACK
+ int format = (*i)->getData1();
+ switch (format) {
+ case 0:
+ m_audioRecFileFormat = RIFFAudioFile::PCM;
+ break;
+ case 1:
+ m_audioRecFileFormat = RIFFAudioFile::FLOAT;
+ break;
+ default:
+#ifdef DEBUG_ALSA
+
+ std::cerr << "AlsaDriver::processEventsOut - "
+ << "MappedEvent::SystemAudioFileFormat - unexpected format number " << format
+ << std::endl;
+#endif
+
+ break;
+ }
+#else
+#ifdef DEBUG_ALSA
+ std::cerr << "AlsaDriver::processEventsOut - "
+ << "MappedEvent::SystemAudioFileFormat - no audio subsystem"
+ << std::endl;
+#endif
+#endif
+
+ }
+
+ if ((*i)->getType() == MappedEvent::Panic) {
+ for (MappedDeviceList::iterator i = m_devices.begin();
+ i != m_devices.end(); ++i) {
+ if ((*i)->getDirection() == MidiDevice::Play) {
+ sendDeviceController((*i)->getId(),
+ MIDI_CONTROLLER_SUSTAIN, 0);
+ sendDeviceController((*i)->getId(),
+ MIDI_CONTROLLER_ALL_NOTES_OFF, 0);
+ sendDeviceController((*i)->getId(),
+ MIDI_CONTROLLER_RESET, 0);
+ }
+ }
+ }
+ }
+
+ // Process Midi and Audio
+ //
+ processMidiOut(mC, sliceStart, sliceEnd);
+
+#ifdef HAVE_LIBJACK
+
+ if (m_jackDriver) {
+ if (haveNewAudio) {
+ if (now) {
+ m_jackDriver->prebufferAudio();
+ m_jackDriver->setHaveAsyncAudioEvent();
+ }
+ if (m_queueRunning) {
+ m_jackDriver->kickAudio();
+ }
+ }
+ }
+#endif
+}
+
+bool
+AlsaDriver::record(RecordStatus recordStatus,
+ const std::vector<InstrumentId> *armedInstruments,
+ const std::vector<QString> *audioFileNames)
+{
+ m_recordingInstruments.clear();
+
+ if (recordStatus == RECORD_ON) {
+ // start recording
+ m_recordStatus = RECORD_ON;
+ m_alsaRecordStartTime = RealTime::zeroTime;
+
+ unsigned int audioCount = 0;
+
+ if (armedInstruments) {
+
+ for (unsigned int i = 0; i < armedInstruments->size(); ++i) {
+
+ InstrumentId id = (*armedInstruments)[i];
+
+ m_recordingInstruments.insert(id);
+ if (!audioFileNames || (audioCount >= audioFileNames->size())) {
+ continue;
+ }
+
+ QString fileName = (*audioFileNames)[audioCount];
+
+ if (id >= AudioInstrumentBase &&
+ id < MidiInstrumentBase) {
+
+ bool good = false;
+
+#ifdef DEBUG_ALSA
+
+ std::cerr << "AlsaDriver::record: Requesting new record file \"" << fileName << "\" for instrument " << id << std::endl;
+#endif
+
+#ifdef HAVE_LIBJACK
+
+ if (m_jackDriver &&
+ m_jackDriver->openRecordFile(id, fileName.data())) {
+ good = true;
+ }
+#endif
+
+ if (!good) {
+ m_recordStatus = RECORD_OFF;
+ std::cerr << "AlsaDriver::record: No JACK driver, or JACK driver failed to prepare for recording audio" << std::endl;
+ return false;
+ }
+
+ ++audioCount;
+ }
+ }
+ }
+ } else
+ if (recordStatus == RECORD_OFF) {
+ m_recordStatus = RECORD_OFF;
+ }
+#ifdef DEBUG_ALSA
+ else {
+ std::cerr << "AlsaDriver::record - unsupported recording mode"
+ << std::endl;
+ }
+#endif
+
+ return true;
+}
+
+ClientPortPair
+AlsaDriver::getFirstDestination(bool duplex)
+{
+ ClientPortPair destPair( -1, -1);
+ AlsaPortList::iterator it;
+
+ for (it = m_alsaPorts.begin(); it != m_alsaPorts.end(); ++it) {
+ destPair.first = (*it)->m_client;
+ destPair.second = (*it)->m_port;
+
+ // If duplex port is required then choose first one
+ //
+ if (duplex) {
+ if ((*it)->m_direction == Duplex)
+ return destPair;
+ } else {
+ // If duplex port isn't required then choose first
+ // specifically non-duplex port (should be a synth)
+ //
+ if ((*it)->m_direction != Duplex)
+ return destPair;
+ }
+ }
+
+ return destPair;
+}
+
+
+// Sort through the ALSA client/port pairs for the range that
+// matches the one we're querying. If none matches then send
+// back -1 for each.
+//
+ClientPortPair
+AlsaDriver::getPairForMappedInstrument(InstrumentId id)
+{
+ MappedInstrument *instrument = getMappedInstrument(id);
+ if (instrument) {
+ DeviceId device = instrument->getDevice();
+ DevicePortMap::iterator i = m_devicePortMap.find(device);
+ if (i != m_devicePortMap.end()) {
+ return i->second;
+ }
+ }
+#ifdef DEBUG_ALSA
+ /*
+ else
+ {
+ cerr << "WARNING: AlsaDriver::getPairForMappedInstrument: couldn't find instrument for id " << id << ", falling through" << endl;
+ }
+ */
+#endif
+
+ return ClientPortPair( -1, -1);
+}
+
+int
+AlsaDriver::getOutputPortForMappedInstrument(InstrumentId id)
+{
+ MappedInstrument *instrument = getMappedInstrument(id);
+ if (instrument) {
+ DeviceId device = instrument->getDevice();
+ DeviceIntMap::iterator i = m_outputPorts.find(device);
+ if (i != m_outputPorts.end()) {
+ return i->second;
+ }
+#ifdef DEBUG_ALSA
+ else {
+ cerr << "WARNING: AlsaDriver::getOutputPortForMappedInstrument: couldn't find output port for device for instrument " << id << ", falling through" << endl;
+ }
+#endif
+
+ }
+
+ return -1;
+}
+
+// Send a direct controller to the specified port/client
+//
+void
+AlsaDriver::sendDeviceController(DeviceId device,
+ MidiByte controller,
+ MidiByte value)
+{
+ snd_seq_event_t event;
+
+ snd_seq_ev_clear(&event);
+
+ snd_seq_ev_set_subs(&event);
+
+ DeviceIntMap::iterator dimi = m_outputPorts.find(device);
+ if (dimi == m_outputPorts.end())
+ return ;
+
+ snd_seq_ev_set_source(&event, dimi->second);
+ snd_seq_ev_set_direct(&event);
+
+ for (int i = 0; i < 16; i++) {
+ snd_seq_ev_set_controller(&event,
+ i,
+ controller,
+ value);
+ snd_seq_event_output_direct(m_midiHandle, &event);
+ }
+
+ // we probably don't need this:
+ checkAlsaError(snd_seq_drain_output(m_midiHandle), "sendDeviceController(): draining");
+}
+
+void
+AlsaDriver::processPending()
+{
+ if (!m_playing) {
+ processNotesOff(getAlsaTime(), true);
+ checkAlsaError(snd_seq_drain_output(m_midiHandle), "processPending(): draining");
+ }
+
+#ifdef HAVE_LIBJACK
+ if (m_jackDriver) {
+ m_jackDriver->updateAudioData();
+ }
+#endif
+
+ scavengePlugins();
+ m_audioQueueScavenger.scavenge();
+}
+
+void
+AlsaDriver::insertMappedEventForReturn(MappedEvent *mE)
+{
+ // Insert the event ready for return at the next opportunity.
+ //
+ m_returnComposition.insert(mE);
+}
+
+// check for recording status on any ALSA Port
+//
+bool
+AlsaDriver::isRecording(AlsaPortDescription *port)
+{
+ if (port->isReadable()) {
+
+ snd_seq_query_subscribe_t *qSubs;
+ snd_seq_addr_t rg_addr, sender_addr;
+ snd_seq_query_subscribe_alloca(&qSubs);
+
+ rg_addr.client = m_client;
+ rg_addr.port = m_inputPort;
+
+ snd_seq_query_subscribe_set_type(qSubs, SND_SEQ_QUERY_SUBS_WRITE);
+ snd_seq_query_subscribe_set_index(qSubs, 0);
+ snd_seq_query_subscribe_set_root(qSubs, &rg_addr);
+
+ while (snd_seq_query_port_subscribers(m_midiHandle, qSubs) >= 0) {
+ sender_addr = *snd_seq_query_subscribe_get_addr(qSubs);
+ if (sender_addr.client == port->m_client &&
+ sender_addr.port == port->m_port)
+ return true;
+
+ snd_seq_query_subscribe_set_index(qSubs,
+ snd_seq_query_subscribe_get_index(qSubs) + 1);
+ }
+ }
+ return false;
+}
+
+bool
+AlsaDriver::checkForNewClients()
+{
+ Audit audit;
+ bool madeChange = false;
+
+ if (!m_portCheckNeeded)
+ return false;
+
+ AlsaPortList newPorts;
+ generatePortList(&newPorts); // updates m_alsaPorts, returns new ports as well
+
+ // If any devices have connections that no longer exist,
+ // clear those connections and stick them in the suspended
+ // port map in case they come back online later.
+
+ for (MappedDeviceList::iterator i = m_devices.begin();
+ i != m_devices.end(); ++i) {
+
+ ClientPortPair pair(m_devicePortMap[(*i)->getId()]);
+
+ bool found = false;
+ for (AlsaPortList::iterator j = m_alsaPorts.begin();
+ j != m_alsaPorts.end(); ++j) {
+ if ((*j)->m_client == pair.first &&
+ (*j)->m_port == pair.second) {
+ if ((*i)->getDirection() == MidiDevice::Record) {
+ bool recState = isRecording(*j);
+ if (recState != (*i)->isRecording()) {
+ madeChange = true;
+ (*i)->setRecording(recState);
+ }
+ } else {
+ (*i)->setRecording(false);
+ }
+ found = true;
+ break;
+ }
+ }
+
+ if (!found) {
+ m_suspendedPortMap[pair] = (*i)->getId();
+ m_devicePortMap[(*i)->getId()] = ClientPortPair( -1, -1);
+ setConnectionToDevice(**i, "");
+ (*i)->setRecording(false);
+ madeChange = true;
+ }
+ }
+
+ // If we've increased the number of connections, we need
+ // to assign the new connections to existing devices that
+ // have none, where possible, and create new devices for
+ // any left over.
+
+ if (newPorts.size() > 0) {
+
+ audit << "New ports:" << std::endl;
+
+ for (AlsaPortList::iterator i = newPorts.begin();
+ i != newPorts.end(); ++i) {
+
+ if ((*i)->m_client == m_client) {
+ audit << "(Ignoring own port " << (*i)->m_client << ":" << (*i)->m_port << ")" << std::endl;
+ continue;
+ } else if ((*i)->m_client == 0) {
+ audit << "(Ignoring system port " << (*i)->m_client << ":" << (*i)->m_port << ")" << std::endl;
+ continue;
+ }
+
+ audit << (*i)->m_name << std::endl;
+
+ QString portName = (*i)->m_name.c_str();
+ ClientPortPair portPair = ClientPortPair((*i)->m_client,
+ (*i)->m_port);
+
+ if (m_suspendedPortMap.find(portPair) != m_suspendedPortMap.end()) {
+
+ DeviceId id = m_suspendedPortMap[portPair];
+
+ audit << "(Reusing suspended device " << id << ")" << std::endl;
+
+ for (MappedDeviceList::iterator j = m_devices.begin();
+ j != m_devices.end(); ++j) {
+ if ((*j)->getId() == id) {
+ setConnectionToDevice(**j, portName);
+ }
+ }
+
+ m_suspendedPortMap.erase(m_suspendedPortMap.find(portPair));
+ m_devicePortMap[id] = portPair;
+ madeChange = true;
+ continue;
+ }
+
+ bool needPlayDevice = true, needRecordDevice = true;
+
+ if ((*i)->isReadable()) {
+ for (MappedDeviceList::iterator j = m_devices.begin();
+ j != m_devices.end(); ++j) {
+ if ((*j)->getType() == Device::Midi &&
+ (*j)->getConnection() == "" &&
+ (*j)->getDirection() == MidiDevice::Record) {
+ audit << "(Reusing record device " << (*j)->getId()
+ << ")" << std::endl;
+ m_devicePortMap[(*j)->getId()] = portPair;
+ setConnectionToDevice(**j, portName);
+ needRecordDevice = false;
+ madeChange = true;
+ break;
+ }
+ }
+ } else {
+ needRecordDevice = false;
+ }
+
+ if ((*i)->isWriteable()) {
+ for (MappedDeviceList::iterator j = m_devices.begin();
+ j != m_devices.end(); ++j) {
+ if ((*j)->getType() == Device::Midi &&
+ (*j)->getConnection() == "" &&
+ (*j)->getDirection() == MidiDevice::Play) {
+ audit << "(Reusing play device " << (*j)->getId()
+ << ")" << std::endl;
+ m_devicePortMap[(*j)->getId()] = portPair;
+ setConnectionToDevice(**j, portName);
+ needPlayDevice = false;
+ madeChange = true;
+ break;
+ }
+ }
+ } else {
+ needPlayDevice = false;
+ }
+
+ if (needRecordDevice) {
+ MappedDevice *device = createMidiDevice(*i, MidiDevice::Record);
+ if (!device) {
+#ifdef DEBUG_ALSA
+ std::cerr << "WARNING: Failed to create record device" << std::endl;
+#else
+
+ ;
+#endif
+
+ } else {
+ audit << "(Created new record device " << device->getId() << ")" << std::endl;
+ addInstrumentsForDevice(device);
+ m_devices.push_back(device);
+ madeChange = true;
+ }
+ }
+
+ if (needPlayDevice) {
+ MappedDevice *device = createMidiDevice(*i, MidiDevice::Play);
+ if (!device) {
+#ifdef DEBUG_ALSA
+ std::cerr << "WARNING: Failed to create play device" << std::endl;
+#else
+
+ ;
+#endif
+
+ } else {
+ audit << "(Created new play device " << device->getId() << ")" << std::endl;
+ addInstrumentsForDevice(device);
+ m_devices.push_back(device);
+ madeChange = true;
+ }
+ }
+ }
+ }
+
+ // If one of our ports is connected to a single other port and
+ // it isn't the one we thought, we should update our connection
+
+ for (MappedDeviceList::iterator i = m_devices.begin();
+ i != m_devices.end(); ++i) {
+
+ DevicePortMap::iterator j = m_devicePortMap.find((*i)->getId());
+
+ snd_seq_addr_t addr;
+ addr.client = m_client;
+
+ DeviceIntMap::iterator ii = m_outputPorts.find((*i)->getId());
+ if (ii == m_outputPorts.end())
+ continue;
+ addr.port = ii->second;
+
+ snd_seq_query_subscribe_t *subs;
+ snd_seq_query_subscribe_alloca(&subs);
+ snd_seq_query_subscribe_set_root(subs, &addr);
+ snd_seq_query_subscribe_set_index(subs, 0);
+
+ bool haveOurs = false;
+ int others = 0;
+ ClientPortPair firstOther;
+
+ while (!snd_seq_query_port_subscribers(m_midiHandle, subs)) {
+
+ const snd_seq_addr_t *otherEnd =
+ snd_seq_query_subscribe_get_addr(subs);
+
+ if (!otherEnd)
+ continue;
+
+ if (j != m_devicePortMap.end() &&
+ otherEnd->client == j->second.first &&
+ otherEnd->port == j->second.second) {
+ haveOurs = true;
+ } else {
+ ++others;
+ firstOther = ClientPortPair(otherEnd->client, otherEnd->port);
+ }
+
+ snd_seq_query_subscribe_set_index
+ (subs, snd_seq_query_subscribe_get_index(subs) + 1);
+ }
+
+ if (haveOurs) { // leave our own connection alone, and stop worrying
+ continue;
+
+ } else {
+ if (others == 0) {
+ if (j != m_devicePortMap.end()) {
+ j->second = ClientPortPair( -1, -1);
+ setConnectionToDevice(**i, "");
+ madeChange = true;
+ }
+ } else {
+ for (AlsaPortList::iterator k = m_alsaPorts.begin();
+ k != m_alsaPorts.end(); ++k) {
+ if ((*k)->m_client == firstOther.first &&
+ (*k)->m_port == firstOther.second) {
+ m_devicePortMap[(*i)->getId()] = firstOther;
+ setConnectionToDevice(**i, (*k)->m_name.c_str(),
+ firstOther);
+ madeChange = true;
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ if (madeChange) {
+ MappedEvent *mE =
+ new MappedEvent(0, MappedEvent::SystemUpdateInstruments,
+ 0, 0);
+ // send completion event
+ insertMappedEventForReturn(mE);
+ }
+
+ m_portCheckNeeded = false;
+
+ return true;
+}
+
+
+// From a DeviceId get a client/port pair for connecting as the
+// MIDI record device.
+//
+void
+AlsaDriver::setRecordDevice(DeviceId id, bool connectAction)
+{
+ Audit audit;
+
+ // Locate a suitable port
+ //
+ if (m_devicePortMap.find(id) == m_devicePortMap.end()) {
+#ifdef DEBUG_ALSA
+ audit << "AlsaDriver::setRecordDevice - "
+ << "couldn't match device id (" << id << ") to ALSA port"
+ << std::endl;
+#endif
+
+ return ;
+ }
+
+ ClientPortPair pair = m_devicePortMap[id];
+
+ snd_seq_addr_t sender, dest;
+ sender.client = pair.first;
+ sender.port = pair.second;
+
+ for (MappedDeviceList::iterator i = m_devices.begin();
+ i != m_devices.end(); ++i) {
+ if ((*i)->getId() == id) {
+ if ((*i)->getDirection() == MidiDevice::Record) {
+ if ((*i)->isRecording() && connectAction) {
+#ifdef DEBUG_ALSA
+ audit << "AlsaDriver::setRecordDevice - "
+ << "attempting to subscribe (" << id
+ << ") already subscribed" << std::endl;
+#endif
+
+ return ;
+ }
+ if (!(*i)->isRecording() && !connectAction) {
+#ifdef DEBUG_ALSA
+ audit << "AlsaDriver::setRecordDevice - "
+ << "attempting to unsubscribe (" << id
+ << ") already unsubscribed" << std::endl;
+#endif
+
+ return ;
+ }
+ } else {
+#ifdef DEBUG_ALSA
+ audit << "AlsaDriver::setRecordDevice - "
+ << "attempting to set play device (" << id
+ << ") to record device" << std::endl;
+#endif
+
+ return ;
+ }
+ break;
+ }
+ }
+
+ snd_seq_port_subscribe_t *subs;
+ snd_seq_port_subscribe_alloca(&subs);
+
+ dest.client = m_client;
+ dest.port = m_inputPort;
+
+ // Set destinations and senders
+ //
+ snd_seq_port_subscribe_set_sender(subs, &sender);
+ snd_seq_port_subscribe_set_dest(subs, &dest);
+
+ // subscribe or unsubscribe the port
+ //
+ if (connectAction) {
+ if (checkAlsaError(snd_seq_subscribe_port(m_midiHandle, subs),
+ "setRecordDevice - failed subscription of input port") < 0) {
+ // Not the end of the world if this fails but we
+ // have to flag it internally.
+ //
+ audit << "AlsaDriver::setRecordDevice - "
+ << int(sender.client) << ":" << int(sender.port)
+ << " failed to subscribe device "
+ << id << " as record port" << std::endl;
+ } else {
+ m_midiInputPortConnected = true;
+ audit << "AlsaDriver::setRecordDevice - "
+ << "successfully subscribed device "
+ << id << " as record port" << std::endl;
+ }
+ } else {
+ if (checkAlsaError(snd_seq_unsubscribe_port(m_midiHandle, subs),
+ "setRecordDevice - failed to unsubscribe a device") == 0)
+ audit << "AlsaDriver::setRecordDevice - "
+ << "successfully unsubscribed device "
+ << id << " as record port" << std::endl;
+
+ }
+}
+
+// Clear any record device connections
+//
+void
+AlsaDriver::unsetRecordDevices()
+{
+ snd_seq_addr_t dest;
+ dest.client = m_client;
+ dest.port = m_inputPort;
+
+ snd_seq_query_subscribe_t *qSubs;
+ snd_seq_addr_t tmp_addr;
+ snd_seq_query_subscribe_alloca(&qSubs);
+
+ tmp_addr.client = m_client;
+ tmp_addr.port = m_inputPort;
+
+ // Unsubsribe any existing connections
+ //
+ snd_seq_query_subscribe_set_type(qSubs, SND_SEQ_QUERY_SUBS_WRITE);
+ snd_seq_query_subscribe_set_index(qSubs, 0);
+ snd_seq_query_subscribe_set_root(qSubs, &tmp_addr);
+
+ while (snd_seq_query_port_subscribers(m_midiHandle, qSubs) >= 0) {
+ tmp_addr = *snd_seq_query_subscribe_get_addr(qSubs);
+
+ snd_seq_port_subscribe_t *dSubs;
+ snd_seq_port_subscribe_alloca(&dSubs);
+
+ snd_seq_addr_t dSender;
+ dSender.client = tmp_addr.client;
+ dSender.port = tmp_addr.port;
+
+ snd_seq_port_subscribe_set_sender(dSubs, &dSender);
+ snd_seq_port_subscribe_set_dest(dSubs, &dest);
+
+ int error = snd_seq_unsubscribe_port(m_midiHandle, dSubs);
+
+ if (error < 0) {
+#ifdef DEBUG_ALSA
+ std::cerr << "AlsaDriver::unsetRecordDevices - "
+ << "can't unsubscribe record port" << std::endl;
+#endif
+
+ }
+
+ snd_seq_query_subscribe_set_index(qSubs,
+ snd_seq_query_subscribe_get_index(qSubs) + 1);
+ }
+}
+
+void
+AlsaDriver::sendMMC(MidiByte deviceArg,
+ MidiByte instruction,
+ bool isCommand,
+ const std::string &data)
+{
+ snd_seq_event_t event;
+
+ snd_seq_ev_clear(&event);
+ snd_seq_ev_set_source(&event, m_syncOutputPort);
+ snd_seq_ev_set_subs(&event);
+
+ unsigned char dataArr[10] =
+ { MIDI_SYSTEM_EXCLUSIVE,
+ MIDI_SYSEX_RT, deviceArg,
+ (isCommand ? MIDI_SYSEX_RT_COMMAND : MIDI_SYSEX_RT_RESPONSE),
+ instruction };
+
+ std::string dataString = std::string((const char *)dataArr) +
+ data + (char)MIDI_END_OF_EXCLUSIVE;
+
+ snd_seq_ev_set_sysex(&event, dataString.length(),
+ (char *)dataString.c_str());
+
+ event.queue = SND_SEQ_QUEUE_DIRECT;
+
+ checkAlsaError(snd_seq_event_output_direct(m_midiHandle, &event),
+ "sendMMC event send");
+
+ if (m_queueRunning) {
+ checkAlsaError(snd_seq_drain_output(m_midiHandle), "sendMMC drain");
+ }
+}
+
+// Send a system real-time message from the sync output port
+//
+void
+AlsaDriver::sendSystemDirect(MidiByte command, int *args)
+{
+ snd_seq_event_t event;
+
+ snd_seq_ev_clear(&event);
+ snd_seq_ev_set_source(&event, m_syncOutputPort);
+ snd_seq_ev_set_subs(&event);
+
+ event.queue = SND_SEQ_QUEUE_DIRECT;
+
+ // set the command
+ event.type = command;
+
+ // set args if we have them
+ if (args) {
+ event.data.control.value = *args;
+ }
+
+ int error = snd_seq_event_output_direct(m_midiHandle, &event);
+
+ if (error < 0) {
+#ifdef DEBUG_ALSA
+ std::cerr << "AlsaDriver::sendSystemDirect - "
+ << "can't send event (" << int(command) << ")"
+ << std::endl;
+#endif
+
+ }
+
+ // checkAlsaError(snd_seq_drain_output(m_midiHandle),
+ // "sendSystemDirect(): draining");
+}
+
+
+void
+AlsaDriver::sendSystemQueued(MidiByte command,
+ const std::string &args,
+ const RealTime &time)
+{
+ snd_seq_event_t event;
+
+ snd_seq_ev_clear(&event);
+ snd_seq_ev_set_source(&event, m_syncOutputPort);
+ snd_seq_ev_set_subs(&event);
+
+ snd_seq_real_time_t sendTime = { time.sec, time.nsec };
+
+ // Schedule the command
+ //
+ event.type = command;
+
+ snd_seq_ev_schedule_real(&event, m_queue, 0, &sendTime);
+
+ // set args if we have them
+ switch (args.length()) {
+ case 1:
+ event.data.control.value = args[0];
+ break;
+
+ case 2:
+ event.data.control.value = int(args[0]) | (int(args[1]) << 7);
+ break;
+
+ default: // do nothing
+ break;
+ }
+
+ int error = snd_seq_event_output(m_midiHandle, &event);
+
+ if (error < 0) {
+#ifdef DEBUG_ALSA
+ std::cerr << "AlsaDriver::sendSystemQueued - "
+ << "can't send event (" << int(command) << ")"
+ << " - error = (" << error << ")"
+ << std::endl;
+#endif
+
+ }
+
+ // if (m_queueRunning) {
+ // checkAlsaError(snd_seq_drain_output(m_midiHandle), "sendSystemQueued(): draining");
+ // }
+}
+
+
+void
+AlsaDriver::claimUnwantedPlugin(void *plugin)
+{
+ m_pluginScavenger.claim((RunnablePluginInstance *)plugin);
+}
+
+
+void
+AlsaDriver::scavengePlugins()
+{
+ m_pluginScavenger.scavenge();
+}
+
+
+QString
+AlsaDriver::getStatusLog()
+{
+ return QString::fromUtf8(Audit::getAudit().c_str());
+}
+
+
+void
+AlsaDriver::sleep(const RealTime &rt)
+{
+ int npfd = snd_seq_poll_descriptors_count(m_midiHandle, POLLIN);
+ struct pollfd *pfd = (struct pollfd *)alloca(npfd * sizeof(struct pollfd));
+ snd_seq_poll_descriptors(m_midiHandle, pfd, npfd, POLLIN);
+ poll(pfd, npfd, rt.sec * 1000 + rt.msec());
+}
+
+void
+AlsaDriver::runTasks()
+{
+#ifdef HAVE_LIBJACK
+ if (m_jackDriver) {
+ if (!m_jackDriver->isOK()) {
+ m_jackDriver->restoreIfRestorable();
+ }
+ }
+
+ if (m_doTimerChecks && m_timerRatioCalculated) {
+
+ double ratio = m_timerRatio;
+ m_timerRatioCalculated = false;
+
+ snd_seq_queue_tempo_t *q_ptr;
+ snd_seq_queue_tempo_alloca(&q_ptr);
+
+ snd_seq_get_queue_tempo(m_midiHandle, m_queue, q_ptr);
+
+ unsigned int t_skew = snd_seq_queue_tempo_get_skew(q_ptr);
+#ifdef DEBUG_ALSA
+
+ unsigned int t_base = snd_seq_queue_tempo_get_skew_base(q_ptr);
+ if (!m_playing) {
+ std::cerr << "Skew: " << t_skew << "/" << t_base;
+ }
+#endif
+
+ unsigned int newSkew = t_skew + (unsigned int)(t_skew * ratio);
+
+ if (newSkew != t_skew) {
+#ifdef DEBUG_ALSA
+ if (!m_playing) {
+ std::cerr << " changed to " << newSkew << endl;
+ }
+#endif
+ snd_seq_queue_tempo_set_skew(q_ptr, newSkew);
+ snd_seq_set_queue_tempo( m_midiHandle, m_queue, q_ptr);
+ } else {
+#ifdef DEBUG_ALSA
+ if (!m_playing) {
+ std::cerr << endl;
+ }
+#endif
+
+ }
+
+ m_firstTimerCheck = true;
+ }
+
+#endif
+}
+
+void
+AlsaDriver::reportFailure(MappedEvent::FailureCode code)
+{
+ //#define REPORT_XRUNS 1
+#ifndef REPORT_XRUNS
+ if (code == MappedEvent::FailureXRuns ||
+ code == MappedEvent::FailureDiscUnderrun ||
+ code == MappedEvent::FailureBussMixUnderrun ||
+ code == MappedEvent::FailureMixUnderrun) {
+ return ;
+ }
+#endif
+
+ // Ignore consecutive duplicates
+ if (_failureReportWriteIndex > 0 &&
+ _failureReportWriteIndex != _failureReportReadIndex) {
+ if (code == _failureReports[_failureReportWriteIndex - 1])
+ return ;
+ }
+
+ _failureReports[_failureReportWriteIndex] = code;
+ _failureReportWriteIndex =
+ (_failureReportWriteIndex + 1) % FAILURE_REPORT_COUNT;
+}
+
+std::string
+AlsaDriver::getAlsaModuleVersionString()
+{
+ FILE *v = fopen("/proc/asound/version", "r");
+
+ // Examples:
+ // Advanced Linux Sound Architecture Driver Version 1.0.14rc3.
+ // Advanced Linux Sound Architecture Driver Version 1.0.14 (Thu May 31 09:03:25 2008 UTC).
+
+ if (v) {
+ char buf[256];
+ fgets(buf, 256, v);
+ fclose(v);
+
+ std::string vs(buf);
+ std::string::size_type sp = vs.find_first_of('.');
+ if (sp > 0 && sp != std::string::npos) {
+ while (sp > 0 && isdigit(vs[sp-1])) --sp;
+ vs = vs.substr(sp);
+ if (vs.length() > 0 && vs[vs.length()-1] == '\n') {
+ vs = vs.substr(0, vs.length()-1);
+ }
+ if (vs.length() > 0 && vs[vs.length()-1] == '.') {
+ vs = vs.substr(0, vs.length()-1);
+ }
+ return vs;
+ }
+ }
+
+ return "(unknown)";
+}
+
+std::string
+AlsaDriver::getKernelVersionString()
+{
+ FILE *v = fopen("/proc/version", "r");
+
+ if (v) {
+ char buf[256];
+ fgets(buf, 256, v);
+ fclose(v);
+
+ std::string vs(buf);
+ std::string key(" version ");
+ std::string::size_type sp = vs.find(key);
+ if (sp != std::string::npos) {
+ vs = vs.substr(sp + key.length());
+ sp = vs.find(' ');
+ if (sp != std::string::npos) {
+ vs = vs.substr(0, sp);
+ }
+ if (vs.length() > 0 && vs[vs.length()-1] == '\n') {
+ vs = vs.substr(0, vs.length()-1);
+ }
+ return vs;
+ }
+ }
+
+ return "(unknown)";
+}
+
+void
+AlsaDriver::extractVersion(std::string v, int &major, int &minor, int &subminor, std::string &suffix)
+{
+ major = minor = subminor = 0;
+ suffix = "";
+ if (v == "(unknown)") return;
+
+ std::string::size_type sp, pp;
+
+ sp = v.find('.');
+ if (sp == std::string::npos) goto done;
+ major = atoi(v.substr(0, sp).c_str());
+ pp = sp + 1;
+
+ sp = v.find('.', pp);
+ if (sp == std::string::npos) goto done;
+ minor = atoi(v.substr(pp, sp - pp).c_str());
+ pp = sp + 1;
+
+ while (++sp < v.length() && (::isdigit(v[sp]) || v[sp] == '-'));
+ subminor = atoi(v.substr(pp, sp - pp).c_str());
+
+ if (sp >= v.length()) goto done;
+ suffix = v.substr(sp);
+
+done:
+ std::cerr << "extractVersion: major = " << major << ", minor = " << minor << ", subminor = " << subminor << ", suffix = \"" << suffix << "\"" << std::endl;
+}
+
+bool
+AlsaDriver::versionIsAtLeast(std::string v, int major, int minor, int subminor)
+{
+ int actualMajor, actualMinor, actualSubminor;
+ std::string actualSuffix;
+
+ extractVersion(v, actualMajor, actualMinor, actualSubminor, actualSuffix);
+
+ bool ok = false;
+
+ if (actualMajor > major) {
+ ok = true;
+ } else if (actualMajor == major) {
+ if (actualMinor > minor) {
+ ok = true;
+ } else if (actualMinor == minor) {
+ if (actualSubminor > subminor) {
+ ok = true;
+ } else if (actualSubminor == subminor) {
+ if (strncmp(actualSuffix.c_str(), "rc", 2) &&
+ strncmp(actualSuffix.c_str(), "pre", 3)) {
+ ok = true;
+ }
+ }
+ }
+ }
+
+ std::cerr << "AlsaDriver::versionIsAtLeast: is version " << v << " at least " << major << "." << minor << "." << subminor << "? " << (ok ? "yes" : "no") << std::endl;
+ return ok;
+}
+
+}
+
+
+#endif // HAVE_ALSA
diff --git a/src/sound/AlsaDriver.h b/src/sound/AlsaDriver.h
new file mode 100644
index 0000000..e80e30f
--- /dev/null
+++ b/src/sound/AlsaDriver.h
@@ -0,0 +1,561 @@
+// -*- c-indentation-style:"stroustrup" c-basic-offset: 4 -*-
+
+/*
+ Rosegarden
+ A sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <bownie@bownie.com>
+
+ 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.
+*/
+
+// Specialisation of SoundDriver to support ALSA (http://www.alsa-project.org)
+//
+//
+#ifndef _ALSADRIVER_H_
+#define _ALSADRIVER_H_
+
+#include <vector>
+#include <set>
+#include <map>
+
+#ifdef HAVE_ALSA
+
+#include <alsa/asoundlib.h> // ALSA
+
+#include "SoundDriver.h"
+#include "Instrument.h"
+#include "Device.h"
+#include "AlsaPort.h"
+#include "Scavenger.h"
+#include "RunnablePluginInstance.h"
+
+#ifdef HAVE_LIBJACK
+#include "JackDriver.h"
+#endif
+
+namespace Rosegarden
+{
+
+class AlsaDriver : public SoundDriver
+{
+public:
+ AlsaDriver(MappedStudio *studio);
+ virtual ~AlsaDriver();
+
+ // shutdown everything that's currently open
+ void shutdown();
+
+ virtual bool initialise();
+ virtual void initialisePlayback(const RealTime &position);
+ virtual void stopPlayback();
+ virtual void punchOut();
+ virtual void resetPlayback(const RealTime &oldPosition, const RealTime &position);
+ virtual void allNotesOff();
+ virtual void processNotesOff(const RealTime &time, bool now, bool everything = false);
+
+ virtual RealTime getSequencerTime();
+
+ virtual MappedComposition *getMappedComposition();
+
+ virtual bool record(RecordStatus recordStatus,
+ const std::vector<InstrumentId> *armedInstruments = 0,
+ const std::vector<QString> *audioFileNames = 0);
+
+ virtual void startClocks();
+ virtual void startClocksApproved(); // called by JACK driver in sync mode
+ virtual void stopClocks();
+ virtual bool areClocksRunning() const { return m_queueRunning; }
+
+ virtual void processEventsOut(const MappedComposition &mC);
+ virtual void processEventsOut(const MappedComposition &mC,
+ const RealTime &sliceStart,
+ const RealTime &sliceEnd);
+
+ // Return the sample rate
+ //
+ virtual unsigned int getSampleRate() const {
+#ifdef HAVE_LIBJACK
+ if (m_jackDriver) return m_jackDriver->getSampleRate();
+ else return 0;
+#else
+ return 0;
+#endif
+ }
+
+ // Define here to catch this being reset
+ //
+ virtual void setMIDIClockInterval(RealTime interval);
+
+ // initialise subsystems
+ //
+ bool initialiseMidi();
+ void initialiseAudio();
+
+ // Some stuff to help us debug this
+ //
+ void getSystemInfo();
+ void showQueueStatus(int queue);
+
+ // Process pending
+ //
+ virtual void processPending();
+
+ // We can return audio control signals to the gui using MappedEvents.
+ // Meter levels or audio file completions can go in here.
+ //
+ void insertMappedEventForReturn(MappedEvent *mE);
+
+
+ virtual RealTime getAudioPlayLatency() {
+#ifdef HAVE_LIBJACK
+ if (m_jackDriver) return m_jackDriver->getAudioPlayLatency();
+#endif
+ return RealTime::zeroTime;
+ }
+
+ virtual RealTime getAudioRecordLatency() {
+#ifdef HAVE_LIBJACK
+ if (m_jackDriver) return m_jackDriver->getAudioRecordLatency();
+#endif
+ return RealTime::zeroTime;
+ }
+
+ virtual RealTime getInstrumentPlayLatency(InstrumentId id) {
+#ifdef HAVE_LIBJACK
+ if (m_jackDriver) return m_jackDriver->getInstrumentPlayLatency(id);
+#endif
+ return RealTime::zeroTime;
+ }
+
+ virtual RealTime getMaximumPlayLatency() {
+#ifdef HAVE_LIBJACK
+ if (m_jackDriver) return m_jackDriver->getMaximumPlayLatency();
+#endif
+ return RealTime::zeroTime;
+ }
+
+
+ // Plugin instance management
+ //
+ virtual void setPluginInstance(InstrumentId id,
+ QString identifier,
+ int position) {
+#ifdef HAVE_LIBJACK
+ if (m_jackDriver) m_jackDriver->setPluginInstance(id, identifier, position);
+#endif
+ }
+
+ virtual void removePluginInstance(InstrumentId id, int position) {
+#ifdef HAVE_LIBJACK
+ if (m_jackDriver) m_jackDriver->removePluginInstance(id, position);
+#endif
+ }
+
+ // Remove all plugin instances
+ //
+ virtual void removePluginInstances() {
+#ifdef HAVE_LIBJACK
+ if (m_jackDriver) m_jackDriver->removePluginInstances();
+#endif
+ }
+
+ virtual void setPluginInstancePortValue(InstrumentId id,
+ int position,
+ unsigned long portNumber,
+ float value) {
+#ifdef HAVE_LIBJACK
+ if (m_jackDriver) m_jackDriver->setPluginInstancePortValue(id, position, portNumber, value);
+#endif
+ }
+
+ virtual float getPluginInstancePortValue(InstrumentId id,
+ int position,
+ unsigned long portNumber) {
+#ifdef HAVE_LIBJACK
+ if (m_jackDriver) return m_jackDriver->getPluginInstancePortValue(id, position, portNumber);
+#endif
+ return 0;
+ }
+
+ virtual void setPluginInstanceBypass(InstrumentId id,
+ int position,
+ bool value) {
+#ifdef HAVE_LIBJACK
+ if (m_jackDriver) m_jackDriver->setPluginInstanceBypass(id, position, value);
+#endif
+ }
+
+ virtual QStringList getPluginInstancePrograms(InstrumentId id,
+ int position) {
+#ifdef HAVE_LIBJACK
+ if (m_jackDriver) return m_jackDriver->getPluginInstancePrograms(id, position);
+#endif
+ return QStringList();
+ }
+
+ virtual QString getPluginInstanceProgram(InstrumentId id,
+ int position) {
+#ifdef HAVE_LIBJACK
+ if (m_jackDriver) return m_jackDriver->getPluginInstanceProgram(id, position);
+#endif
+ return QString();
+ }
+
+ virtual QString getPluginInstanceProgram(InstrumentId id,
+ int position,
+ int bank,
+ int program) {
+#ifdef HAVE_LIBJACK
+ if (m_jackDriver) return m_jackDriver->getPluginInstanceProgram(id, position, bank, program);
+#endif
+ return QString();
+ }
+
+ virtual unsigned long getPluginInstanceProgram(InstrumentId id,
+ int position,
+ QString name) {
+#ifdef HAVE_LIBJACK
+ if (m_jackDriver) return m_jackDriver->getPluginInstanceProgram(id, position, name);
+#endif
+ return 0;
+ }
+
+ virtual void setPluginInstanceProgram(InstrumentId id,
+ int position,
+ QString program) {
+#ifdef HAVE_LIBJACK
+ if (m_jackDriver) m_jackDriver->setPluginInstanceProgram(id, position, program);
+#endif
+ }
+
+ virtual QString configurePlugin(InstrumentId id,
+ int position,
+ QString key,
+ QString value) {
+#ifdef HAVE_LIBJACK
+ if (m_jackDriver) return m_jackDriver->configurePlugin(id, position, key, value);
+#endif
+ return QString();
+ }
+
+ virtual void setAudioBussLevels(int bussId,
+ float dB,
+ float pan) {
+#ifdef HAVE_LIBJACK
+ if (m_jackDriver) m_jackDriver->setAudioBussLevels(bussId, dB, pan);
+#endif
+ }
+
+ virtual void setAudioInstrumentLevels(InstrumentId instrument,
+ float dB,
+ float pan) {
+#ifdef HAVE_LIBJACK
+ if (m_jackDriver) m_jackDriver->setAudioInstrumentLevels(instrument, dB, pan);
+#endif
+ }
+
+ virtual void claimUnwantedPlugin(void *plugin);
+ virtual void scavengePlugins();
+
+ virtual bool checkForNewClients();
+
+ virtual void setLoop(const RealTime &loopStart, const RealTime &loopEnd);
+
+ virtual void sleep(const RealTime &);
+
+ // ----------------------- End of Virtuals ----------------------
+
+ // Create and send an MMC command
+ //
+ void sendMMC(MidiByte deviceId,
+ MidiByte instruction,
+ bool isCommand,
+ const std::string &data);
+
+ // Check whether the given event is an MMC command we need to act on
+ // (and if so act on it)
+ //
+ bool testForMMCSysex(const snd_seq_event_t *event);
+
+ // Create and enqueue a batch of MTC quarter-frame events
+ //
+ void insertMTCQFrames(RealTime sliceStart, RealTime sliceEnd);
+
+ // Create and enqueue an MTC full-frame system exclusive event
+ //
+ void insertMTCFullFrame(RealTime time);
+
+ // Parse and accept an incoming MTC quarter-frame event
+ //
+ void handleMTCQFrame(unsigned int data_byte, RealTime the_time);
+
+ // Check whether the given event is an MTC sysex we need to act on
+ // (and if so act on it)
+ //
+ bool testForMTCSysex(const snd_seq_event_t *event);
+
+ // Adjust the ALSA clock skew for MTC lock
+ //
+ void tweakSkewForMTC(int factor);
+
+ // Recalibrate internal MTC factors
+ //
+ void calibrateMTC();
+
+ // Send a System message straight away
+ //
+ void sendSystemDirect(MidiByte command, int *arg);
+
+ // Scheduled system message with arguments
+ //
+ void sendSystemQueued(MidiByte command,
+ const std::string &args,
+ const RealTime &time);
+
+ // Set the record device
+ //
+ void setRecordDevice(DeviceId id, bool connectAction);
+ void unsetRecordDevices();
+
+ virtual bool canReconnect(Device::DeviceType type);
+
+ virtual DeviceId addDevice(Device::DeviceType type,
+ MidiDevice::DeviceDirection direction);
+ virtual void removeDevice(DeviceId id);
+ virtual void renameDevice(DeviceId id, QString name);
+
+ // Get available connections per device
+ //
+ virtual unsigned int getConnections(Device::DeviceType type,
+ MidiDevice::DeviceDirection direction);
+ virtual QString getConnection(Device::DeviceType type,
+ MidiDevice::DeviceDirection direction,
+ unsigned int connectionNo);
+ virtual void setConnection(DeviceId deviceId, QString connection);
+ virtual void setPlausibleConnection(DeviceId deviceId, QString connection);
+
+ virtual unsigned int getTimers();
+ virtual QString getTimer(unsigned int);
+ virtual QString getCurrentTimer();
+ virtual void setCurrentTimer(QString);
+
+ virtual void getAudioInstrumentNumbers(InstrumentId &audioInstrumentBase,
+ int &audioInstrumentCount) {
+ audioInstrumentBase = AudioInstrumentBase;
+#ifdef HAVE_LIBJACK
+ audioInstrumentCount = AudioInstrumentCount;
+#else
+ audioInstrumentCount = 0;
+#endif
+ }
+
+ virtual void getSoftSynthInstrumentNumbers(InstrumentId &ssInstrumentBase,
+ int &ssInstrumentCount) {
+ ssInstrumentBase = SoftSynthInstrumentBase;
+#ifdef HAVE_DSSI
+ ssInstrumentCount = SoftSynthInstrumentCount;
+#else
+ ssInstrumentCount = 0;
+#endif
+ }
+
+ virtual QString getStatusLog();
+
+ // To be called regularly from JACK driver when idle
+ void checkTimerSync(size_t frames);
+
+ virtual void runTasks();
+
+ // Report a failure back to the GUI
+ //
+ virtual void reportFailure(MappedEvent::FailureCode code);
+
+protected:
+ typedef std::vector<AlsaPortDescription *> AlsaPortList;
+
+ ClientPortPair getFirstDestination(bool duplex);
+ ClientPortPair getPairForMappedInstrument(InstrumentId id);
+ int getOutputPortForMappedInstrument(InstrumentId id);
+ std::map<unsigned int, std::map<unsigned int, MappedEvent*> > m_noteOnMap;
+
+ /**
+ * Bring m_alsaPorts up-to-date; if newPorts is non-null, also
+ * return the new ports (not previously in m_alsaPorts) through it
+ */
+ virtual void generatePortList(AlsaPortList *newPorts = 0);
+ virtual void generateInstruments();
+
+ virtual void generateTimerList();
+ virtual std::string getAutoTimer(bool &wantTimerChecks);
+
+ void addInstrumentsForDevice(MappedDevice *device);
+ MappedDevice *createMidiDevice(AlsaPortDescription *,
+ MidiDevice::DeviceDirection);
+
+ virtual void processMidiOut(const MappedComposition &mC,
+ const RealTime &sliceStart,
+ const RealTime &sliceEnd);
+
+ virtual void processSoftSynthEventOut(InstrumentId id,
+ const snd_seq_event_t *event,
+ bool now);
+
+ virtual bool isRecording(AlsaPortDescription *port);
+
+ virtual void processAudioQueue(bool /* now */) { }
+
+ virtual void setConnectionToDevice(MappedDevice &device, QString connection);
+ virtual void setConnectionToDevice(MappedDevice &device, QString connection,
+ const ClientPortPair &pair);
+
+private:
+ RealTime getAlsaTime();
+
+ // Locally convenient to control our devices
+ //
+ void sendDeviceController(DeviceId device,
+ MidiByte byte1,
+ MidiByte byte2);
+
+ int checkAlsaError(int rc, const char *message);
+
+ AlsaPortList m_alsaPorts;
+
+ // ALSA MIDI/Sequencer stuff
+ //
+ snd_seq_t *m_midiHandle;
+ int m_client;
+
+ int m_inputPort;
+
+ typedef std::map<DeviceId, int> DeviceIntMap;
+ DeviceIntMap m_outputPorts;
+
+ int m_syncOutputPort;
+ int m_controllerPort;
+
+ int m_queue;
+ int m_maxClients;
+ int m_maxPorts;
+ int m_maxQueues;
+
+ // Because this can fail even if the driver's up (if
+ // another service is using the port say)
+ //
+ bool m_midiInputPortConnected;
+
+ bool m_midiSyncAutoConnect;
+
+ RealTime m_alsaPlayStartTime;
+ RealTime m_alsaRecordStartTime;
+
+ RealTime m_loopStartTime;
+ RealTime m_loopEndTime;
+
+ // MIDI Time Code handling:
+
+ unsigned int m_eat_mtc;
+ // Received/emitted MTC data breakdown:
+ RealTime m_mtcReceiveTime;
+ RealTime m_mtcEncodedTime;
+ int m_mtcFrames;
+ int m_mtcSeconds;
+ int m_mtcMinutes;
+ int m_mtcHours;
+ int m_mtcSMPTEType;
+
+ // Calculated MTC factors:
+ int m_mtcFirstTime;
+ RealTime m_mtcLastEncoded;
+ RealTime m_mtcLastReceive;
+ long long int m_mtcSigmaE;
+ long long int m_mtcSigmaC;
+ unsigned int m_mtcSkew;
+
+ bool m_looping;
+
+ bool m_haveShutdown;
+
+#ifdef HAVE_LIBJACK
+ JackDriver *m_jackDriver;
+#endif
+
+ Scavenger<RunnablePluginInstance> m_pluginScavenger;
+
+ //!!! -- hoist to SoundDriver w/setter?
+ typedef std::set<InstrumentId> InstrumentSet;
+ InstrumentSet m_recordingInstruments;
+
+ typedef std::map<DeviceId, ClientPortPair> DevicePortMap;
+ DevicePortMap m_devicePortMap;
+
+ typedef std::map<ClientPortPair, DeviceId> PortDeviceMap;
+ PortDeviceMap m_suspendedPortMap;
+
+ std::string getPortName(ClientPortPair port);
+ ClientPortPair getPortByName(std::string name);
+
+ DeviceId getSpareDeviceId();
+
+ struct AlsaTimerInfo {
+ int clas;
+ int sclas;
+ int card;
+ int device;
+ int subdevice;
+ std::string name;
+ long resolution;
+ };
+ std::vector<AlsaTimerInfo> m_timers;
+ std::string m_currentTimer;
+
+ // This auxiliary queue is here as a hack, to avoid stuck notes if
+ // resetting playback while a note-off is currently in the ALSA
+ // queue. When playback is reset by ffwd or rewind etc, we drop
+ // all the queued events (which is generally what is desired,
+ // except for note offs) and reset the queue timer (so the note
+ // offs would have the wrong time stamps even if we hadn't dropped
+ // them). Thus, we need to re-send any recent note offs before
+ // continuing. This queue records which note offs have been
+ // added to the ALSA queue recently.
+ //
+ NoteOffQueue m_recentNoteOffs;
+ void pushRecentNoteOffs(); // move from recent to normal queue after reset
+ void cropRecentNoteOffs(const RealTime &t); // remove old note offs
+ void weedRecentNoteOffs(unsigned int pitch, MidiByte channel,
+ InstrumentId instrument); // on subsequent note on
+
+ bool m_queueRunning;
+
+ bool m_portCheckNeeded;
+
+ enum { NeedNoJackStart, NeedJackReposition, NeedJackStart } m_needJackStart;
+
+ bool m_doTimerChecks;
+ bool m_firstTimerCheck;
+ double m_timerRatio;
+ bool m_timerRatioCalculated;
+
+ std::string getAlsaModuleVersionString();
+ std::string getKernelVersionString();
+ void extractVersion(std::string vstr, int &major, int &minor, int &subminor, std::string &suffix);
+ bool versionIsAtLeast(std::string vstr, int major, int minor, int subminor);
+};
+
+}
+
+#endif // HAVE_ALSA
+
+#endif // _ALSADRIVER_H_
+
diff --git a/src/sound/AlsaPort.cpp b/src/sound/AlsaPort.cpp
new file mode 100644
index 0000000..dc12610
--- /dev/null
+++ b/src/sound/AlsaPort.cpp
@@ -0,0 +1,192 @@
+// -*- c-indentation-style:"stroustrup" c-basic-offset: 4 -*-
+/*
+ Rosegarden
+ A sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <bownie@bownie.com>
+
+ 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 "AlsaPort.h"
+
+#include <iostream>
+#include <cstdlib>
+#include <cstdio>
+
+#ifdef HAVE_ALSA
+
+// ALSA
+#include <alsa/asoundlib.h>
+#include <alsa/seq_event.h>
+#include <alsa/version.h>
+
+#include "MappedInstrument.h"
+#include "Midi.h"
+#include "WAVAudioFile.h"
+#include "MappedStudio.h"
+#include "misc/Strings.h"
+
+#ifdef HAVE_LIBJACK
+#include <jack/types.h>
+#include <unistd.h> // for usleep
+#include <cmath>
+#endif
+
+namespace Rosegarden
+{
+
+AlsaPortDescription::AlsaPortDescription(Instrument::InstrumentType type,
+ const std::string &name,
+ int client,
+ int port,
+ unsigned int clientType,
+ unsigned int portType,
+ unsigned int capability,
+ PortDirection direction):
+ m_type(type),
+ m_name(name),
+ m_client(client),
+ m_port(port),
+ m_clientType(clientType),
+ m_portType(portType),
+ m_capability(capability),
+ m_direction(direction)
+{}
+
+
+bool
+AlsaPortCmp::operator()(AlsaPortDescription *a1, AlsaPortDescription *a2)
+{
+ // Ordering for ALSA ports in the list:
+ //
+ // * Hardware ports (client id 64-127) sorted by direction
+ // (write, duplex, read) then client id then port id
+ //
+ // * Software ports (client id 128+) sorted by client id
+ // then port id
+ //
+ // * System ports (client id 0-63) sorted by client id then
+ // port id
+
+
+ // See comment in AlsaDriver::createMidiDevice -- the client
+ // numbering scheme changed in ALSA driver 1.0.11rc1.
+ // We now order:
+ //
+ // * Write-only software ports (client id 128+) sorted by client
+ // id then port id
+ //
+ // * Probable hardware ports (client id 16-127) sorted by
+ // direction (write, duplex, read) then client id (64+
+ // preferred) then port id
+ //
+ // * Read-write or read-only software ports (client id 128+)
+ // sorted by client id then port id
+ //
+ // * System ports (client id 0-15) sorted by client id then
+ // port id
+ //
+ // It's necessary to handle software ports ahead of
+ // hardware/system ports, because we want to keep all the hardware
+ // ports together (we don't want to change the priority of a
+ // hardware port relative to a software port based on its client
+ // ID) and we can't know for sure whether the 16-63 range are
+ // hardware or system ports.
+
+ enum Category {
+ WRITE_ONLY_SOFTWARE,
+ HARDWARE_PROBABLY,
+ MIXED_SOFTWARE,
+ SYSTEM
+ };
+
+ bool oldScheme = (SND_LIB_MAJOR == 0 ||
+ (SND_LIB_MAJOR == 1 &&
+ SND_LIB_MINOR == 0 &&
+ SND_LIB_SUBMINOR < 11));
+
+ Category a1cat;
+ if (a1->m_client < 16)
+ a1cat = SYSTEM;
+ else if (oldScheme && (a1->m_client < 64))
+ a1cat = SYSTEM;
+ else if (a1->m_client < 128)
+ a1cat = HARDWARE_PROBABLY;
+ else
+ a1cat = MIXED_SOFTWARE;
+
+ if (a1cat == MIXED_SOFTWARE) {
+ if (a1->m_direction == WriteOnly)
+ a1cat = WRITE_ONLY_SOFTWARE;
+ }
+
+ Category a2cat;
+ if (a2->m_client < 16)
+ a2cat = SYSTEM;
+ else if (oldScheme && (a2->m_client < 64))
+ a2cat = SYSTEM;
+ else if (a2->m_client < 128)
+ a2cat = HARDWARE_PROBABLY;
+ else
+ a2cat = MIXED_SOFTWARE;
+
+ if (a2cat == MIXED_SOFTWARE) {
+ if (a2->m_direction == WriteOnly)
+ a2cat = WRITE_ONLY_SOFTWARE;
+ }
+
+ if (a1cat != a2cat)
+ return int(a1cat) < int(a2cat);
+
+ if (a1cat == HARDWARE_PROBABLY) {
+
+ if (a1->m_direction == WriteOnly) {
+ if (a2->m_direction != WriteOnly)
+ return true;
+ } else if (a1->m_direction == Duplex) {
+ if (a2->m_direction == ReadOnly)
+ return true;
+ }
+
+ int c1 = a1->m_client;
+ int c2 = a2->m_client;
+ if (c1 < 64)
+ c1 += 1000;
+ if (c2 < 64)
+ c2 += 1000;
+ if (c1 != c2)
+ return c1 < c2;
+
+ } else if (a1cat == SYSTEM) {
+
+ int c1 = a1->m_client;
+ int c2 = a2->m_client;
+ if (c1 < 16)
+ c1 += 1000;
+ if (c2 < 16)
+ c2 += 1000;
+ if (c1 != c2)
+ return c1 < c2;
+ }
+
+ if (a1->m_client != a2->m_client) {
+ return a1->m_client < a2->m_client;
+ } else {
+ return a1->m_port < a2->m_port;
+ }
+}
+
+}
+
+#endif // HAVE_ALSA
diff --git a/src/sound/AlsaPort.h b/src/sound/AlsaPort.h
new file mode 100644
index 0000000..39cf009
--- /dev/null
+++ b/src/sound/AlsaPort.h
@@ -0,0 +1,86 @@
+// -*- c-indentation-style:"stroustrup" c-basic-offset: 4 -*-
+/*
+ Rosegarden
+ A sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <bownie@bownie.com>
+
+ 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 <vector>
+#include <set>
+#include "Instrument.h"
+#include "MappedCommon.h"
+
+#ifndef _ALSAPORT_H_
+#define _ALSAPORT_H_
+
+#ifdef HAVE_ALSA
+#include <alsa/asoundlib.h> // ALSA
+
+namespace Rosegarden
+{
+
+typedef std::pair<int, int> ClientPortPair;
+
+// Use this to hold all client information so that we can sort it
+// before generating devices - we want to put non-duplex devices
+// at the front of any device list (makes thing much easier at the
+// GUI and we already have some backwards compatability issues with
+// this).
+//
+class AlsaPortDescription
+{
+public:
+ AlsaPortDescription(Instrument::InstrumentType type,
+ const std::string &name,
+ int client,
+ int port,
+ unsigned int clientType,
+ unsigned int portType,
+ unsigned int capability,
+ PortDirection direction);
+
+ Instrument::InstrumentType m_type;
+ std::string m_name;
+ int m_client;
+ int m_port;
+ unsigned int m_clientType;
+ unsigned int m_portType;
+ unsigned int m_capability;
+ PortDirection m_direction; // or can deduce from capability
+
+ bool isReadable() { return m_direction == ReadOnly ||
+ m_direction == Duplex; }
+
+ bool isWriteable() { return m_direction == WriteOnly ||
+ m_direction == Duplex; }
+
+};
+
+// Sort by checking direction
+//
+struct AlsaPortCmp
+{
+ bool operator()(AlsaPortDescription *a1,
+ AlsaPortDescription *a2);
+};
+
+
+}
+
+#endif // HAVE_ALSA
+
+#endif // _RG_ALSA_PORT_H_
+
diff --git a/src/sound/AudioCache.cpp b/src/sound/AudioCache.cpp
new file mode 100644
index 0000000..e1e9000
--- /dev/null
+++ b/src/sound/AudioCache.cpp
@@ -0,0 +1,139 @@
+
+// -*- c-basic-offset: 4 -*-
+
+/*
+ Rosegarden
+ A sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <bownie@bownie.com>
+
+ 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 "AudioCache.h"
+#include <iostream>
+
+//#define DEBUG_AUDIO_CACHE 1
+
+namespace Rosegarden
+{
+
+AudioCache::~AudioCache()
+{
+ clear();
+}
+
+bool
+AudioCache::has(void *index)
+{
+ return m_cache.find(index) != m_cache.end();
+}
+
+float **
+AudioCache::getData(void *index, size_t &channels, size_t &frames)
+{
+ if (m_cache.find(index) == m_cache.end())
+ return 0;
+ CacheRec *rec = m_cache[index];
+ channels = rec->channels;
+ frames = rec->nframes;
+ return rec->data;
+}
+
+void
+AudioCache::addData(void *index, size_t channels, size_t nframes, float **data)
+{
+#ifdef DEBUG_AUDIO_CACHE
+ std::cerr << "AudioCache::addData(" << index << ")" << std::endl;
+#endif
+
+ if (m_cache.find(index) != m_cache.end()) {
+ std::cerr << "WARNING: AudioCache::addData(" << index << ", "
+ << channels << ", " << nframes
+ << ": already cached" << std::endl;
+ return ;
+ }
+
+ m_cache[index] = new CacheRec(data, channels, nframes);
+}
+
+void
+AudioCache::incrementReference(void *index)
+{
+ if (m_cache.find(index) == m_cache.end()) {
+ std::cerr << "WARNING: AudioCache::incrementReference(" << index
+ << "): not found" << std::endl;
+ return ;
+ }
+ ++m_cache[index]->refCount;
+
+#ifdef DEBUG_AUDIO_CACHE
+
+ std::cerr << "AudioCache::incrementReference(" << index << ") [to " << (m_cache[index]->refCount) << "]" << std::endl;
+#endif
+}
+
+void
+AudioCache::decrementReference(void *index)
+{
+ std::map<void *, CacheRec *>::iterator i = m_cache.find(index);
+
+ if (i == m_cache.end()) {
+ std::cerr << "WARNING: AudioCache::decrementReference(" << index
+ << "): not found" << std::endl;
+ return ;
+ }
+ if (i->second->refCount <= 1) {
+ delete i->second;
+ m_cache.erase(i);
+#ifdef DEBUG_AUDIO_CACHE
+
+ std::cerr << "AudioCache::decrementReference(" << index << ") [deleting]" << std::endl;
+#endif
+
+ } else {
+ --i->second->refCount;
+#ifdef DEBUG_AUDIO_CACHE
+
+ std::cerr << "AudioCache::decrementReference(" << index << ") [to " << (m_cache[index]->refCount) << "]" << std::endl;
+#endif
+
+ }
+}
+
+void
+AudioCache::clear()
+{
+#ifdef DEBUG_AUDIO_CACHE
+ std::cerr << "AudioCache::clear()" << std::endl;
+#endif
+
+ for (std::map<void *, CacheRec *>::iterator i = m_cache.begin();
+ i != m_cache.end(); ++i) {
+ if (i->second->refCount > 0) {
+ std::cerr << "WARNING: AudioCache::clear: deleting cached data with refCount " << i->second->refCount << std::endl;
+ }
+ }
+ m_cache.clear();
+}
+
+AudioCache::CacheRec::~CacheRec()
+{
+ for (size_t j = 0; j < channels; ++j)
+ delete[] data[j];
+ delete[] data;
+}
+
+}
+
+
diff --git a/src/sound/AudioCache.h b/src/sound/AudioCache.h
new file mode 100644
index 0000000..6dd55ff
--- /dev/null
+++ b/src/sound/AudioCache.h
@@ -0,0 +1,98 @@
+// -*- c-basic-offset: 4 -*-
+
+/*
+ Rosegarden
+ A sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <bownie@bownie.com>
+
+ 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_CACHE_H_
+#define _AUDIO_CACHE_H_
+
+#include <map>
+
+namespace Rosegarden
+{
+
+/**
+ * A simple cache for smallish bits of audio data, indexed by some
+ * opaque pointer type. (The PlayableAudioFile uses this with an
+ * AudioFile* index type, for example.) With reference counting.
+ */
+
+class AudioCache
+{
+public:
+ AudioCache() { }
+ virtual ~AudioCache();
+
+ /**
+ * Look some audio data up in the cache and report whether it
+ * exists.
+ */
+ bool has(void *index);
+
+ /**
+ * Look some audio data up in the cache and return it if it
+ * exists, returning the number of channels in channels, the frame
+ * count in frames, and one pointer per channel to samples in the
+ * return value. Return 0 if the data is not in cache. Does not
+ * affect the reference count. Ownership of the returned data
+ * remains with the cache object. You should not call this unless
+ * you have already called incrementReference (or addData) to
+ * register your interest.
+ */
+ float **getData(void *index, size_t &channels, size_t &frames);
+
+ /**
+ * Add a piece of data to the cache, and increment the reference
+ * count for that data (to 1). Ownership of the data is passed
+ * to the cache, which will delete it with delete[] when done.
+ */
+ void addData(void *index, size_t channels, size_t nframes, float **data);
+
+ /**
+ * Increment the reference count for a given piece of data.
+ */
+ void incrementReference(void *index);
+
+ /**
+ * Decrement the reference count for a given piece of data,
+ * and delete the data from the cache if the reference count has
+ * reached zero.
+ */
+ void decrementReference(void *index);
+
+protected:
+ void clear();
+
+ struct CacheRec {
+ CacheRec() : data(0), channels(0), nframes(0), refCount(0) { }
+ CacheRec(float **d, size_t c, size_t n) :
+ data(d), channels(c), nframes(n), refCount(1) { }
+ ~CacheRec();
+ float **data;
+ size_t channels;
+ size_t nframes;
+ int refCount;
+ };
+
+ std::map<void *, CacheRec *> m_cache;
+};
+
+}
+
+#endif
diff --git a/src/sound/AudioFile.cpp b/src/sound/AudioFile.cpp
new file mode 100644
index 0000000..6eba15a
--- /dev/null
+++ b/src/sound/AudioFile.cpp
@@ -0,0 +1,75 @@
+// -*- c-indentation-style:"stroustrup" c-basic-offset: 4 -*-
+/*
+ Rosegarden
+ A sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <bownie@bownie.com>
+
+ 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 "AudioFile.h"
+
+namespace Rosegarden
+
+{
+
+AudioFile::AudioFile(unsigned int id,
+ const std::string &name,
+ const std::string &fileName):
+ SoundFile(fileName),
+ m_type(UNKNOWN),
+ m_id(id),
+ m_name(name),
+ m_bitsPerSample(0),
+ m_sampleRate(0),
+ m_channels(0),
+ m_dataChunkIndex( -1)
+{
+ m_fileInfo = new QFileInfo(QString(fileName.c_str()));
+}
+
+AudioFile::AudioFile(const std::string &fileName,
+ unsigned int channels = 1,
+ unsigned int sampleRate = 48000,
+ unsigned int bitsPerSample = 16):
+ SoundFile(fileName),
+ m_type(UNKNOWN),
+ m_id(0),
+ m_name(""),
+ m_bitsPerSample(bitsPerSample),
+ m_sampleRate(sampleRate),
+ m_channels(channels),
+ m_dataChunkIndex( -1)
+{
+ m_fileInfo = new QFileInfo(QString(fileName.c_str()));
+}
+
+AudioFile::~AudioFile()
+{
+ delete m_fileInfo;
+}
+
+QDateTime
+AudioFile::getModificationDateTime()
+{
+ if (m_fileInfo)
+ return m_fileInfo->lastModified();
+ else
+ return QDateTime();
+}
+
+
+}
+
diff --git a/src/sound/AudioFile.h b/src/sound/AudioFile.h
new file mode 100644
index 0000000..1acbe61
--- /dev/null
+++ b/src/sound/AudioFile.h
@@ -0,0 +1,216 @@
+// -*- c-basic-offset: 4 -*-
+
+/*
+ Rosegarden
+ A sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <bownie@bownie.com>
+
+ 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 _AUDIOFILE_H_
+#define _AUDIOFILE_H_
+
+#include <string>
+#include <vector>
+#include <cmath>
+
+#include <qfileinfo.h>
+
+#include "SoundFile.h"
+#include "RealTime.h"
+
+// An AudioFile maintains information pertaining to an audio sample.
+// This is an abstract base class from which we derive our actual
+// AudioFile types - WAV, BWF, AIFF etc.
+//
+//
+
+namespace Rosegarden
+{
+
+typedef unsigned int AudioFileId;
+
+// The different types of audio file we support.
+//
+typedef enum
+{
+
+ UNKNOWN, // not yet known
+ WAV, // RIFF (Resource Interchange File Format) wav file
+ BWF, // RIFF Broadcast Wave File
+ AIFF, // Audio Interchange File Format
+ MP3
+
+} AudioFileType;
+
+class AudioFile : public SoundFile
+{
+public:
+ // The "read" constructor - open a file
+ // an assign a name and id to it.
+ //
+ AudioFile(AudioFileId id,
+ const std::string &name,
+ const std::string &fileName);
+
+ // The "write" constructor - we only need to
+ // specify a filename and some parameters and
+ // then write it out.
+ //
+ AudioFile(const std::string &fileName,
+ unsigned int channels,
+ unsigned int sampleRate,
+ unsigned int bitsPerSample);
+
+ ~AudioFile();
+
+ // Id of this audio file (used by AudioFileManager)
+ //
+ void setId(AudioFileId id) { m_id = id; }
+ AudioFileId getId() const { return m_id; }
+
+ // Name of this sample - in addition to a filename
+ //
+ void setName(const std::string &name) { m_name = name; }
+ std::string getName() const { return m_name; }
+
+ // Used for waveform interpolation at a point
+ //
+ float sinc(float value) { return sin(M_PI * value)/ M_PI * value; }
+
+ // Audio file identifier - set in constructor of file type
+ //
+ AudioFileType getType() const { return m_type; }
+
+ unsigned int getBitsPerSample() const { return m_bitsPerSample; }
+ unsigned int getSampleRate() const { return m_sampleRate; }
+ unsigned int getChannels() const { return m_channels; }
+
+ // We must continue our main control abstract methods from SoundFile
+ // and add our own close() method that will add any relevant header
+ // information to an audio file that has been written and is now
+ // being closed.
+ //
+ virtual bool open() = 0;
+ virtual bool write() = 0;
+ virtual void close() = 0;
+
+ // Show the information we have on this file
+ //
+ virtual void printStats() = 0;
+
+ // Move file pointer to relative time in data chunk - shouldn't be
+ // less than zero. Returns true if the scan time was valid and
+ // successful. Need two interfaces because when playing we use an
+ // external file handle (one per playback instance - PlayableAudioFile)
+ //
+ virtual bool scanTo(const RealTime &time) = 0;
+ virtual bool scanTo(std::ifstream *file, const RealTime &time) = 0;
+
+ // Scan forward in a file by a certain amount of time - same
+ // double interface (simple one for peak file generation, other
+ // for playback).
+ //
+ virtual bool scanForward(const RealTime &time) = 0;
+ virtual bool scanForward(std::ifstream *file, const RealTime &time) = 0;
+
+ // Return a number of samples - caller will have to
+ // de-interleave n-channel samples themselves.
+ //
+ virtual std::string getSampleFrames(std::ifstream *file,
+ unsigned int frames) = 0;
+
+ // Return a number of samples - caller will have to
+ // de-interleave n-channel samples themselves. Place
+ // results in buf, return actual number of frames read.
+ //
+ virtual unsigned int getSampleFrames(std::ifstream *file,
+ char *buf,
+ unsigned int frames) = 0;
+
+ // Return a number of (possibly) interleaved samples
+ // over a time slice from current file pointer position.
+ //
+ virtual std::string getSampleFrameSlice(std::ifstream *file,
+ const RealTime &time) = 0;
+
+ // Append a string of samples to an already open (for writing)
+ // audio file. Caller must have interleaved samples etc.
+ //
+ virtual bool appendSamples(const std::string &buffer) = 0;
+
+ // Append a string of samples to an already open (for writing)
+ // audio file. Caller must have interleaved samples etc.
+ //
+ virtual bool appendSamples(const char *buffer, unsigned int frames) = 0;
+
+ // Get the length of the sample file in RealTime
+ //
+ virtual RealTime getLength() = 0;
+
+ // Offset to start of sample data
+ //
+ virtual std::streampos getDataOffset() = 0;
+
+ // Return the peak file name
+ //
+ virtual std::string getPeakFilename() = 0;
+
+ // Return the modification timestamp
+ //
+ QDateTime getModificationDateTime();
+
+ // Implement in actual file type
+ //
+ virtual unsigned int getBytesPerFrame() = 0;
+
+ // Decode and de-interleave the given samples that were retrieved
+ // from this file or another with the same format as it. Place
+ // the results in the given float buffer. Return true for
+ // success. This function does crappy resampling if necessary.
+ //
+ virtual bool decode(const unsigned char *sourceData,
+ size_t sourceBytes,
+ size_t targetSampleRate,
+ size_t targetChannels,
+ size_t targetFrames,
+ std::vector<float *> &targetData,
+ bool addToResultBuffers = false) = 0;
+
+protected:
+
+ AudioFileType m_type; // AudioFile type
+ AudioFileId m_id; // AudioFile ID
+ std::string m_name; // AudioFile name (not filename)
+
+ unsigned int m_bitsPerSample;
+ unsigned int m_sampleRate;
+ unsigned int m_channels;
+
+ // How many bytes do we read before we get to the data?
+ // Could be huge so we make it a long long. -1 means it
+ // hasn't been set yet.
+ //
+ long long m_dataChunkIndex;
+
+ QFileInfo *m_fileInfo;
+
+};
+
+}
+
+
+#endif // _AUDIOFILE_H_
diff --git a/src/sound/AudioFileManager.cpp b/src/sound/AudioFileManager.cpp
new file mode 100644
index 0000000..93be26c
--- /dev/null
+++ b/src/sound/AudioFileManager.cpp
@@ -0,0 +1,1257 @@
+// -*- c-indentation-style:"stroustrup" c-basic-offset: 4 -*-
+/*
+ Rosegarden
+ A sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <bownie@bownie.com>
+
+ 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 <iostream>
+#include <kapplication.h>
+#include <fstream>
+#include <string>
+#include <dirent.h> // for new recording file
+#include <cstdio> // sprintf
+#include <cstdlib>
+#include <pthread.h>
+
+#if (__GNUC__ < 3)
+#include <strstream>
+#define stringstream strstream
+#else
+#include <sstream>
+#endif
+
+#include <kapp.h>
+#include <klocale.h>
+#include <kprocess.h>
+#include <kio/netaccess.h>
+#include <kmessagebox.h>
+
+#include <qpixmap.h>
+#include <qpainter.h>
+#include <qdatetime.h>
+#include <qfile.h>
+
+#include "AudioFile.h"
+#include "AudioFileManager.h"
+#include "WAVAudioFile.h"
+#include "BWFAudioFile.h"
+#include "MP3AudioFile.h"
+#include "misc/Strings.h"
+
+namespace Rosegarden
+{
+
+static pthread_mutex_t _audioFileManagerLock;
+
+class MutexLock
+{
+public:
+ MutexLock(pthread_mutex_t *mutex) : m_mutex(mutex)
+ {
+ pthread_mutex_lock(m_mutex);
+ }
+ ~MutexLock()
+ {
+ pthread_mutex_unlock(m_mutex);
+ }
+private:
+ pthread_mutex_t *m_mutex;
+};
+
+AudioFileManager::AudioFileManager() :
+ m_importProcess(0),
+ m_expectedSampleRate(0)
+{
+ pthread_mutexattr_t attr;
+ pthread_mutexattr_init(&attr);
+#ifdef HAVE_PTHREAD_MUTEX_RECURSIVE
+
+ pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
+#else
+#ifdef PTHREAD_MUTEX_RECURSIVE
+
+ pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
+#else
+
+ pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE_NP);
+#endif
+#endif
+
+ pthread_mutex_init(&_audioFileManagerLock, &attr);
+
+ // Set this through the set method so that the tilde gets
+ // shaken out.
+ //
+ setAudioPath("~/rosegarden");
+
+ // Retransmit progress
+ //
+ connect(&m_peakManager, SIGNAL(setProgress(int)),
+ this, SIGNAL(setProgress(int)));
+}
+
+AudioFileManager::~AudioFileManager()
+{
+ clear();
+}
+
+// Add a file from an absolute path
+//
+AudioFileId
+AudioFileManager::addFile(const std::string &filePath)
+{
+ MutexLock lock (&_audioFileManagerLock)
+ ;
+
+ QString ext;
+
+ if (filePath.length() > 3) {
+ ext = QString(filePath.substr(filePath.length() - 3, 3).c_str()).lower();
+ }
+
+ // Check for file existing already in manager by path
+ //
+ int check = fileExists(filePath);
+ if (check != -1) {
+ return AudioFileId(check);
+ }
+
+ // prepare for audio file
+ AudioFile *aF = 0;
+ AudioFileId id = getFirstUnusedID();
+
+ if (ext == "wav") {
+ // identify file type
+ AudioFileType subType = RIFFAudioFile::identifySubType(filePath);
+
+ if (subType == BWF) {
+#ifdef DEBUG_AUDIOFILEMANAGER
+ std::cout << "FOUND BWF" << std::endl;
+#endif
+
+ try {
+ aF = new BWFAudioFile(id, getShortFilename(filePath), filePath);
+ } catch (SoundFile::BadSoundFileException e) {
+ delete aF;
+ throw BadAudioPathException(e);
+ }
+ } else if (subType == WAV) {
+ try {
+ aF = new WAVAudioFile(id, getShortFilename(filePath), filePath);
+ } catch (SoundFile::BadSoundFileException e) {
+ delete aF;
+ throw BadAudioPathException(e);
+ }
+ }
+
+ // Ensure we have a valid file handle
+ //
+ if (aF == 0) {
+ std::cerr << "AudioFileManager: Unknown WAV audio file subtype in " << filePath << std::endl;
+ throw BadAudioPathException(filePath, __FILE__, __LINE__);
+ }
+
+ // Add file type on extension
+ try {
+ if (aF->open() == false) {
+ delete aF;
+ std::cerr << "AudioFileManager: Malformed audio file in " << filePath << std::endl;
+ throw BadAudioPathException(filePath, __FILE__, __LINE__);
+ }
+ } catch (SoundFile::BadSoundFileException e) {
+ delete aF;
+ throw BadAudioPathException(e);
+ }
+ }
+#ifdef HAVE_LIBMAD
+ else if (ext == "mp3") {
+ try {
+ aF = new MP3AudioFile(id, getShortFilename(filePath), filePath);
+
+ if (aF->open() == false) {
+ delete aF;
+ std::cerr << "AudioFileManager: Malformed mp3 audio file in " << filePath << std::endl;
+ throw BadAudioPathException(filePath, __FILE__, __LINE__);
+ }
+ } catch (SoundFile::BadSoundFileException e) {
+ delete aF;
+ throw BadAudioPathException(e);
+ }
+ }
+#endif // HAVE_LIBMAD
+ else {
+ std::cerr << "AudioFileManager: Unsupported audio file extension in " << filePath << std::endl;
+ throw BadAudioPathException(filePath, __FILE__, __LINE__);
+ }
+
+ if (aF) {
+ m_audioFiles.push_back(aF);
+ return id;
+ }
+
+ return 0;
+}
+
+// Convert long filename to shorter version
+std::string
+AudioFileManager::getShortFilename(const std::string &fileName)
+{
+ std::string rS = fileName;
+ unsigned int pos = rS.find_last_of("/");
+
+ if (pos > 0 && ( pos + 1 ) < rS.length())
+ rS = rS.substr(pos + 1, rS.length());
+
+ return rS;
+}
+
+// Turn a long path into a directory ending with a slash
+//
+std::string
+AudioFileManager::getDirectory(const std::string &path)
+{
+ std::string rS = path;
+ unsigned int pos = rS.find_last_of("/");
+
+ if (pos > 0 && ( pos + 1 ) < rS.length())
+ rS = rS.substr(0, pos + 1);
+
+ return rS;
+}
+
+
+// Create a new AudioFile with unique ID and label - insert from
+// our RG4 file
+//
+AudioFileId
+AudioFileManager::insertFile(const std::string &name,
+ const std::string &fileName)
+{
+ MutexLock lock (&_audioFileManagerLock)
+ ;
+
+ // first try to expand any beginning tilde
+ //
+ std::string foundFileName = substituteTildeForHome(fileName);
+
+ // If we've expanded and we can't find the file
+ // then try to find it in audio file directory.
+ //
+ QFileInfo info(foundFileName.c_str());
+ if (!info.exists())
+ foundFileName = getFileInPath(foundFileName);
+
+#ifdef DEBUG_AUDIOFILEMANAGER_INSERT_FILE
+
+ std::cout << "AudioFileManager::insertFile - "
+ << "expanded fileName = \""
+ << foundFileName << "\"" << std::endl;
+#endif
+
+ // bail if we haven't found any reasonable filename
+ if (foundFileName == "")
+ return false;
+
+ AudioFileId id = getFirstUnusedID();
+
+ WAVAudioFile *aF = 0;
+
+ try {
+
+ aF = new WAVAudioFile(id, name, foundFileName);
+
+ // if we don't recognise the file then don't insert it
+ //
+ if (aF->open() == false) {
+ delete aF;
+ std::cerr << "AudioFileManager::insertFile - don't recognise file type in " << foundFileName << std::endl;
+ throw BadAudioPathException(foundFileName, __FILE__, __LINE__);
+ }
+ m_audioFiles.push_back(aF);
+
+ } catch (SoundFile::BadSoundFileException e) {
+ delete aF;
+ throw BadAudioPathException(e);
+ }
+
+ return id;
+}
+
+
+bool
+AudioFileManager::removeFile(AudioFileId id)
+{
+ MutexLock lock (&_audioFileManagerLock)
+ ;
+
+ std::vector<AudioFile*>::iterator it;
+
+ for (it = m_audioFiles.begin();
+ it != m_audioFiles.end();
+ ++it) {
+ if ((*it)->getId() == id) {
+ m_peakManager.removeAudioFile(*it);
+ m_recordedAudioFiles.erase(*it);
+ m_derivedAudioFiles.erase(*it);
+ delete(*it);
+ m_audioFiles.erase(it);
+ return true;
+ }
+ }
+
+ return false;
+}
+
+AudioFileId
+AudioFileManager::getFirstUnusedID()
+{
+ AudioFileId rI = 0;
+
+ if (m_audioFiles.size() == 0)
+ return rI;
+
+ std::vector<AudioFile*>::iterator it;
+
+ for (it = m_audioFiles.begin();
+ it != m_audioFiles.end();
+ ++it) {
+ if (rI < (*it)->getId())
+ rI = (*it)->getId();
+ }
+
+ rI++;
+
+ return rI;
+}
+
+bool
+AudioFileManager::insertFile(const std::string &name,
+ const std::string &fileName,
+ AudioFileId id)
+{
+ MutexLock lock (&_audioFileManagerLock)
+ ;
+
+ // first try to expany any beginning tilde
+ std::string foundFileName = substituteTildeForHome(fileName);
+
+ // If we've expanded and we can't find the file
+ // then try to find it in audio file directory.
+ //
+ QFileInfo info(foundFileName.c_str());
+ if (!info.exists())
+ foundFileName = getFileInPath(foundFileName);
+
+#ifdef DEBUG_AUDIOFILEMANAGER_INSERT_FILE
+
+ std::cout << "AudioFileManager::insertFile - "
+ << "expanded fileName = \""
+ << foundFileName << "\"" << std::endl;
+#endif
+
+ // If no joy here then we can't find this file
+ if (foundFileName == "")
+ return false;
+
+ // make sure we don't have a file of this ID hanging around already
+ removeFile(id);
+
+ // and insert
+ WAVAudioFile *aF = 0;
+
+ try {
+
+ aF = new WAVAudioFile(id, name, foundFileName);
+
+ // Test the file
+ if (aF->open() == false) {
+ delete aF;
+ return false;
+ }
+
+ m_audioFiles.push_back(aF);
+
+ } catch (SoundFile::BadSoundFileException e) {
+ delete aF;
+ throw BadAudioPathException(e);
+ }
+
+ return true;
+}
+
+// Add a given path to our sample search path
+//
+void
+AudioFileManager::setAudioPath(const std::string &path)
+{
+ MutexLock lock (&_audioFileManagerLock)
+ ;
+
+ std::string hPath = path;
+
+ // add a trailing / if we don't have one
+ //
+ if (hPath[hPath.size() - 1] != '/')
+ hPath += std::string("/");
+
+ // get the home directory
+ if (hPath[0] == '~') {
+ hPath.erase(0, 1);
+ hPath = std::string(getenv("HOME")) + hPath;
+ }
+
+ m_audioPath = hPath;
+
+}
+
+void
+AudioFileManager::testAudioPath() throw (BadAudioPathException)
+{
+ QFileInfo info(m_audioPath.c_str());
+ if (!(info.exists() && info.isDir() && !info.isRelative() &&
+ info.isWritable() && info.isReadable()))
+ throw BadAudioPathException(m_audioPath.data());
+}
+
+
+// See if we can find a given file in our search path
+// return the first occurence of a match or the empty
+// std::string if no match.
+//
+std::string
+AudioFileManager::getFileInPath(const std::string &file)
+{
+ MutexLock lock (&_audioFileManagerLock)
+ ;
+
+ QFileInfo info(file.c_str());
+
+ if (info.exists())
+ return file;
+
+ // Build the search filename from the audio path and
+ // the file basename.
+ //
+ QString searchFile = QString(m_audioPath.c_str()) + info.fileName();
+ QFileInfo searchInfo(searchFile);
+
+ if (searchInfo.exists())
+ return searchFile.latin1();
+
+ std::cout << "AudioFileManager::getFileInPath - "
+ << "searchInfo = " << searchFile << std::endl;
+
+ return "";
+}
+
+
+// Check for file path existence
+//
+int
+AudioFileManager::fileExists(const std::string &path)
+{
+ MutexLock lock (&_audioFileManagerLock)
+ ;
+
+ std::vector<AudioFile*>::iterator it;
+
+ for (it = m_audioFiles.begin();
+ it != m_audioFiles.end();
+ ++it) {
+ if ((*it)->getFilename() == path)
+ return (*it)->getId();
+ }
+
+ return -1;
+
+}
+
+// Does a specific file id exist on the manager?
+//
+bool
+AudioFileManager::fileExists(AudioFileId id)
+{
+ MutexLock lock (&_audioFileManagerLock)
+ ;
+
+ std::vector<AudioFile*>::iterator it;
+
+ for (it = m_audioFiles.begin();
+ it != m_audioFiles.end();
+ ++it) {
+ if ((*it)->getId() == id)
+ return true;
+ }
+
+ return false;
+
+}
+
+void
+AudioFileManager::clear()
+{
+ MutexLock lock (&_audioFileManagerLock)
+ ;
+
+ std::vector<AudioFile*>::iterator it;
+
+ for (it = m_audioFiles.begin();
+ it != m_audioFiles.end();
+ ++it) {
+ m_recordedAudioFiles.erase(*it);
+ m_derivedAudioFiles.erase(*it);
+ delete(*it);
+ }
+
+ m_audioFiles.erase(m_audioFiles.begin(), m_audioFiles.end());
+
+ // Clear the PeakFileManager too
+ //
+ m_peakManager.clear();
+}
+
+AudioFile *
+AudioFileManager::createRecordingAudioFile()
+{
+ MutexLock lock (&_audioFileManagerLock)
+ ;
+
+ AudioFileId newId = getFirstUnusedID();
+ QString fileName = "";
+
+ while (fileName == "") {
+
+ fileName = QString("rg-%1-%2.wav")
+ .arg(QDateTime::currentDateTime().toString("yyyyMMdd-hhmmss"))
+ .arg(newId + 1);
+
+ if (QFile(m_audioPath.c_str() + fileName).exists()) {
+ fileName = "";
+ ++newId;
+ }
+ }
+
+ // insert file into vector
+ WAVAudioFile *aF = 0;
+
+ try {
+ aF = new WAVAudioFile(newId, fileName.data(), m_audioPath + fileName.data());
+ m_audioFiles.push_back(aF);
+ m_recordedAudioFiles.insert(aF);
+ } catch (SoundFile::BadSoundFileException e) {
+ delete aF;
+ throw BadAudioPathException(e);
+ }
+
+ return aF;
+}
+
+std::vector<std::string>
+AudioFileManager::createRecordingAudioFiles(unsigned int n)
+{
+ std::vector<std::string> v;
+ for (unsigned int i = 0; i < n; ++i) {
+ AudioFile *af = createRecordingAudioFile();
+ if (af)
+ v.push_back(m_audioPath + af->getFilename().data());
+ // !af should not happen, and we have no good recovery if it does
+ }
+ return v;
+}
+
+bool
+AudioFileManager::wasAudioFileRecentlyRecorded(AudioFileId id)
+{
+ AudioFile *file = getAudioFile(id);
+ if (file)
+ return (m_recordedAudioFiles.find(file) !=
+ m_recordedAudioFiles.end());
+ return false;
+}
+
+bool
+AudioFileManager::wasAudioFileRecentlyDerived(AudioFileId id)
+{
+ AudioFile *file = getAudioFile(id);
+ if (file)
+ return (m_derivedAudioFiles.find(file) !=
+ m_derivedAudioFiles.end());
+ return false;
+}
+
+void
+AudioFileManager::resetRecentlyCreatedFiles()
+{
+ m_recordedAudioFiles.clear();
+ m_derivedAudioFiles.clear();
+}
+
+AudioFile *
+AudioFileManager::createDerivedAudioFile(AudioFileId source,
+ const char *prefix)
+{
+ MutexLock lock (&_audioFileManagerLock);
+
+ AudioFile *sourceFile = getAudioFile(source);
+ if (!sourceFile) return 0;
+
+ AudioFileId newId = getFirstUnusedID();
+ QString fileName = "";
+
+ std::string sourceBase = sourceFile->getShortFilename();
+ if (sourceBase.length() > 4 && sourceBase.substr(0, 3) == "rg-") {
+ sourceBase = sourceBase.substr(3);
+ }
+ if (sourceBase.length() > 15) sourceBase = sourceBase.substr(0, 15);
+
+ while (fileName == "") {
+
+ fileName = QString("%1-%2-%3-%4.wav")
+ .arg(prefix)
+ .arg(sourceBase)
+ .arg(QDateTime::currentDateTime().toString("yyyyMMdd-hhmmss"))
+ .arg(newId + 1);
+
+ if (QFile(m_audioPath.c_str() + fileName).exists()) {
+ fileName = "";
+ ++newId;
+ }
+ }
+
+ // insert file into vector
+ WAVAudioFile *aF = 0;
+
+ try {
+ aF = new WAVAudioFile(newId,
+ fileName.data(),
+ m_audioPath + fileName.data());
+ m_audioFiles.push_back(aF);
+ m_derivedAudioFiles.insert(aF);
+ } catch (SoundFile::BadSoundFileException e) {
+ delete aF;
+ throw BadAudioPathException(e);
+ }
+
+ return aF;
+}
+
+AudioFileId
+AudioFileManager::importURL(const KURL &url, int sampleRate)
+{
+ if (url.isLocalFile()) return importFile(url.path(), sampleRate);
+
+ std::cerr << "AudioFileManager::importURL("<< url.prettyURL() << ", " << sampleRate << ")" << std::endl;
+
+ emit setOperationName(i18n("Downloading file %1").arg(url.prettyURL()));
+
+ QString localPath = "";
+ if (!KIO::NetAccess::download(url, localPath)) {
+ KMessageBox::error(0, i18n("Cannot download file %1").arg(url.prettyURL()));
+ throw SoundFile::BadSoundFileException(url.prettyURL());
+ }
+
+ AudioFileId id = 0;
+
+ try {
+ id = importFile(localPath.data(), sampleRate);
+ } catch (BadAudioPathException ape) {
+ KIO::NetAccess::removeTempFile(localPath);
+ throw ape;
+ } catch (SoundFile::BadSoundFileException bse) {
+ KIO::NetAccess::removeTempFile(localPath);
+ throw bse;
+ }
+
+ return id;
+}
+
+bool
+AudioFileManager::fileNeedsConversion(const std::string &fileName,
+ int sampleRate)
+{
+ KProcess *proc = new KProcess();
+ *proc << "rosegarden-audiofile-importer";
+ if (sampleRate > 0) {
+ *proc << "-r";
+ *proc << QString("%1").arg(sampleRate);
+ }
+ *proc << "-w";
+ *proc << fileName.c_str();
+
+ proc->start(KProcess::Block, KProcess::NoCommunication);
+
+ int es = proc->exitStatus();
+ delete proc;
+
+ if (es == 0 || es == 1) { // 1 == "other error" -- wouldn't be able to convert
+ return false;
+ }
+ return true;
+}
+
+AudioFileId
+AudioFileManager::importFile(const std::string &fileName, int sampleRate)
+{
+ MutexLock lock (&_audioFileManagerLock);
+
+ std::cerr << "AudioFileManager::importFile("<< fileName << ", " << sampleRate << ")" << std::endl;
+
+ KProcess *proc = new KProcess();
+ *proc << "rosegarden-audiofile-importer";
+ if (sampleRate > 0) {
+ *proc << "-r";
+ *proc << QString("%1").arg(sampleRate);
+ }
+ *proc << "-w";
+ *proc << fileName.c_str();
+
+ proc->start(KProcess::Block, KProcess::NoCommunication);
+
+ int es = proc->exitStatus();
+ delete proc;
+
+ if (es == 0) {
+ AudioFileId id = addFile(fileName);
+ m_expectedSampleRate = sampleRate;
+ return id;
+ }
+
+ if (es == 2) {
+ emit setOperationName(i18n("Converting audio file..."));
+ } else if (es == 3) {
+ emit setOperationName(i18n("Resampling audio file..."));
+ } else if (es == 4) {
+ emit setOperationName(i18n("Converting and resampling audio file..."));
+ } else {
+ emit setOperationName(i18n("Importing audio file..."));
+ }
+
+ AudioFileId newId = getFirstUnusedID();
+ QString targetName = "";
+
+ QString sourceBase = QFileInfo(fileName.c_str()).baseName();
+ if (sourceBase.length() > 3 && sourceBase.startsWith("rg-")) {
+ sourceBase = sourceBase.right(sourceBase.length() - 3);
+ }
+ if (sourceBase.length() > 15) sourceBase = sourceBase.left(15);
+
+ while (targetName == "") {
+
+ targetName = QString("conv-%2-%3-%4.wav")
+ .arg(sourceBase)
+ .arg(QDateTime::currentDateTime().toString("yyyyMMdd-hhmmss"))
+ .arg(newId + 1);
+
+ if (QFile(m_audioPath.c_str() + targetName).exists()) {
+ targetName = "";
+ ++newId;
+ }
+ }
+
+ m_importProcess = new KProcess;
+
+ *m_importProcess << "rosegarden-audiofile-importer";
+ if (sampleRate > 0) {
+ *m_importProcess << "-r";
+ *m_importProcess << QString("%1").arg(sampleRate);
+ }
+ *m_importProcess << "-c";
+ *m_importProcess << fileName.c_str();
+ *m_importProcess << (m_audioPath.c_str() + targetName);
+
+ m_importProcess->start(KProcess::NotifyOnExit, KProcess::NoCommunication);
+
+ while (m_importProcess->isRunning()) {
+ kapp->processEvents(100);
+ }
+
+ if (!m_importProcess->normalExit()) {
+ // interrupted
+ throw SoundFile::BadSoundFileException(fileName, "Import cancelled");
+ }
+
+ es = m_importProcess->exitStatus();
+ delete m_importProcess;
+ m_importProcess = 0;
+
+ if (es) {
+ std::cerr << "audio file importer failed" << std::endl;
+ throw SoundFile::BadSoundFileException(fileName, i18n("Failed to convert or resample audio file on import"));
+ } else {
+ std::cerr << "audio file importer succeeded" << std::endl;
+ }
+
+ // insert file into vector
+ WAVAudioFile *aF = 0;
+
+ aF = new WAVAudioFile(newId,
+ targetName.data(),
+ m_audioPath + targetName.data());
+ m_audioFiles.push_back(aF);
+ m_derivedAudioFiles.insert(aF);
+ // Don't catch SoundFile::BadSoundFileException
+
+ m_expectedSampleRate = sampleRate;
+
+ return aF->getId();
+}
+
+void
+AudioFileManager::slotStopImport()
+{
+ if (m_importProcess) {
+ m_importProcess->kill(SIGTERM);
+ sleep(1);
+ m_importProcess->kill(SIGKILL);
+ }
+}
+
+AudioFile*
+AudioFileManager::getLastAudioFile()
+{
+ MutexLock lock (&_audioFileManagerLock)
+ ;
+
+ std::vector<AudioFile*>::iterator it = m_audioFiles.begin();
+ AudioFile* audioFile = 0;
+
+ while (it != m_audioFiles.end()) {
+ audioFile = (*it);
+ it++;
+ }
+
+ return audioFile;
+}
+
+std::string
+AudioFileManager::substituteHomeForTilde(const std::string &path)
+{
+ std::string rS = path;
+ std::string homePath = std::string(getenv("HOME"));
+
+ // if path length is less than homePath then just return unchanged
+ if (rS.length() < homePath.length())
+ return rS;
+
+ // if the first section matches the path then substitute
+ if (rS.substr(0, homePath.length()) == homePath) {
+ rS.erase(0, homePath.length());
+ rS = "~" + rS;
+ }
+
+ return rS;
+}
+
+std::string
+AudioFileManager::substituteTildeForHome(const std::string &path)
+{
+ std::string rS = path;
+ std::string homePath = std::string(getenv("HOME"));
+
+ if (rS.substr(0, 2) == std::string("~/")) {
+ rS.erase(0, 1); // erase tilde and prepend HOME env
+ rS = homePath + rS;
+ }
+
+ return rS;
+}
+
+
+
+// Export audio files and assorted bits and bobs - make sure
+// that we store the files in a format that's user independent
+// so that people can pack up and swap their songs (including
+// audio files) and shift them about easily.
+//
+std::string
+AudioFileManager::toXmlString()
+{
+ MutexLock lock (&_audioFileManagerLock)
+ ;
+
+ std::stringstream audioFiles;
+ std::string audioPath = substituteHomeForTilde(m_audioPath);
+
+ audioFiles << "<audiofiles";
+ if (m_expectedSampleRate != 0) {
+ audioFiles << " expectedRate=\"" << m_expectedSampleRate << "\"";
+ }
+ audioFiles << ">" << std::endl;
+ audioFiles << " <audioPath value=\""
+ << audioPath << "\"/>" << std::endl;
+
+ std::string fileName;
+ std::vector<AudioFile*>::iterator it;
+
+ for (it = m_audioFiles.begin(); it != m_audioFiles.end(); ++it) {
+ fileName = (*it)->getFilename();
+
+ // attempt two substitutions - If the prefix to the filename
+ // is the same as the audio path then we can dock the prefix
+ // as it'll be added again next time. If the path doesn't
+ // have the audio path in it but has our home directory in it
+ // then swap this out for a tilde '~'
+ //
+#ifdef DEBUG_AUDIOFILEMANAGER
+
+ std::cout << "DIR = " << getDirectory(fileName) << " : "
+ " PATH = " << m_audioPath << std::endl;
+#endif
+
+ if (getDirectory(fileName) == m_audioPath)
+ fileName = getShortFilename(fileName);
+ else
+ fileName = substituteHomeForTilde(fileName);
+
+ audioFiles << " <audio id=\""
+ << (*it)->getId()
+ << "\" file=\""
+ << fileName
+ << "\" label=\""
+ << encode((*it)->getName())
+ << "\"/>" << std::endl;
+ }
+
+ audioFiles << "</audiofiles>" << std::endl;
+
+#if (__GNUC__ < 3)
+
+ audioFiles << std::ends;
+#else
+
+ audioFiles << std::endl;
+#endif
+
+ return audioFiles.str();
+}
+
+// Generate preview peak files or peak chunks according
+// to file type.
+//
+void
+AudioFileManager::generatePreviews()
+{
+ MutexLock lock (&_audioFileManagerLock)
+ ;
+
+#ifdef DEBUG_AUDIOFILEMANAGER
+
+ std::cout << "AudioFileManager::generatePreviews - "
+ << "for " << m_audioFiles.size() << " files"
+ << std::endl;
+#endif
+
+
+ // Generate peaks if we need to
+ //
+ std::vector<AudioFile*>::iterator it;
+ for (it = m_audioFiles.begin(); it != m_audioFiles.end(); ++it) {
+ if (!m_peakManager.hasValidPeaks(*it))
+ m_peakManager.generatePeaks(*it, 1);
+ }
+}
+
+// Attempt to stop a preview
+//
+void
+AudioFileManager::slotStopPreview()
+{
+ MutexLock lock (&_audioFileManagerLock);
+ m_peakManager.stopPreview();
+}
+
+
+// Generate a preview for a specific audio file - say if
+// one has just been added to the AudioFileManager.
+// Also used for generating previews if the file has been
+// modified.
+//
+bool
+AudioFileManager::generatePreview(AudioFileId id)
+{
+ MutexLock lock (&_audioFileManagerLock)
+ ;
+
+ AudioFile *audioFile = getAudioFile(id);
+
+ if (audioFile == 0)
+ return false;
+
+ if (!m_peakManager.hasValidPeaks(audioFile))
+ m_peakManager.generatePeaks(audioFile, 1);
+
+ return true;
+}
+
+AudioFile*
+AudioFileManager::getAudioFile(AudioFileId id)
+{
+ MutexLock lock (&_audioFileManagerLock)
+ ;
+
+ std::vector<AudioFile*>::iterator it;
+
+ for (it = m_audioFiles.begin();
+ it != m_audioFiles.end();
+ it++) {
+ if ((*it)->getId() == id)
+ return (*it);
+ }
+ return 0;
+}
+
+std::vector<float>
+AudioFileManager::getPreview(AudioFileId id,
+ const RealTime &startTime,
+ const RealTime &endTime,
+ int width,
+ bool withMinima)
+{
+ MutexLock lock (&_audioFileManagerLock)
+ ;
+
+ AudioFile *audioFile = getAudioFile(id);
+
+ if (audioFile == 0) {
+ return std::vector<float>();
+ }
+
+ if (!m_peakManager.hasValidPeaks(audioFile)) {
+ std::cerr << "AudioFileManager::getPreview: No peaks for audio file " << audioFile->getFilename() << std::endl;
+ throw PeakFileManager::BadPeakFileException
+ (audioFile->getFilename(), __FILE__, __LINE__);
+ }
+
+ return m_peakManager.getPreview(audioFile,
+ startTime,
+ endTime,
+ width,
+ withMinima);
+}
+
+void
+AudioFileManager::drawPreview(AudioFileId id,
+ const RealTime &startTime,
+ const RealTime &endTime,
+ QPixmap *pixmap)
+{
+ MutexLock lock (&_audioFileManagerLock)
+ ;
+
+ AudioFile *audioFile = getAudioFile(id);
+ if (!audioFile)
+ return ;
+
+ if (!m_peakManager.hasValidPeaks(audioFile)) {
+ std::cerr << "AudioFileManager::getPreview: No peaks for audio file " << audioFile->getFilename() << std::endl;
+ throw PeakFileManager::BadPeakFileException
+ (audioFile->getFilename(), __FILE__, __LINE__);
+ }
+
+ std::vector<float> values = m_peakManager.getPreview
+ (audioFile,
+ startTime,
+ endTime,
+ pixmap->width(),
+ false);
+
+ QPainter painter(pixmap);
+ pixmap->fill(kapp->palette().color(QPalette::Active,
+ QColorGroup::Base));
+ painter.setPen(kapp->palette().color(QPalette::Active,
+ QColorGroup::Dark));
+
+ if (values.size() == 0) {
+#ifdef DEBUG_AUDIOFILEMANAGER
+ std::cerr << "AudioFileManager::drawPreview - "
+ << "no preview values returned!" << std::endl;
+#endif
+
+ return ;
+ }
+
+ float yStep = pixmap->height() / 2;
+ int channels = audioFile->getChannels();
+ float ch1Value, ch2Value;
+
+ if (channels == 0) {
+#ifdef DEBUG_AUDIOFILEMANAGER
+ std::cerr << "AudioFileManager::drawPreview - "
+ << "no channels in audio file!" << std::endl;
+#endif
+
+ return ;
+ }
+
+
+ // Render pixmap
+ //
+ for (int i = 0; i < pixmap->width(); i++) {
+ // Always get two values for our pixmap no matter how many
+ // channels in AudioFile as that's all we can display.
+ //
+ if (channels == 1) {
+ ch1Value = values[i];
+ ch2Value = values[i];
+ } else {
+ ch1Value = values[i * channels];
+ ch2Value = values[i * channels + 1];
+ }
+
+ painter.drawLine(i, static_cast<int>(yStep - ch1Value * yStep),
+ i, static_cast<int>(yStep + ch2Value * yStep));
+ }
+}
+
+void
+AudioFileManager::drawHighlightedPreview(AudioFileId id,
+ const RealTime &startTime,
+ const RealTime &endTime,
+ const RealTime &highlightStart,
+ const RealTime &highlightEnd,
+ QPixmap *pixmap)
+{
+ MutexLock lock (&_audioFileManagerLock)
+ ;
+
+ AudioFile *audioFile = getAudioFile(id);
+ if (!audioFile)
+ return ;
+
+ if (!m_peakManager.hasValidPeaks(audioFile)) {
+ std::cerr << "AudioFileManager::getPreview: No peaks for audio file " << audioFile->getFilename() << std::endl;
+ throw PeakFileManager::BadPeakFileException
+ (audioFile->getFilename(), __FILE__, __LINE__);
+ }
+
+ std::vector<float> values = m_peakManager.getPreview
+ (audioFile,
+ startTime,
+ endTime,
+ pixmap->width(),
+ false);
+
+ int startWidth = (int)(double(pixmap->width()) * (highlightStart /
+ (endTime - startTime)));
+ int endWidth = (int)(double(pixmap->width()) * (highlightEnd /
+ (endTime - startTime)));
+
+ QPainter painter(pixmap);
+ pixmap->fill(kapp->palette().color(QPalette::Active,
+ QColorGroup::Base));
+
+ float yStep = pixmap->height() / 2;
+ int channels = audioFile->getChannels();
+ float ch1Value, ch2Value;
+
+ // Render pixmap
+ //
+ for (int i = 0; i < pixmap->width(); ++i) {
+ if ((i * channels + (channels - 1)) >= int(values.size()))
+ break;
+
+ // Always get two values for our pixmap no matter how many
+ // channels in AudioFile as that's all we can display.
+ //
+ if (channels == 1) {
+ ch1Value = values[i];
+ ch2Value = values[i];
+ } else {
+ ch1Value = values[i * channels];
+ ch2Value = values[i * channels + 1];
+ }
+
+ if (i < startWidth || i > endWidth)
+ painter.setPen(kapp->palette().color(QPalette::Active,
+ QColorGroup::Mid));
+ else
+ painter.setPen(kapp->palette().color(QPalette::Active,
+ QColorGroup::Dark));
+
+ painter.drawLine(i, static_cast<int>(yStep - ch1Value * yStep),
+ i, static_cast<int>(yStep + ch2Value * yStep));
+ }
+}
+
+
+void
+AudioFileManager::print()
+{
+ MutexLock lock (&_audioFileManagerLock)
+ ;
+
+#ifdef DEBUG_AUDIOFILEMANAGER
+
+ std::cout << "AudioFileManager - " << m_audioFiles.size() << " entr";
+
+ if (m_audioFiles.size() == 1)
+ std::cout << "y";
+ else
+ std::cout << "ies";
+
+ std::cout << std::endl << std::endl;
+
+ std::vector<AudioFile*>::iterator it;
+ for (it = m_audioFiles.begin(); it != m_audioFiles.end(); ++it) {
+ std::cout << (*it)->getId() << " : " << (*it)->getName()
+ << " : \"" << (*it)->getFilename() << "\"" << std::endl;
+ }
+#endif
+}
+
+std::vector<SplitPointPair>
+AudioFileManager::getSplitPoints(AudioFileId id,
+ const RealTime &startTime,
+ const RealTime &endTime,
+ int threshold,
+ const RealTime &minTime)
+{
+ MutexLock lock (&_audioFileManagerLock)
+ ;
+
+ AudioFile *audioFile = getAudioFile(id);
+
+ if (audioFile == 0)
+ return std::vector<SplitPointPair>();
+
+ return m_peakManager.getSplitPoints(audioFile,
+ startTime,
+ endTime,
+ threshold,
+ minTime);
+}
+
+std::set<int>
+AudioFileManager::getActualSampleRates() const
+{
+ std::set<int> rates;
+
+ for (std::vector<AudioFile *>::const_iterator i = m_audioFiles.begin();
+ i != m_audioFiles.end(); ++i) {
+
+ unsigned int sr = (*i)->getSampleRate();
+ if (sr != 0) rates.insert(int(sr));
+ }
+
+ return rates;
+}
+
+}
+
+
+#include "AudioFileManager.moc"
diff --git a/src/sound/AudioFileManager.h b/src/sound/AudioFileManager.h
new file mode 100644
index 0000000..9721669
--- /dev/null
+++ b/src/sound/AudioFileManager.h
@@ -0,0 +1,327 @@
+// -*- c-indentation-style:"stroustrup" c-basic-offset: 4 -*-
+/*
+ Rosegarden
+ A sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <bownie@bownie.com>
+
+ 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 _AUDIOFILEMANAGER_H_
+#define _AUDIOFILEMANAGER_H_
+
+#include <string>
+#include <vector>
+#include <set>
+#include <map>
+
+#include <qpixmap.h>
+#include <qobject.h>
+
+#include "AudioFile.h"
+#include "XmlExportable.h"
+#include "PeakFileManager.h"
+#include "PeakFile.h"
+#include "Exception.h"
+
+#include <kurl.h>
+
+// AudioFileManager loads and maps audio files to their
+// internal references (ids). A point of contact for
+// AudioFile information - loading a Composition should
+// use this class to pick up the AudioFile references,
+// editing the AudioFiles in a Composition will be
+// made through this manager.
+
+// This is in the sound library because it's so closely
+// connected to other sound classes like the AudioFile
+// ones. However, the audio file manager itself within
+// Rosegarden is stored in the GUI process. This class
+// is not (and should not be) used elsewhere within the
+// sound or sequencer libraries.
+
+class KProcess;
+
+namespace Rosegarden
+{
+
+typedef std::vector<AudioFile*>::const_iterator AudioFileManagerIterator;
+
+class AudioFileManager : public QObject, public XmlExportable
+{
+ Q_OBJECT
+public:
+ AudioFileManager();
+ virtual ~AudioFileManager();
+
+ class BadAudioPathException : public Exception
+ {
+ public:
+ BadAudioPathException(std::string path) :
+ Exception("Bad audio file path " + path), m_path(path) { }
+ BadAudioPathException(std::string path, std::string file, int line) :
+ Exception("Bad audio file path " + path, file, line), m_path(path) { }
+ BadAudioPathException(const SoundFile::BadSoundFileException &e) :
+ Exception("Bad audio file path (malformed file?) " + e.getPath()), m_path(e.getPath()) { }
+
+ ~BadAudioPathException() throw() { }
+
+ std::string getPath() const { return m_path; }
+
+ private:
+ std::string m_path;
+ };
+
+private:
+ AudioFileManager(const AudioFileManager &aFM);
+ AudioFileManager& operator=(const AudioFileManager &);
+
+public:
+
+ // Create an audio file from an absolute path - we use this interface
+ // to add an actual file.
+ //
+ AudioFileId addFile(const std::string &filePath);
+ // throw BadAudioPathException
+
+ // Return true if a file would require importFile to import it, rather
+ // than a simple addFile. You can use importFile even when a file
+ // doesn't need conversion, but this tells you whether it's necessary
+ //
+ bool fileNeedsConversion(const std::string &filePath,
+ int targetSampleRate = 0);
+
+ // Create an audio file by importing (i.e. converting and/or
+ // resampling) an existing file using the external conversion
+ // utility
+ //
+ AudioFileId importFile(const std::string &filePath,
+ int targetSampleRate = 0);
+ // throw BadAudioPathException, BadSoundFileException
+
+ // Create an audio file by importing from a URL
+ //
+ AudioFileId importURL(const KURL &filePath,
+ int targetSampleRate = 0);
+ // throw BadAudioPathException, BadSoundFileException
+
+ // Insert an audio file into the AudioFileManager and get the
+ // first allocated id for it. Used from the RG file as we already
+ // have both name and filename/path.
+ //
+ AudioFileId insertFile(const std::string &name,
+ const std::string &fileName);
+ // throw BadAudioPathException
+
+ // And insert an AudioFile and specify an id
+ //
+ bool insertFile(const std::string &name, const std::string &fileName,
+ AudioFileId id);
+ // throw BadAudioPathException
+
+ // Remove a file from the AudioManager by id
+ //
+ bool removeFile(AudioFileId id);
+
+ // Does a specific file id exist?
+ //
+ bool fileExists(AudioFileId id);
+
+ // Does a specific file path exist? Return ID or -1.
+ //
+ int fileExists(const std::string &path);
+
+ // get audio file by id
+ //
+ AudioFile* getAudioFile(AudioFileId id);
+
+ // Get the list of files
+ //
+ std::vector<AudioFile*>::const_iterator begin() const
+ { return m_audioFiles.begin(); }
+
+ std::vector<AudioFile*>::const_iterator end() const
+ { return m_audioFiles.end(); }
+
+ // Clear down all audio file references
+ //
+ void clear();
+
+ // Get and set the record path
+ //
+ std::string getAudioPath() const { return m_audioPath; }
+ void setAudioPath(const std::string &path);
+
+ // Throw if the current audio path does not exist or is not writable
+ //
+ void testAudioPath() throw(BadAudioPathException);
+
+ // Get a new audio filename at the audio record path
+ //
+ AudioFile *createRecordingAudioFile();
+ // throw BadAudioPathException
+
+ // Get a set of new audio filenames at the audio record path
+ //
+ std::vector<std::string> createRecordingAudioFiles(unsigned int number);
+ // throw BadAudioPathException
+
+ // Return whether a file was created by recording within this "session"
+ //
+ bool wasAudioFileRecentlyRecorded(AudioFileId id);
+
+ // Return whether a file was created by derivation within this "session"
+ //
+ bool wasAudioFileRecentlyDerived(AudioFileId id);
+
+ // Indicate that a new "session" has started from the point of
+ // view of recorded and derived audio files (e.g. that the
+ // document has been saved)
+ //
+ void resetRecentlyCreatedFiles();
+
+ // Create an empty file "derived from" the source (used by e.g. stretcher)
+ //
+ AudioFile *createDerivedAudioFile(AudioFileId source,
+ const char *prefix);
+
+ // return the last file in the vector - the last created
+ //
+ AudioFile* getLastAudioFile();
+
+ // Export to XML
+ //
+ virtual std::string toXmlString();
+
+ // Convenience function generate all previews on the audio file.
+ //
+ void generatePreviews();
+ // throw BadSoundFileException, BadPeakFileException
+
+ // Generate for a single audio file
+ //
+ bool generatePreview(AudioFileId id);
+ // throw BadSoundFileException, BadPeakFileException
+
+ // Get a preview for an AudioFile adjusted to Segment start and
+ // end parameters (assuming they fall within boundaries).
+ //
+ // We can get back a set of values (floats) or a Pixmap if we
+ // supply the details.
+ //
+ std::vector<float> getPreview(AudioFileId id,
+ const RealTime &startTime,
+ const RealTime &endTime,
+ int width,
+ bool withMinima);
+ // throw BadPeakFileException, BadAudioPathException
+
+ // Draw a fixed size (fixed by QPixmap) preview of an audio file
+ //
+ void drawPreview(AudioFileId id,
+ const RealTime &startTime,
+ const RealTime &endTime,
+ QPixmap *pixmap);
+ // throw BadPeakFileException, BadAudioPathException
+
+ // Usually used to show how an audio Segment makes up part of
+ // an audio file.
+ //
+ void drawHighlightedPreview(AudioFileId it,
+ const RealTime &startTime,
+ const RealTime &endTime,
+ const RealTime &highlightStart,
+ const RealTime &highlightEnd,
+ QPixmap *pixmap);
+ // throw BadPeakFileException, BadAudioPathException
+
+ // Get a short file name from a long one (with '/'s)
+ //
+ std::string getShortFilename(const std::string &fileName);
+
+ // Get a directory from a full file path
+ //
+ std::string getDirectory(const std::string &path);
+
+ // Attempt to subsititute a tilde '~' for a home directory
+ // to make paths a little more generic when saving. Also
+ // provide the inverse function as convenience here.
+ //
+ std::string substituteHomeForTilde(const std::string &path);
+ std::string substituteTildeForHome(const std::string &path);
+
+ // Show entries for debug purposes
+ //
+ void print();
+
+ // Get a split point vector from a peak file
+ //
+ std::vector<SplitPointPair>
+ getSplitPoints(AudioFileId id,
+ const RealTime &startTime,
+ const RealTime &endTime,
+ int threshold,
+ const RealTime &minTime = RealTime(0, 100000000));
+ // throw BadPeakFileException, BadAudioPathException
+
+ // Get the peak file manager
+ //
+ const PeakFileManager& getPeakFileManager() const { return m_peakManager; }
+
+ // Get the peak file manager
+ //
+ PeakFileManager& getPeakFileManager() { return m_peakManager; }
+
+ int getExpectedSampleRate() const { return m_expectedSampleRate; }
+ void setExpectedSampleRate(int rate) { m_expectedSampleRate = rate; }
+
+ std::set<int> getActualSampleRates() const;
+
+signals:
+ void setProgress(int);
+ void setOperationName(QString);
+
+public slots:
+ // Cancel a running preview
+ //
+ void slotStopPreview();
+
+ void slotStopImport();
+
+private:
+ std::string getFileInPath(const std::string &file);
+
+ AudioFileId getFirstUnusedID();
+
+ std::vector<AudioFile*> m_audioFiles;
+ std::string m_audioPath;
+
+ PeakFileManager m_peakManager;
+
+ // All audio files are stored in m_audioFiles. These additional
+ // sets of pointers just refer to those that have been created by
+ // recording or derivations within the current session, and thus
+ // that the user may wish to remove at the end of the session if
+ // the document is not saved.
+ std::set<AudioFile *> m_recordedAudioFiles;
+ std::set<AudioFile *> m_derivedAudioFiles;
+
+ KProcess *m_importProcess;
+
+ int m_expectedSampleRate;
+};
+
+}
+
+#endif // _AUDIOFILEMANAGER_H_
diff --git a/src/sound/AudioFileTimeStretcher.cpp b/src/sound/AudioFileTimeStretcher.cpp
new file mode 100644
index 0000000..d5b2321
--- /dev/null
+++ b/src/sound/AudioFileTimeStretcher.cpp
@@ -0,0 +1,268 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
+
+/*
+ Rosegarden
+ A MIDI and audio sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#include "AudioFileTimeStretcher.h"
+#include "AudioTimeStretcher.h"
+#include "AudioFileManager.h"
+#include "WAVAudioFile.h"
+#include "base/RealTime.h"
+
+#include <kapplication.h>
+
+#include <iostream>
+#include <fstream>
+
+namespace Rosegarden {
+
+
+AudioFileTimeStretcher::AudioFileTimeStretcher(AudioFileManager *manager) :
+ m_manager(manager),
+ m_timestretchCancelled(false)
+{
+}
+
+AudioFileTimeStretcher::~AudioFileTimeStretcher()
+{
+}
+
+AudioFileId
+AudioFileTimeStretcher::getStretchedAudioFile(AudioFileId source,
+ float ratio)
+{
+ AudioFile *sourceFile = m_manager->getAudioFile(source);
+ if (!sourceFile) {
+ throw SoundFile::BadSoundFileException
+ ("<unknown source>",
+ "Source file not found in AudioFileTimeStretcher::getStretchedAudioFile");
+ }
+
+ std::cerr << "AudioFileTimeStretcher: got source file id " << source
+ << ", name " << sourceFile->getFilename() << std::endl;
+
+ AudioFile *file = m_manager->createDerivedAudioFile(source, "stretch");
+ if (!file) {
+ throw AudioFileManager::BadAudioPathException(m_manager->getAudioPath());
+ }
+
+ std::cerr << "AudioFileTimeStretcher: got derived file id " << file->getId()
+ << ", name " << file->getFilename() << std::endl;
+
+ std::ifstream streamIn(sourceFile->getFilename().c_str(),
+ std::ios::in | std::ios::binary);
+ if (!streamIn) {
+ throw SoundFile::BadSoundFileException
+ (file->getFilename().c_str(),
+ "Failed to open source stream for time stretcher");
+ }
+
+ //!!!
+ //...
+ // Need to make SoundDriver::getAudioRecFileFormat available?
+ // -- the sound file classes should just have a float interface
+ // (like libsndfile, or hey!, we could use libsndfile...)
+
+ WAVAudioFile writeFile
+ (file->getFilename(),
+ sourceFile->getChannels(),
+ sourceFile->getSampleRate(),
+ sourceFile->getSampleRate() * 4 * sourceFile->getChannels(),
+ 4 * sourceFile->getChannels(),
+ 32);
+
+ if (!writeFile.write()) {
+ throw AudioFileManager::BadAudioPathException
+ (file->getFilename());
+ }
+
+ int obs = 1024;
+ int ibs = obs / ratio;
+ int ch = sourceFile->getChannels();
+ int sr = sourceFile->getSampleRate();
+
+ AudioTimeStretcher stretcher(sr, ch, ratio, true, obs);
+
+ // We'll first prime the timestretcher with half its window size
+ // of silence, an amount which we then discard at the start of the
+ // output (as well as its own processing latency). Really the
+ // timestretcher should handle this itself and report it in its
+ // own latency calculation
+
+ size_t padding = stretcher.getWindowSize()/2;
+
+ char *ebf = (char *)alloca
+ (ch * ibs * sourceFile->getBytesPerFrame());
+
+ std::vector<float *> dbfs;
+ for (int c = 0; c < ch; ++c) {
+ dbfs.push_back((float *)alloca((ibs > padding ? ibs : padding)
+ * sizeof(float)));
+ }
+
+ float **ibfs = (float **)alloca(ch * sizeof(float *));
+ float **obfs = (float **)alloca(ch * sizeof(float *));
+
+ for (int c = 0; c < ch; ++c) {
+ ibfs[c] = dbfs[c];
+ }
+
+ for (int c = 0; c < ch; ++c) {
+ obfs[c] = (float *)alloca(obs * sizeof(float));
+ }
+
+ char *oebf = (char *)alloca(ch * obs * sizeof(float));
+
+ int totalIn = 0, totalOut = 0;
+
+ for (int c = 0; c < ch; ++c) {
+ for (size_t i = 0; i < padding; ++i) {
+ ibfs[c][i] = 0.f;
+ }
+ }
+ stretcher.putInput(ibfs, padding);
+
+ RealTime totalTime = sourceFile->getLength();
+ long fileTotalIn = RealTime::realTime2Frame
+ (totalTime, sourceFile->getSampleRate());
+ int progressCount = 0;
+
+ long expectedOut = ceil(fileTotalIn * ratio);
+
+ m_timestretchCancelled = false;
+ bool inputExhausted = false;
+
+ sourceFile->scanTo(&streamIn, RealTime::zeroTime);
+
+ while (1) {
+
+ if (m_timestretchCancelled) {
+ std::cerr << "AudioFileTimeStretcher::getStretchedAudioFile: cancelled" << std::endl;
+ throw CancelledException();
+ }
+
+ unsigned int thisRead = 0;
+
+ if (!inputExhausted) {
+ thisRead = sourceFile->getSampleFrames(&streamIn, ebf, ibs);
+ if (thisRead < ibs) inputExhausted = true;
+ }
+
+ if (thisRead == 0) {
+ if (totalOut >= expectedOut) break;
+ else {
+ // run out of input data, continue feeding zeroes until
+ // we have enough output data
+ for (int c = 0; c < ch; ++c) {
+ for (int i = 0; i < ibs; ++i) {
+ ibfs[c][i] = 0.f;
+ }
+ }
+ thisRead = ibs;
+ }
+ }
+
+ if (!sourceFile->decode((unsigned char *)ebf,
+ thisRead * sourceFile->getBytesPerFrame(),
+ sr, ch,
+ thisRead, dbfs, false)) {
+ std::cerr << "ERROR: Stupid audio file class failed to decode its own output" << std::endl;
+ break;
+ }
+
+ stretcher.putInput(ibfs, thisRead);
+ totalIn += thisRead;
+
+ unsigned int available = stretcher.getAvailableOutputSamples();
+
+ while (available > 0) {
+
+ unsigned int count = available;
+ if (count > obs) count = obs;
+
+ if (padding > 0) {
+ if (count <= padding) {
+ stretcher.getOutput(obfs, count);
+ padding -= count;
+ available -= count;
+ continue;
+ } else {
+ stretcher.getOutput(obfs, padding);
+ count -= padding;
+ available -= padding;
+ padding = 0;
+ }
+ }
+
+ stretcher.getOutput(obfs, count);
+
+ char *encodePointer = oebf;
+ for (int i = 0; i < count; ++i) {
+ for (int c = 0; c < ch; ++c) {
+ float sample = obfs[c][i];
+ *(float *)encodePointer = sample;
+ encodePointer += sizeof(float);
+ }
+ }
+
+ if (totalOut < expectedOut &&
+ totalOut + count > expectedOut) {
+ count = expectedOut - totalOut;
+ }
+
+ writeFile.appendSamples(oebf, count);
+ totalOut += count;
+ available -= count;
+
+ if (totalOut >= expectedOut) break;
+ }
+
+ if (++progressCount == 100) {
+ int progress = int
+ ((100.f * float(totalIn)) / float(fileTotalIn));
+ emit setProgress(progress);
+ kapp->processEvents();
+ progressCount = 0;
+ }
+ }
+
+ emit setProgress(100);
+ kapp->processEvents();
+ writeFile.close();
+
+ std::cerr << "AudioFileTimeStretcher::getStretchedAudioFile: success, id is "
+ << file->getId() << std::endl;
+
+ return file->getId();
+}
+
+void
+AudioFileTimeStretcher::slotStopTimestretch()
+{
+ m_timestretchCancelled = true;
+}
+
+
+}
+
+#include "AudioFileTimeStretcher.moc"
+
diff --git a/src/sound/AudioFileTimeStretcher.h b/src/sound/AudioFileTimeStretcher.h
new file mode 100644
index 0000000..c02e286
--- /dev/null
+++ b/src/sound/AudioFileTimeStretcher.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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _AUDIO_FILE_TIME_STRETCHER_H_
+#define _AUDIO_FILE_TIME_STRETCHER_H_
+
+#include <qobject.h>
+#include "AudioFile.h"
+#include "base/Exception.h"
+
+namespace Rosegarden {
+
+class AudioFileManager;
+
+class AudioFileTimeStretcher : public QObject
+{
+ Q_OBJECT
+
+public:
+ AudioFileTimeStretcher(AudioFileManager *mgr);
+ virtual ~AudioFileTimeStretcher();
+
+ /**
+ * Stretch an audio file and return the ID of the stretched
+ * version. May throw SoundFile::BadSoundFileException,
+ * AudioFileManager::BadAudioPathException, CancelledException
+ */
+ AudioFileId getStretchedAudioFile(AudioFileId source,
+ float ratio);
+
+ class CancelledException : public Exception
+ {
+ public:
+ CancelledException() : Exception("Cancelled") { }
+ ~CancelledException() throw() { }
+ };
+
+signals:
+ void setProgress(int);
+
+public slots:
+ /**
+ * Cancel an ongoing getStretchedAudioFile
+ */
+ void slotStopTimestretch();
+
+protected:
+ AudioFileManager *m_manager;
+
+ bool m_timestretchCancelled;
+};
+
+}
+
+#endif
diff --git a/src/sound/AudioPlayQueue.cpp b/src/sound/AudioPlayQueue.cpp
new file mode 100644
index 0000000..2bd07c3
--- /dev/null
+++ b/src/sound/AudioPlayQueue.cpp
@@ -0,0 +1,501 @@
+
+// -*- c-basic-offset: 4 -*-
+
+/*
+ Rosegarden
+ A sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <bownie@bownie.com>
+
+ 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 "AudioPlayQueue.h"
+#include "misc/Debug.h"
+#include "PlayableAudioFile.h"
+#include "Profiler.h"
+
+//#define DEBUG_AUDIO_PLAY_QUEUE 1
+//#define FINE_DEBUG_AUDIO_PLAY_QUEUE 1
+
+namespace Rosegarden
+{
+
+
+static inline unsigned int instrumentId2Index(InstrumentId id)
+{
+ if (id < AudioInstrumentBase)
+ return 0;
+ else
+ return (id - AudioInstrumentBase);
+}
+
+bool
+AudioPlayQueue::FileTimeCmp::operator()(const PlayableAudioFile &f1,
+ const PlayableAudioFile &f2) const
+{
+ return operator()(&f1, &f2);
+}
+
+bool
+AudioPlayQueue::FileTimeCmp::operator()(const PlayableAudioFile *f1,
+ const PlayableAudioFile *f2) const
+{
+ RealTime t1 = f1->getStartTime(), t2 = f2->getStartTime();
+ if (t1 < t2)
+ return true;
+ else if (t2 < t1)
+ return false;
+ else
+ return f1 < f2;
+}
+
+
+AudioPlayQueue::AudioPlayQueue() :
+ m_maxBuffers(0)
+{
+ // nothing to do
+}
+
+AudioPlayQueue::~AudioPlayQueue()
+{
+ std::cerr << "AudioPlayQueue::~AudioPlayQueue()" << std::endl;
+ clear();
+}
+
+void
+AudioPlayQueue::addScheduled(PlayableAudioFile *file)
+{
+ if (m_files.find(file) != m_files.end()) {
+ std::cerr << "WARNING: AudioPlayQueue::addScheduled("
+ << file << "): already in queue" << std::endl;
+ return ;
+ }
+
+ m_files.insert(file);
+
+ RealTime startTime = file->getStartTime();
+ RealTime endTime = file->getStartTime() + file->getDuration();
+
+ InstrumentId instrument = file->getInstrument();
+ unsigned int index = instrumentId2Index(instrument);
+
+ while (m_instrumentIndex.size() <= index) {
+ m_instrumentIndex.push_back(ReverseFileMap());
+ }
+
+#ifdef DEBUG_AUDIO_PLAY_QUEUE
+ std::cerr << "AudioPlayQueue[" << this << "]::addScheduled(" << file << "): start " << file->getStartTime() << ", end " << file->getEndTime() << ", slots: " << std::endl;
+#endif
+
+ for (int i = startTime.sec; i <= endTime.sec; ++i) {
+ m_index[i].push_back(file);
+ m_instrumentIndex[index][i].push_back(file);
+ if (!file->isSmallFile()) {
+ m_counts[i] += file->getTargetChannels();
+ if (m_counts[i] > m_maxBuffers) {
+ m_maxBuffers = m_counts[i];
+ }
+ }
+#ifdef DEBUG_AUDIO_PLAY_QUEUE
+ std::cerr << i << " ";
+#endif
+
+ }
+
+#ifdef DEBUG_AUDIO_PLAY_QUEUE
+ std::cerr << std::endl << "(max buffers now "
+ << m_maxBuffers << ")" << std::endl;
+#endif
+}
+
+void
+AudioPlayQueue::addUnscheduled(PlayableAudioFile *file)
+{
+#ifdef DEBUG_AUDIO_PLAY_QUEUE
+ std::cerr << "AudioPlayQueue[" << this << "]::addUnscheduled(" << file << "): start " << file->getStartTime() << ", end " << file->getEndTime() << ", instrument " << file->getInstrument() << std::endl;
+#endif
+
+ m_unscheduled.push_back(file);
+
+#ifdef DEBUG_AUDIO_PLAY_QUEUE
+
+ std::cerr << "AudioPlayQueue[" << this << "]::addUnscheduled: now " << m_unscheduled.size() << " unscheduled files" << std::endl;
+#endif
+
+}
+
+void
+AudioPlayQueue::erase(PlayableAudioFile *file)
+{
+#ifdef DEBUG_AUDIO_PLAY_QUEUE
+ std::cerr << "AudioPlayQueue::erase(" << file << "): start " << file->getStartTime() << ", end " << file->getEndTime() << std::endl;
+#endif
+
+ FileSet::iterator fi = m_files.find(file);
+ if (fi == m_files.end()) {
+ for (FileList::iterator fli = m_unscheduled.begin();
+ fli != m_unscheduled.end(); ++fli) {
+ if (*fli == file) {
+ m_unscheduled.erase(fli);
+ delete file;
+ return ;
+ }
+ }
+ return ;
+ }
+ m_files.erase(fi);
+
+ InstrumentId instrument = file->getInstrument();
+ unsigned int index = instrumentId2Index(instrument);
+
+ for (ReverseFileMap::iterator mi = m_instrumentIndex[index].begin();
+ mi != m_instrumentIndex[index].end(); ++mi) {
+
+ for (FileVector::iterator fi = mi->second.begin();
+ fi != mi->second.end(); ++fi) {
+
+ if (*fi == file) {
+ mi->second.erase(fi);
+ if (m_counts[mi->first] > 0)
+ --m_counts[mi->first];
+ break;
+ }
+ }
+ }
+
+ for (ReverseFileMap::iterator mi = m_index.begin();
+ mi != m_index.end(); ++mi) {
+
+ for (FileVector::iterator fi = mi->second.begin();
+ fi != mi->second.end(); ++fi) {
+
+ if (*fi == file) {
+ mi->second.erase(fi);
+ if (m_counts[mi->first] > 0)
+ --m_counts[mi->first];
+ break;
+ }
+ }
+ }
+
+ delete file;
+}
+
+void
+AudioPlayQueue::clear()
+{
+#ifdef DEBUG_AUDIO_PLAY_QUEUE
+ std::cerr << "AudioPlayQueue::clear()" << std::endl;
+#endif
+
+ while (m_files.begin() != m_files.end()) {
+ delete *m_files.begin();
+ m_files.erase(m_files.begin());
+ }
+
+ while (m_unscheduled.begin() != m_unscheduled.end()) {
+ delete *m_unscheduled.begin();
+ m_unscheduled.erase(m_unscheduled.begin());
+ }
+
+ m_instrumentIndex.clear();
+ m_index.clear();
+ m_counts.clear();
+ m_maxBuffers = 0;
+}
+
+bool
+AudioPlayQueue::empty() const
+{
+ return m_unscheduled.empty() && m_files.empty();
+}
+
+size_t
+AudioPlayQueue::size() const
+{
+ return m_unscheduled.size() + m_files.size();
+}
+
+void
+AudioPlayQueue::getPlayingFiles(const RealTime &sliceStart,
+ const RealTime &sliceDuration,
+ FileSet &playing) const
+{
+ // Profiler profiler("AudioPlayQueue::getPlayingFiles");
+
+ // This one needs to be quick.
+
+ playing.clear();
+
+ RealTime sliceEnd = sliceStart + sliceDuration;
+
+ for (int i = sliceStart.sec; i <= sliceEnd.sec; ++i) {
+
+ ReverseFileMap::const_iterator mi(m_index.find(i));
+ if (mi == m_index.end())
+ continue;
+
+ for (FileVector::const_iterator fi = mi->second.begin();
+ fi != mi->second.end(); ++fi) {
+
+ PlayableAudioFile *f = *fi;
+
+ if (f->getStartTime() > sliceEnd ||
+ f->getStartTime() + f->getDuration() <= sliceStart)
+ continue;
+
+#ifdef FINE_DEBUG_AUDIO_PLAY_QUEUE
+
+ std::cerr << "... found " << f << " in slot " << i << std::endl;
+#endif
+
+ playing.insert(f);
+ }
+ }
+
+ for (FileList::const_iterator fli = m_unscheduled.begin();
+ fli != m_unscheduled.end(); ++fli) {
+ PlayableAudioFile *file = *fli;
+ if (file->getStartTime() <= sliceEnd &&
+ file->getStartTime() + file->getDuration() > sliceStart) {
+ playing.insert(file);
+ }
+ }
+
+#ifdef FINE_DEBUG_AUDIO_PLAY_QUEUE
+ if (playing.size() > 0) {
+ std::cerr << "AudioPlayQueue::getPlayingFiles(" << sliceStart << ","
+ << sliceDuration << "): total "
+ << playing.size() << " files" << std::endl;
+ }
+#endif
+}
+
+void
+AudioPlayQueue::getPlayingFilesForInstrument(const RealTime &sliceStart,
+ const RealTime &sliceDuration,
+ InstrumentId instrumentId,
+ PlayableAudioFile **playing,
+ size_t &size) const
+{
+#ifdef FINE_DEBUG_AUDIO_PLAY_QUEUE
+ bool printed = false;
+ Profiler profiler("AudioPlayQueue::getPlayingFilesForInstrument", true);
+#endif
+
+ // This one needs to be quick.
+
+ size_t written = 0;
+
+ RealTime sliceEnd = sliceStart + sliceDuration;
+
+ unsigned int index = instrumentId2Index(instrumentId);
+ if (index >= m_instrumentIndex.size()) {
+ goto unscheduled; // nothing scheduled here
+ }
+
+ for (int i = sliceStart.sec; i <= sliceEnd.sec; ++i) {
+
+ ReverseFileMap::const_iterator mi
+ (m_instrumentIndex[index].find(i));
+
+ if (mi == m_instrumentIndex[index].end())
+ continue;
+
+ for (FileVector::const_iterator fi = mi->second.begin();
+ fi != mi->second.end(); ++fi) {
+
+ PlayableAudioFile *f = *fi;
+
+ if (f->getInstrument() != instrumentId)
+ continue;
+
+#ifdef FINE_DEBUG_AUDIO_PLAY_QUEUE
+
+ if (!printed) {
+ std::cerr << "AudioPlayQueue::getPlayingFilesForInstrument(" << sliceStart
+ << ", " << sliceDuration << ", " << instrumentId << ")"
+ << std::endl;
+ printed = true;
+ }
+#endif
+
+ if (f->getStartTime() > sliceEnd ||
+ f->getEndTime() <= sliceStart) {
+
+#ifdef FINE_DEBUG_AUDIO_PLAY_QUEUE
+ std::cerr << "... rejected " << f << " in slot " << i << std::endl;
+ if (f->getStartTime() > sliceEnd) {
+ std::cerr << "(" << f->getStartTime() << " > " << sliceEnd
+ << ")" << std::endl;
+ } else {
+ std::cerr << "(" << f->getEndTime() << " <= " << sliceStart
+ << ")" << std::endl;
+ }
+#endif
+
+ continue;
+ }
+
+#ifdef FINE_DEBUG_AUDIO_PLAY_QUEUE
+ std::cerr << "... found " << f << " in slot " << i << " ("
+ << f->getStartTime() << " -> " << f->getEndTime()
+ << ")" << std::endl;
+#endif
+
+ size_t j = 0;
+ for (j = 0; j < written; ++j) {
+ if (playing[j] == f)
+ break;
+ }
+ if (j < written)
+ break; // already have it
+
+ if (written >= size) {
+#ifdef FINE_DEBUG_AUDIO_PLAY_QUEUE
+ std::cerr << "No room to write it!" << std::endl;
+#endif
+
+ break;
+ }
+
+ playing[written++] = f;
+ }
+ }
+
+unscheduled:
+
+ for (FileList::const_iterator fli = m_unscheduled.begin();
+ fli != m_unscheduled.end(); ++fli) {
+
+ PlayableAudioFile *f = *fli;
+
+ if (f->getInstrument() != instrumentId) {
+#ifdef FINE_DEBUG_AUDIO_PLAY_QUEUE
+ std::cerr << "rejecting unscheduled " << f << " as wrong instrument ("
+ << f->getInstrument() << " != " << instrumentId << ")" << std::endl;
+#endif
+
+ continue;
+ }
+
+#ifdef FINE_DEBUG_AUDIO_PLAY_QUEUE
+ if (!printed) {
+ std::cerr << "AudioPlayQueue::getPlayingFilesForInstrument(" << sliceStart
+ << ", " << sliceDuration << ", " << instrumentId << ")"
+ << std::endl;
+ printed = true;
+ }
+#endif
+
+ if (f->getStartTime() <= sliceEnd &&
+ f->getStartTime() + f->getDuration() > sliceStart) {
+
+#ifdef FINE_DEBUG_AUDIO_PLAY_QUEUE
+ std::cerr << "... found " << f << " in unscheduled list ("
+ << f->getStartTime() << " -> " << f->getEndTime()
+ << ")" << std::endl;
+#endif
+
+ if (written >= size)
+ break;
+ playing[written++] = f;
+
+#ifdef FINE_DEBUG_AUDIO_PLAY_QUEUE
+
+ } else {
+
+ std::cerr << "... rejected " << f << " in unscheduled list" << std::endl;
+ if (f->getStartTime() > sliceEnd) {
+ std::cerr << "(" << f->getStartTime() << " > " << sliceEnd
+ << ")" << std::endl;
+ } else {
+ std::cerr << "(" << f->getEndTime() << " <= " << sliceStart
+ << ")" << std::endl;
+ }
+#endif
+
+ }
+ }
+
+#ifdef FINE_DEBUG_AUDIO_PLAY_QUEUE
+ if (written > 0) {
+ std::cerr << "AudioPlayQueue::getPlayingFilesForInstrument: total "
+ << written << " files" << std::endl;
+ }
+#endif
+
+ size = written;
+}
+
+bool
+AudioPlayQueue::haveFilesForInstrument(InstrumentId instrumentId) const
+{
+#ifdef FINE_DEBUG_AUDIO_PLAY_QUEUE
+ std::cerr << "AudioPlayQueue::haveFilesForInstrument(" << instrumentId << ")...";
+#endif
+
+ unsigned int index = instrumentId2Index(instrumentId);
+
+ if (index < m_instrumentIndex.size() &&
+ !m_instrumentIndex[index].empty()) {
+#ifdef FINE_DEBUG_AUDIO_PLAY_QUEUE
+ std::cerr << " yes (scheduled)" << std::endl;
+#endif
+
+ return true;
+ }
+
+ for (FileList::const_iterator fli = m_unscheduled.begin();
+ fli != m_unscheduled.end(); ++fli) {
+ PlayableAudioFile *file = *fli;
+ if (file->getInstrument() == instrumentId) {
+#ifdef FINE_DEBUG_AUDIO_PLAY_QUEUE
+ std::cerr << " yes (unscheduled)" << std::endl;
+#endif
+
+ return true;
+ }
+ }
+
+#ifdef FINE_DEBUG_AUDIO_PLAY_QUEUE
+ std::cerr << " no" << std::endl;
+#endif
+
+ return false;
+}
+
+const AudioPlayQueue::FileSet &
+AudioPlayQueue::getAllScheduledFiles() const
+{
+#ifdef DEBUG_AUDIO_PLAY_QUEUE
+ std::cerr << "AudioPlayQueue[" << this << "]::getAllScheduledFiles: have " << m_files.size() << " files" << std::endl;
+#endif
+
+ return m_files;
+}
+
+const AudioPlayQueue::FileList &
+AudioPlayQueue::getAllUnscheduledFiles() const
+{
+#ifdef DEBUG_AUDIO_PLAY_QUEUE
+ std::cerr << "AudioPlayQueue[" << this << "]::getAllUnscheduledFiles: have " << m_unscheduled.size() << " files" << std::endl;
+#endif
+
+ return m_unscheduled;
+}
+
+
+}
+
diff --git a/src/sound/AudioPlayQueue.h b/src/sound/AudioPlayQueue.h
new file mode 100644
index 0000000..2a7067c
--- /dev/null
+++ b/src/sound/AudioPlayQueue.h
@@ -0,0 +1,168 @@
+// -*- c-basic-offset: 4 -*-
+
+/*
+ Rosegarden
+ A sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <bownie@bownie.com>
+
+ 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_PLAY_QUEUE_H_
+#define _AUDIO_PLAY_QUEUE_H_
+
+#include "RealTime.h"
+#include "Instrument.h"
+
+#include <set>
+#include <vector>
+#include <map>
+#include <list>
+
+namespace Rosegarden
+{
+
+class PlayableAudioFile;
+
+/**
+ * An ordered list of PlayableAudioFiles that does not aim to be quick
+ * to add to or remove files from, but that aims to quickly answer the
+ * question of which files are playing within a given time slice.
+ *
+ * Note that there is no locking between audio file add/remove and
+ * lookup. Add/remove should only be carried out when it is known
+ * that no threads will be performing lookup.
+ */
+
+class AudioPlayQueue
+{
+public:
+ AudioPlayQueue();
+ virtual ~AudioPlayQueue();
+
+ struct FileTimeCmp {
+ bool operator()(const PlayableAudioFile &, const PlayableAudioFile &) const;
+ bool operator()(const PlayableAudioFile *, const PlayableAudioFile *) const;
+ };
+ typedef std::set<PlayableAudioFile *, FileTimeCmp> FileSet;
+ typedef std::list<PlayableAudioFile *> FileList;
+
+ /**
+ * Add a file to the queue. AudioPlayQueue takes ownership of the
+ * file and will delete it when removed.
+ */
+ void addScheduled(PlayableAudioFile *file);
+
+ /**
+ * Add a file to the unscheduled list. AudioPlayQueue takes
+ * ownership of the file and will delete it when removed.
+ * Unscheduled files will be returned along with schuled ones for
+ * normal lookups, but everything will be less efficient when
+ * there are unscheduled files on the queue. This is intended
+ * for asynchronous (preview) playback.
+ */
+ void addUnscheduled(PlayableAudioFile *file);
+
+ /**
+ * Remove a scheduled or unscheduled file from the queue and
+ * delete it.
+ */
+ void erase(PlayableAudioFile *file);
+
+ /**
+ * Remove all files and delete them.
+ */
+ void clear();
+
+ /**
+ * Return true if the queue is empty.
+ */
+ bool empty() const;
+
+ /**
+ * Return the total number of files in the queue. (May be slow.)
+ */
+ size_t size() const;
+
+ /**
+ * Look up the files playing during a given slice and return them
+ * in the passed FileSet. The pointers returned are still owned
+ * by me and the caller should not delete them.
+ */
+ void getPlayingFiles(const RealTime &sliceStart,
+ const RealTime &sliceDuration,
+ FileSet &) const;
+
+ /**
+ * Look up the files playing during a given slice on a given
+ * instrument and return them in the passed array. The size arg
+ * gives the available size of the array and is used to return the
+ * number of file pointers written. The pointers returned are
+ * still owned by me and the caller should not delete them.
+ */
+ void getPlayingFilesForInstrument(const RealTime &sliceStart,
+ const RealTime &sliceDuration,
+ InstrumentId instrumentId,
+ PlayableAudioFile **files,
+ size_t &size) const;
+
+ /**
+ * Return true if at least one scheduled or unscheduled file is
+ * associated with the given instrument somewhere in the queue.
+ */
+ bool haveFilesForInstrument(InstrumentId instrumentId) const;
+
+ /**
+ * Return a (shared reference to an) ordered set of all files on
+ * the scheduled queue.
+ */
+ const FileSet &getAllScheduledFiles() const;
+
+ /**
+ * Return a (shared reference to an) ordered set of all files on
+ * the unscheduled queue.
+ */
+ const FileList &getAllUnscheduledFiles() const;
+
+ /**
+ * Get an approximate (but always pessimistic) estimate of the
+ * number of ring buffers required for the current queue -- that
+ * is, the maximum possible number of audio channels playing at
+ * once from non-small-file-cached-files.
+ */
+ size_t getMaxBuffersRequired() const { return m_maxBuffers; }
+
+private:
+ FileSet m_files;
+
+ typedef std::vector<PlayableAudioFile *> FileVector;
+ typedef std::map<int, FileVector> ReverseFileMap;
+ ReverseFileMap m_index;
+
+ typedef std::vector<ReverseFileMap> InstrumentReverseFileMap;
+ InstrumentReverseFileMap m_instrumentIndex;
+
+ FileList m_unscheduled;
+
+ typedef std::map<int, size_t> FileCountMap;
+ FileCountMap m_counts;
+
+ size_t m_maxBuffers;
+};
+
+
+}
+
+#endif
+
diff --git a/src/sound/AudioProcess.cpp b/src/sound/AudioProcess.cpp
new file mode 100644
index 0000000..9b44e13
--- /dev/null
+++ b/src/sound/AudioProcess.cpp
@@ -0,0 +1,2463 @@
+// -*- c-basic-offset: 4 -*-
+
+/*
+ Rosegarden
+ A sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <bownie@bownie.com>
+
+ 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 "AudioProcess.h"
+
+#include "RunnablePluginInstance.h"
+#include "PlayableAudioFile.h"
+#include "RecordableAudioFile.h"
+#include "WAVAudioFile.h"
+#include "MappedStudio.h"
+#include "Profiler.h"
+#include "AudioLevel.h"
+#include "AudioPlayQueue.h"
+#include "PluginFactory.h"
+
+#include <sys/time.h>
+#include <pthread.h>
+
+#include <cmath>
+
+//#define DEBUG_THREAD_CREATE_DESTROY 1
+//#define DEBUG_BUSS_MIXER 1
+//#define DEBUG_MIXER 1
+//#define DEBUG_MIXER_LIGHTWEIGHT 1
+//#define DEBUG_LOCKS 1
+//#define DEBUG_READER 1
+//#define DEBUG_WRITER 1
+
+namespace Rosegarden
+{
+
+/* Branch-free optimizer-resistant denormal killer courtesy of Simon
+ Jenkins on LAD: */
+
+static inline float flushToZero(volatile float f)
+{
+ f += 9.8607615E-32f;
+ return f - 9.8607615E-32f;
+}
+
+static inline void denormalKill(float *buffer, int size)
+{
+ for (int i = 0; i < size; ++i) {
+ buffer[i] = flushToZero(buffer[i]);
+ }
+}
+
+AudioThread::AudioThread(std::string name,
+ SoundDriver *driver,
+ unsigned int sampleRate) :
+ m_name(name),
+ m_driver(driver),
+ m_sampleRate(sampleRate),
+ m_thread(0),
+ m_running(false),
+ m_exiting(false)
+{
+#ifdef DEBUG_THREAD_CREATE_DESTROY
+ std::cerr << "AudioThread::AudioThread() [" << m_name << "]" << std::endl;
+#endif
+
+ pthread_mutex_t initialisingMutex = PTHREAD_MUTEX_INITIALIZER;
+ memcpy(&m_lock, &initialisingMutex, sizeof(pthread_mutex_t));
+
+ pthread_cond_t initialisingCondition = PTHREAD_COND_INITIALIZER;
+ memcpy(&m_condition, &initialisingCondition, sizeof(pthread_cond_t));
+}
+
+AudioThread::~AudioThread()
+{
+#ifdef DEBUG_THREAD_CREATE_DESTROY
+ std::cerr << "AudioThread::~AudioThread() [" << m_name << "]" << std::endl;
+#endif
+
+ if (m_thread) {
+ pthread_mutex_destroy(&m_lock);
+ m_thread = 0;
+ }
+
+#ifdef DEBUG_THREAD_CREATE_DESTROY
+ std::cerr << "AudioThread::~AudioThread() exiting" << std::endl;
+#endif
+}
+
+void
+AudioThread::run()
+{
+#ifdef DEBUG_THREAD_CREATE_DESTROY
+ std::cerr << m_name << "::run()" << std::endl;
+#endif
+
+ pthread_attr_t attr;
+ pthread_attr_init(&attr);
+
+ int priority = getPriority();
+
+ if (priority > 0) {
+
+ if (pthread_attr_setschedpolicy(&attr, SCHED_FIFO)) {
+
+ std::cerr << m_name << "::run: WARNING: couldn't set FIFO scheduling "
+ << "on new thread" << std::endl;
+ pthread_attr_init(&attr); // reset to safety
+
+ } else {
+
+ struct sched_param param;
+ memset(&param, 0, sizeof(struct sched_param));
+ param.sched_priority = priority;
+
+ if (pthread_attr_setschedparam(&attr, &param)) {
+ std::cerr << m_name << "::run: WARNING: couldn't set priority "
+ << priority << " on new thread" << std::endl;
+ pthread_attr_init(&attr); // reset to safety
+ }
+ }
+ }
+
+ pthread_attr_setstacksize(&attr, 1048576);
+ int rv = pthread_create(&m_thread, &attr, staticThreadRun, this);
+
+ if (rv != 0 && priority > 0) {
+#ifdef DEBUG_THREAD_CREATE_DESTROY
+ std::cerr << m_name << "::run: WARNING: unable to start RT thread;"
+ << "\ntrying again with normal scheduling" << std::endl;
+#endif
+
+ pthread_attr_init(&attr);
+ pthread_attr_setstacksize(&attr, 1048576);
+ rv = pthread_create(&m_thread, &attr, staticThreadRun, this);
+ }
+
+ if (rv != 0) {
+ // This is quite fatal.
+ std::cerr << m_name << "::run: ERROR: failed to start thread!" << std::endl;
+ ::exit(1);
+ }
+
+ m_running = true;
+
+#ifdef DEBUG_THREAD_CREATE_DESTROY
+
+ std::cerr << m_name << "::run() done" << std::endl;
+#endif
+}
+
+void
+AudioThread::terminate()
+{
+#ifdef DEBUG_THREAD_CREATE_DESTROY
+ std::string name = m_name;
+ std::cerr << name << "::terminate()" << std::endl;
+#endif
+
+ m_running = false;
+
+ if (m_thread) {
+
+ pthread_cancel(m_thread);
+
+#ifdef DEBUG_THREAD_CREATE_DESTROY
+
+ std::cerr << name << "::terminate(): cancel requested" << std::endl;
+#endif
+
+ int rv = pthread_join(m_thread, 0);
+
+#ifdef DEBUG_THREAD_CREATE_DESTROY
+
+ std::cerr << name << "::terminate(): thread exited with return value " << rv << std::endl;
+#endif
+
+ }
+
+#ifdef DEBUG_THREAD_CREATE_DESTROY
+ std::cerr << name << "::terminate(): done" << std::endl;
+#endif
+}
+
+void *
+AudioThread::staticThreadRun(void *arg)
+{
+ AudioThread *inst = static_cast<AudioThread *>(arg);
+ if (!inst)
+ return 0;
+
+ pthread_cleanup_push(staticThreadCleanup, arg);
+
+ inst->getLock();
+ inst->m_exiting = false;
+ inst->threadRun();
+
+#ifdef DEBUG_THREAD_CREATE_DESTROY
+
+ std::cerr << inst->m_name << "::staticThreadRun(): threadRun exited" << std::endl;
+#endif
+
+ inst->releaseLock();
+ pthread_cleanup_pop(0);
+
+ return 0;
+}
+
+void
+AudioThread::staticThreadCleanup(void *arg)
+{
+ AudioThread *inst = static_cast<AudioThread *>(arg);
+ if (!inst || inst->m_exiting)
+ return ;
+
+#ifdef DEBUG_THREAD_CREATE_DESTROY
+
+ std::string name = inst->m_name;
+ std::cerr << name << "::staticThreadCleanup()" << std::endl;
+#endif
+
+ inst->m_exiting = true;
+ inst->releaseLock();
+
+#ifdef DEBUG_THREAD_CREATE_DESTROY
+
+ std::cerr << name << "::staticThreadCleanup() done" << std::endl;
+#endif
+}
+
+int
+AudioThread::getLock()
+{
+ int rv;
+#ifdef DEBUG_LOCKS
+
+ std::cerr << m_name << "::getLock()" << std::endl;
+#endif
+
+ rv = pthread_mutex_lock(&m_lock);
+#ifdef DEBUG_LOCKS
+
+ std::cerr << "OK" << std::endl;
+#endif
+
+ return rv;
+}
+
+int
+AudioThread::tryLock()
+{
+ int rv;
+#ifdef DEBUG_LOCKS
+
+ std::cerr << m_name << "::tryLock()" << std::endl;
+#endif
+
+ rv = pthread_mutex_trylock(&m_lock);
+#ifdef DEBUG_LOCKS
+
+ std::cerr << "OK (rv is " << rv << ")" << std::endl;
+#endif
+
+ return rv;
+}
+
+int
+AudioThread::releaseLock()
+{
+ int rv;
+#ifdef DEBUG_LOCKS
+
+ std::cerr << m_name << "::releaseLock()" << std::endl;
+#endif
+
+ rv = pthread_mutex_unlock(&m_lock);
+#ifdef DEBUG_LOCKS
+
+ std::cerr << "OK" << std::endl;
+#endif
+
+ return rv;
+}
+
+void
+AudioThread::signal()
+{
+#ifdef DEBUG_LOCKS
+ std::cerr << m_name << "::signal()" << std::endl;
+#endif
+
+ pthread_cond_signal(&m_condition);
+}
+
+
+AudioBussMixer::AudioBussMixer(SoundDriver *driver,
+ AudioInstrumentMixer *instrumentMixer,
+ unsigned int sampleRate,
+ unsigned int blockSize) :
+ AudioThread("AudioBussMixer", driver, sampleRate),
+ m_instrumentMixer(instrumentMixer),
+ m_blockSize(blockSize),
+ m_bussCount(0)
+{
+ // nothing else here
+}
+
+AudioBussMixer::~AudioBussMixer()
+{
+ for (unsigned int i = 0; i < m_processBuffers.size(); ++i) {
+ delete[] m_processBuffers[i];
+ }
+}
+
+AudioBussMixer::BufferRec::~BufferRec()
+{
+ for (size_t i = 0; i < buffers.size(); ++i)
+ delete buffers[i];
+}
+
+void
+AudioBussMixer::generateBuffers()
+{
+ // Not RT safe
+
+#ifdef DEBUG_BUSS_MIXER
+ std::cerr << "AudioBussMixer::generateBuffers" << std::endl;
+#endif
+
+ // This returns one too many, as the master is counted as buss 0
+ m_bussCount =
+ m_driver->getMappedStudio()->getObjectCount(MappedStudio::AudioBuss) - 1;
+
+#ifdef DEBUG_BUSS_MIXER
+
+ std::cerr << "AudioBussMixer::generateBuffers: have " << m_bussCount << " busses" << std::endl;
+#endif
+
+ int bufferSamples = m_blockSize;
+
+ if (!m_driver->getLowLatencyMode()) {
+ RealTime bufferLength = m_driver->getAudioMixBufferLength();
+ int bufferSamples = RealTime::realTime2Frame(bufferLength, m_sampleRate);
+ bufferSamples = ((bufferSamples / m_blockSize) + 1) * m_blockSize;
+ }
+
+ for (int i = 0; i < m_bussCount; ++i) {
+
+ BufferRec &rec = m_bufferMap[i];
+
+ if (rec.buffers.size() == 2)
+ continue;
+
+ for (unsigned int ch = 0; ch < 2; ++ch) {
+ RingBuffer<sample_t> *rb = new RingBuffer<sample_t>(bufferSamples);
+ if (!rb->mlock()) {
+ // std::cerr << "WARNING: AudioBussMixer::generateBuffers: couldn't lock ring buffer into real memory, performance may be impaired" << std::endl;
+ }
+ rec.buffers.push_back(rb);
+ }
+
+ MappedAudioBuss *mbuss =
+ m_driver->getMappedStudio()->getAudioBuss(i + 1); // master is 0
+
+ if (mbuss) {
+
+ float level = 0.0;
+ (void)mbuss->getProperty(MappedAudioBuss::Level, level);
+
+ float pan = 0.0;
+ (void)mbuss->getProperty(MappedAudioBuss::Pan, pan);
+
+ setBussLevels(i + 1, level, pan);
+ }
+ }
+
+ if (m_processBuffers.size() == 0) {
+ m_processBuffers.push_back(new sample_t[m_blockSize]);
+ m_processBuffers.push_back(new sample_t[m_blockSize]);
+ }
+}
+
+void
+AudioBussMixer::fillBuffers(const RealTime &currentTime)
+{
+ // Not RT safe
+
+#ifdef DEBUG_BUSS_MIXER
+ std::cerr << "AudioBussMixer::fillBuffers" << std::endl;
+#endif
+
+ emptyBuffers();
+ m_instrumentMixer->fillBuffers(currentTime);
+ kick();
+}
+
+void
+AudioBussMixer::emptyBuffers()
+{
+ // Not RT safe
+
+ getLock();
+
+#ifdef DEBUG_BUSS_MIXER
+
+ std::cerr << "AudioBussMixer::emptyBuffers" << std::endl;
+#endif
+
+ // We can't generate buffers before this, because we don't know how
+ // many busses there are
+ generateBuffers();
+
+ for (int i = 0; i < m_bussCount; ++i) {
+ m_bufferMap[i].dormant = true;
+ for (int ch = 0; ch < 2; ++ch) {
+ if (int(m_bufferMap[i].buffers.size()) > ch) {
+ m_bufferMap[i].buffers[ch]->reset();
+ }
+ }
+ }
+
+ releaseLock();
+}
+
+void
+AudioBussMixer::kick(bool wantLock, bool signalInstrumentMixer)
+{
+ // Needs to be RT safe if wantLock is not specified
+
+ if (wantLock)
+ getLock();
+
+#ifdef DEBUG_BUSS_MIXER
+
+ std::cerr << "AudioBussMixer::kick" << std::endl;
+#endif
+
+ processBlocks();
+
+#ifdef DEBUG_BUSS_MIXER
+
+ std::cerr << "AudioBussMixer::kick: processed" << std::endl;
+#endif
+
+ if (wantLock)
+ releaseLock();
+
+ if (signalInstrumentMixer) {
+ m_instrumentMixer->signal();
+ }
+}
+
+void
+AudioBussMixer::setBussLevels(int bussId, float dB, float pan)
+{
+ // No requirement to be RT safe
+
+ if (bussId == 0)
+ return ; // master
+ int buss = bussId - 1;
+
+ BufferRec &rec = m_bufferMap[buss];
+
+ float volume = AudioLevel::dB_to_multiplier(dB);
+
+ rec.gainLeft = volume * ((pan > 0.0) ? (1.0 - (pan / 100.0)) : 1.0);
+ rec.gainRight = volume * ((pan < 0.0) ? ((pan + 100.0) / 100.0) : 1.0);
+}
+
+void
+AudioBussMixer::updateInstrumentConnections()
+{
+ // Not RT safe
+
+ if (m_bussCount <= 0)
+ generateBuffers();
+
+ InstrumentId audioInstrumentBase;
+ int audioInstruments;
+ m_driver->getAudioInstrumentNumbers(audioInstrumentBase, audioInstruments);
+
+ InstrumentId synthInstrumentBase;
+ int synthInstruments;
+ m_driver->getSoftSynthInstrumentNumbers(synthInstrumentBase, synthInstruments);
+
+ for (int buss = 0; buss < m_bussCount; ++buss) {
+
+ MappedAudioBuss *mbuss =
+ m_driver->getMappedStudio()->getAudioBuss(buss + 1); // master is 0
+
+ if (!mbuss) {
+#ifdef DEBUG_BUSS_MIXER
+ std::cerr << "AudioBussMixer::updateInstrumentConnections: buss " << buss << " not found" << std::endl;
+#endif
+
+ continue;
+ }
+
+ BufferRec &rec = m_bufferMap[buss];
+
+ while (int(rec.instruments.size()) < audioInstruments + synthInstruments) {
+ rec.instruments.push_back(false);
+ }
+
+ std::vector<InstrumentId> instruments = mbuss->getInstruments();
+
+ for (int i = 0; i < audioInstruments + synthInstruments; ++i) {
+
+ InstrumentId id;
+ if (i < audioInstruments)
+ id = audioInstrumentBase + i;
+ else
+ id = synthInstrumentBase + (i - audioInstruments);
+
+ size_t j = 0;
+ for (j = 0; j < instruments.size(); ++j) {
+ if (instruments[j] == id) {
+ rec.instruments[i] = true;
+ break;
+ }
+ }
+ if (j == instruments.size())
+ rec.instruments[i] = false;
+ }
+ }
+}
+
+void
+AudioBussMixer::processBlocks()
+{
+ // Needs to be RT safe
+
+ if (m_bussCount == 0)
+ return ;
+
+#ifdef DEBUG_BUSS_MIXER
+
+ if (m_driver->isPlaying())
+ std::cerr << "AudioBussMixer::processBlocks" << std::endl;
+#endif
+
+ InstrumentId audioInstrumentBase;
+ int audioInstruments;
+ m_driver->getAudioInstrumentNumbers(audioInstrumentBase, audioInstruments);
+
+ InstrumentId synthInstrumentBase;
+ int synthInstruments;
+ m_driver->getSoftSynthInstrumentNumbers(synthInstrumentBase, synthInstruments);
+
+ bool *processedInstruments = (bool *)alloca
+ ((audioInstruments + synthInstruments) * sizeof(bool));
+
+ for (int i = 0; i < audioInstruments + synthInstruments; ++i) {
+ processedInstruments[i] = false;
+ }
+
+ int minBlocks = 0;
+ bool haveMinBlocks = false;
+
+ for (int buss = 0; buss < m_bussCount; ++buss) {
+
+ BufferRec &rec = m_bufferMap[buss];
+
+ float gain[2];
+ gain[0] = rec.gainLeft;
+ gain[1] = rec.gainRight;
+
+ // The dormant calculation here depends on the buffer length
+ // for this mixer being the same as that for the instrument mixer
+
+ size_t minSpace = 0;
+
+ for (int ch = 0; ch < 2; ++ch) {
+
+ size_t w = rec.buffers[ch]->getWriteSpace();
+ if (ch == 0 || w < minSpace)
+ minSpace = w;
+
+#ifdef DEBUG_BUSS_MIXER
+
+ std::cerr << "AudioBussMixer::processBlocks: buss " << buss << ": write space " << w << " on channel " << ch << std::endl;
+#endif
+
+ if (minSpace == 0)
+ break;
+
+ for (int i = 0; i < audioInstruments + synthInstruments; ++i) {
+
+ // is this instrument on this buss?
+ if (int(rec.instruments.size()) <= i ||
+ !rec.instruments[i])
+ continue;
+
+ InstrumentId id;
+ if (i < audioInstruments)
+ id = audioInstrumentBase + i;
+ else
+ id = synthInstrumentBase + (i - audioInstruments);
+
+ if (m_instrumentMixer->isInstrumentEmpty(id))
+ continue;
+
+ RingBuffer<sample_t, 2> *rb =
+ m_instrumentMixer->getRingBuffer(id, ch);
+ if (rb) {
+ size_t r = rb->getReadSpace(1);
+ if (r < minSpace)
+ minSpace = r;
+
+#ifdef DEBUG_BUSS_MIXER
+
+ if (id == 1000) {
+ std::cerr << "AudioBussMixer::processBlocks: buss " << buss << ": read space " << r << " on instrument " << id << ", channel " << ch << std::endl;
+ }
+#endif
+
+ if (minSpace == 0)
+ break;
+ }
+ }
+
+ if (minSpace == 0)
+ break;
+ }
+
+ int blocks = minSpace / m_blockSize;
+ if (!haveMinBlocks || (blocks < minBlocks)) {
+ minBlocks = blocks;
+ haveMinBlocks = true;
+ }
+
+#ifdef DEBUG_BUSS_MIXER
+ if (m_driver->isPlaying())
+ std::cerr << "AudioBussMixer::processBlocks: doing " << blocks << " blocks at block size " << m_blockSize << std::endl;
+#endif
+
+ for (int block = 0; block < blocks; ++block) {
+
+ memset(m_processBuffers[0], 0, m_blockSize * sizeof(sample_t));
+ memset(m_processBuffers[1], 0, m_blockSize * sizeof(sample_t));
+
+ bool dormant = true;
+
+ for (int i = 0; i < audioInstruments + synthInstruments; ++i) {
+
+ // is this instrument on this buss?
+ if (int(rec.instruments.size()) <= i ||
+ !rec.instruments[i])
+ continue;
+
+ if (processedInstruments[i]) {
+ // we aren't set up to process any instrument to
+ // more than one buss
+ continue;
+ } else {
+ processedInstruments[i] = true;
+ }
+
+ InstrumentId id;
+ if (i < audioInstruments)
+ id = audioInstrumentBase + i;
+ else
+ id = synthInstrumentBase + (i - audioInstruments);
+
+ if (m_instrumentMixer->isInstrumentEmpty(id))
+ continue;
+
+ if (m_instrumentMixer->isInstrumentDormant(id)) {
+
+ for (int ch = 0; ch < 2; ++ch) {
+ RingBuffer<sample_t, 2> *rb =
+ m_instrumentMixer->getRingBuffer(id, ch);
+
+ if (rb)
+ rb->skip(m_blockSize,
+ 1);
+ }
+ } else {
+ dormant = false;
+
+ for (int ch = 0; ch < 2; ++ch) {
+ RingBuffer<sample_t, 2> *rb =
+ m_instrumentMixer->getRingBuffer(id, ch);
+
+ if (rb)
+ rb->readAdding(m_processBuffers[ch],
+ m_blockSize,
+ 1);
+ }
+ }
+ }
+
+ if (m_instrumentMixer) {
+ AudioInstrumentMixer::PluginList &plugins =
+ m_instrumentMixer->getBussPlugins(buss + 1);
+
+ // This will have to do for now!
+ if (!plugins.empty())
+ dormant = false;
+
+ for (AudioInstrumentMixer::PluginList::iterator pli =
+ plugins.begin(); pli != plugins.end(); ++pli) {
+
+ RunnablePluginInstance *plugin = *pli;
+ if (!plugin || plugin->isBypassed())
+ continue;
+
+ unsigned int ch = 0;
+
+ while (ch < plugin->getAudioInputCount()) {
+ if (ch < 2) {
+ memcpy(plugin->getAudioInputBuffers()[ch],
+ m_processBuffers[ch],
+ m_blockSize * sizeof(sample_t));
+ } else {
+ memset(plugin->getAudioInputBuffers()[ch], 0,
+ m_blockSize * sizeof(sample_t));
+ }
+ ++ch;
+ }
+
+#ifdef DEBUG_BUSS_MIXER
+ std::cerr << "Running buss plugin with " << plugin->getAudioInputCount()
+ << " inputs, " << plugin->getAudioOutputCount() << " outputs" << std::endl;
+#endif
+
+ // We don't currently maintain a record of our
+ // frame time in the buss mixer. This will screw
+ // up any plugin that requires a good frame count:
+ // at the moment that only means DSSI effects
+ // plugins using run_multiple_synths, which would
+ // be an unusual although plausible combination
+ plugin->run(RealTime::zeroTime);
+
+ ch = 0;
+
+ while (ch < 2 && ch < plugin->getAudioOutputCount()) {
+
+ denormalKill(plugin->getAudioOutputBuffers()[ch],
+ m_blockSize);
+
+ memcpy(m_processBuffers[ch],
+ plugin->getAudioOutputBuffers()[ch],
+ m_blockSize * sizeof(sample_t));
+
+ ++ch;
+ }
+ }
+ }
+
+ for (int ch = 0; ch < 2; ++ch) {
+ if (dormant) {
+ rec.buffers[ch]->zero(m_blockSize);
+ } else {
+ for (size_t j = 0; j < m_blockSize; ++j) {
+ m_processBuffers[ch][j] *= gain[ch];
+ }
+ rec.buffers[ch]->write(m_processBuffers[ch], m_blockSize);
+ }
+ }
+
+ rec.dormant = dormant;
+
+#ifdef DEBUG_BUSS_MIXER
+
+ if (m_driver->isPlaying())
+ std::cerr << "AudioBussMixer::processBlocks: buss " << buss << (dormant ? " dormant" : " not dormant") << std::endl;
+#endif
+
+ }
+ }
+
+ // any unprocessed instruments need to be skipped, or they'll block
+
+ for (int i = 0; i < audioInstruments + synthInstruments; ++i) {
+
+ if (processedInstruments[i])
+ continue;
+
+ InstrumentId id;
+ if (i < audioInstruments)
+ id = audioInstrumentBase + i;
+ else
+ id = synthInstrumentBase + (i - audioInstruments);
+
+ if (m_instrumentMixer->isInstrumentEmpty(id))
+ continue;
+
+ for (int ch = 0; ch < 2; ++ch) {
+ RingBuffer<sample_t, 2> *rb =
+ m_instrumentMixer->getRingBuffer(id, ch);
+
+ if (rb)
+ rb->skip(m_blockSize * minBlocks,
+ 1);
+ }
+ }
+
+
+#ifdef DEBUG_BUSS_MIXER
+ std::cerr << "AudioBussMixer::processBlocks: done" << std::endl;
+#endif
+}
+
+void
+AudioBussMixer::threadRun()
+{
+ while (!m_exiting) {
+
+ if (m_driver->areClocksRunning()) {
+ kick(false);
+ }
+
+ RealTime t = m_driver->getAudioMixBufferLength();
+ t = t / 2;
+ if (t < RealTime(0, 10000000))
+ t = RealTime(0, 10000000); // 10ms minimum
+
+ struct timeval now;
+ gettimeofday(&now, 0);
+ t = t + RealTime(now.tv_sec, now.tv_usec * 1000);
+
+ struct timespec timeout;
+ timeout.tv_sec = t.sec;
+ timeout.tv_nsec = t.nsec;
+
+ pthread_cond_timedwait(&m_condition, &m_lock, &timeout);
+ pthread_testcancel();
+ }
+}
+
+
+AudioInstrumentMixer::AudioInstrumentMixer(SoundDriver *driver,
+ AudioFileReader *fileReader,
+ unsigned int sampleRate,
+ unsigned int blockSize) :
+ AudioThread("AudioInstrumentMixer", driver, sampleRate),
+ m_fileReader(fileReader),
+ m_bussMixer(0),
+ m_blockSize(blockSize)
+{
+ // Pregenerate empty plugin slots
+
+ InstrumentId audioInstrumentBase;
+ int audioInstruments;
+ m_driver->getAudioInstrumentNumbers(audioInstrumentBase, audioInstruments);
+
+ InstrumentId synthInstrumentBase;
+ int synthInstruments;
+ m_driver->getSoftSynthInstrumentNumbers(synthInstrumentBase, synthInstruments);
+
+ for (int i = 0; i < audioInstruments + synthInstruments; ++i) {
+
+ InstrumentId id;
+ if (i < audioInstruments)
+ id = audioInstrumentBase + i;
+ else
+ id = synthInstrumentBase + (i - audioInstruments);
+
+ PluginList &list = m_plugins[id];
+ for (int j = 0; j < int(Instrument::PLUGIN_COUNT); ++j) {
+ list.push_back(0);
+ }
+
+ if (i >= audioInstruments) {
+ m_synths[id] = 0;
+ }
+ }
+
+ // Leave the buffer map and process buffer list empty for now.
+ // The buffer length can change between plays, so we always
+ // examine the buffers in fillBuffers and are prepared to
+ // regenerate from scratch if necessary. Don't like it though.
+}
+
+AudioInstrumentMixer::~AudioInstrumentMixer()
+{
+ std::cerr << "AudioInstrumentMixer::~AudioInstrumentMixer" << std::endl;
+ // BufferRec dtor will handle the BufferMap
+
+ removeAllPlugins();
+
+ for (std::vector<sample_t *>::iterator i = m_processBuffers.begin();
+ i != m_processBuffers.end(); ++i) {
+ delete[] *i;
+ }
+
+ std::cerr << "AudioInstrumentMixer::~AudioInstrumentMixer exiting" << std::endl;
+}
+
+AudioInstrumentMixer::BufferRec::~BufferRec()
+{
+ for (size_t i = 0; i < buffers.size(); ++i)
+ delete buffers[i];
+}
+
+
+void
+AudioInstrumentMixer::setPlugin(InstrumentId id, int position, QString identifier)
+{
+ // Not RT safe
+
+ std::cerr << "AudioInstrumentMixer::setPlugin(" << id << ", " << position << ", " << identifier << ")" << std::endl;
+
+ int channels = 2;
+ if (m_bufferMap.find(id) != m_bufferMap.end()) {
+ channels = m_bufferMap[id].channels;
+ }
+
+ RunnablePluginInstance *instance = 0;
+
+ PluginFactory *factory = PluginFactory::instanceFor(identifier);
+ if (factory) {
+ instance = factory->instantiatePlugin(identifier,
+ id,
+ position,
+ m_sampleRate,
+ m_blockSize,
+ channels);
+ if (instance && !instance->isOK()) {
+ std::cerr << "AudioInstrumentMixer::setPlugin(" << id << ", " << position
+ << ": instance is not OK" << std::endl;
+ delete instance;
+ instance = 0;
+ }
+ } else {
+ std::cerr << "AudioInstrumentMixer::setPlugin: No factory for identifier "
+ << identifier << std::endl;
+ }
+
+ RunnablePluginInstance *oldInstance = 0;
+
+ if (position == int(Instrument::SYNTH_PLUGIN_POSITION)) {
+
+ oldInstance = m_synths[id];
+ m_synths[id] = instance;
+
+ } else {
+
+ PluginList &list = m_plugins[id];
+
+ if (position < Instrument::PLUGIN_COUNT) {
+ while (position >= (int)list.size()) {
+ list.push_back(0);
+ }
+ oldInstance = list[position];
+ list[position] = instance;
+ } else {
+ std::cerr << "AudioInstrumentMixer::setPlugin: No position "
+ << position << " for instrument " << id << std::endl;
+ delete instance;
+ }
+ }
+
+ if (oldInstance) {
+ m_driver->claimUnwantedPlugin(oldInstance);
+ }
+}
+
+void
+AudioInstrumentMixer::removePlugin(InstrumentId id, int position)
+{
+ // Not RT safe
+
+ std::cerr << "AudioInstrumentMixer::removePlugin(" << id << ", " << position << ")" << std::endl;
+
+ RunnablePluginInstance *oldInstance = 0;
+
+ if (position == int(Instrument::SYNTH_PLUGIN_POSITION)) {
+
+ if (m_synths[id]) {
+ oldInstance = m_synths[id];
+ m_synths[id] = 0;
+ }
+
+ } else {
+
+ PluginList &list = m_plugins[id];
+ if (position < (int)list.size()) {
+ oldInstance = list[position];
+ list[position] = 0;
+ }
+ }
+
+ if (oldInstance) {
+ m_driver->claimUnwantedPlugin(oldInstance);
+ }
+}
+
+void
+AudioInstrumentMixer::removeAllPlugins()
+{
+ // Not RT safe
+
+ std::cerr << "AudioInstrumentMixer::removeAllPlugins" << std::endl;
+
+ for (SynthPluginMap::iterator i = m_synths.begin();
+ i != m_synths.end(); ++i) {
+ if (i->second) {
+ RunnablePluginInstance *instance = i->second;
+ i->second = 0;
+ m_driver->claimUnwantedPlugin(instance);
+ }
+ }
+
+ for (PluginMap::iterator j = m_plugins.begin();
+ j != m_plugins.end(); ++j) {
+
+ PluginList &list = j->second;
+
+ for (PluginList::iterator i = list.begin(); i != list.end(); ++i) {
+ RunnablePluginInstance *instance = *i;
+ *i = 0;
+ m_driver->claimUnwantedPlugin(instance);
+ }
+ }
+}
+
+
+RunnablePluginInstance *
+AudioInstrumentMixer::getPluginInstance(InstrumentId id, int position)
+{
+ // Not RT safe
+
+ if (position == int(Instrument::SYNTH_PLUGIN_POSITION)) {
+ return m_synths[id];
+ } else {
+ PluginList &list = m_plugins[id];
+ if (position < int(list.size()))
+ return list[position];
+ }
+ return 0;
+}
+
+
+void
+AudioInstrumentMixer::setPluginPortValue(InstrumentId id, int position,
+ unsigned int port, float value)
+{
+ // Not RT safe
+
+ RunnablePluginInstance *instance = getPluginInstance(id, position);
+
+ if (instance) {
+ instance->setPortValue(port, value);
+ }
+}
+
+float
+AudioInstrumentMixer::getPluginPortValue(InstrumentId id, int position,
+ unsigned int port)
+{
+ // Not RT safe
+
+ RunnablePluginInstance *instance = getPluginInstance(id, position);
+
+ if (instance) {
+ return instance->getPortValue(port);
+ }
+
+ return 0;
+}
+
+void
+AudioInstrumentMixer::setPluginBypass(InstrumentId id, int position, bool bypass)
+{
+ // Not RT safe
+
+ RunnablePluginInstance *instance = getPluginInstance(id, position);
+ if (instance)
+ instance->setBypassed(bypass);
+}
+
+QStringList
+AudioInstrumentMixer::getPluginPrograms(InstrumentId id, int position)
+{
+ // Not RT safe
+
+ QStringList programs;
+ RunnablePluginInstance *instance = getPluginInstance(id, position);
+ if (instance)
+ programs = instance->getPrograms();
+ return programs;
+}
+
+QString
+AudioInstrumentMixer::getPluginProgram(InstrumentId id, int position)
+{
+ // Not RT safe
+
+ QString program;
+ RunnablePluginInstance *instance = getPluginInstance(id, position);
+ if (instance)
+ program = instance->getCurrentProgram();
+ return program;
+}
+
+QString
+AudioInstrumentMixer::getPluginProgram(InstrumentId id, int position, int bank,
+ int program)
+{
+ // Not RT safe
+
+ QString programName;
+ RunnablePluginInstance *instance = getPluginInstance(id, position);
+ if (instance)
+ programName = instance->getProgram(bank, program);
+ return programName;
+}
+
+unsigned long
+AudioInstrumentMixer::getPluginProgram(InstrumentId id, int position, QString name)
+{
+ // Not RT safe
+
+ unsigned long program = 0;
+ RunnablePluginInstance *instance = getPluginInstance(id, position);
+ if (instance)
+ program = instance->getProgram(name);
+ return program;
+}
+
+void
+AudioInstrumentMixer::setPluginProgram(InstrumentId id, int position, QString program)
+{
+ // Not RT safe
+
+ RunnablePluginInstance *instance = getPluginInstance(id, position);
+ if (instance)
+ instance->selectProgram(program);
+}
+
+QString
+AudioInstrumentMixer::configurePlugin(InstrumentId id, int position, QString key, QString value)
+{
+ // Not RT safe
+
+ RunnablePluginInstance *instance = getPluginInstance(id, position);
+ if (instance)
+ return instance->configure(key, value);
+ return QString();
+}
+
+void
+AudioInstrumentMixer::discardPluginEvents()
+{
+ getLock();
+ if (m_bussMixer) m_bussMixer->getLock();
+
+ for (SynthPluginMap::iterator j = m_synths.begin();
+ j != m_synths.end(); ++j) {
+
+ RunnablePluginInstance *instance = j->second;
+ if (instance) instance->discardEvents();
+ }
+
+ for (PluginMap::iterator j = m_plugins.begin();
+ j != m_plugins.end(); ++j) {
+
+ InstrumentId id = j->first;
+
+ for (PluginList::iterator i = m_plugins[id].begin();
+ i != m_plugins[id].end(); ++i) {
+
+ RunnablePluginInstance *instance = *i;
+ if (instance) instance->discardEvents();
+ }
+ }
+
+ if (m_bussMixer) m_bussMixer->releaseLock();
+ releaseLock();
+}
+
+void
+AudioInstrumentMixer::resetAllPlugins(bool discardEvents)
+{
+ // Not RT safe
+
+ // lock required here to protect against calling
+ // activate/deactivate at the same time as run()
+
+#ifdef DEBUG_MIXER
+ std::cerr << "AudioInstrumentMixer::resetAllPlugins!" << std::endl;
+ if (discardEvents) std::cerr << "(discardEvents true)" << std::endl;
+#endif
+
+ getLock();
+ if (m_bussMixer)
+ m_bussMixer->getLock();
+
+ for (SynthPluginMap::iterator j = m_synths.begin();
+ j != m_synths.end(); ++j) {
+
+ InstrumentId id = j->first;
+
+ int channels = 2;
+ if (m_bufferMap.find(id) != m_bufferMap.end()) {
+ channels = m_bufferMap[id].channels;
+ }
+
+ RunnablePluginInstance *instance = j->second;
+
+ if (instance) {
+#ifdef DEBUG_MIXER
+ std::cerr << "AudioInstrumentMixer::resetAllPlugins: (re)setting " << channels << " channels on synth for instrument " << id << std::endl;
+#endif
+
+ if (discardEvents)
+ instance->discardEvents();
+ instance->setIdealChannelCount(channels);
+ }
+ }
+
+ for (PluginMap::iterator j = m_plugins.begin();
+ j != m_plugins.end(); ++j) {
+
+ InstrumentId id = j->first;
+
+ int channels = 2;
+ if (m_bufferMap.find(id) != m_bufferMap.end()) {
+ channels = m_bufferMap[id].channels;
+ }
+
+ for (PluginList::iterator i = m_plugins[id].begin();
+ i != m_plugins[id].end(); ++i) {
+
+ RunnablePluginInstance *instance = *i;
+
+ if (instance) {
+#ifdef DEBUG_MIXER
+ std::cerr << "AudioInstrumentMixer::resetAllPlugins: (re)setting " << channels << " channels on plugin for instrument " << id << std::endl;
+#endif
+
+ if (discardEvents)
+ instance->discardEvents();
+ instance->setIdealChannelCount(channels);
+ }
+ }
+ }
+
+ if (m_bussMixer)
+ m_bussMixer->releaseLock();
+ releaseLock();
+}
+
+void
+AudioInstrumentMixer::destroyAllPlugins()
+{
+ // Not RT safe
+
+ getLock();
+ if (m_bussMixer)
+ m_bussMixer->getLock();
+
+ // Delete immediately, as we're probably exiting here -- don't use
+ // the scavenger.
+
+ std::cerr << "AudioInstrumentMixer::destroyAllPlugins" << std::endl;
+
+ for (SynthPluginMap::iterator j = m_synths.begin();
+ j != m_synths.end(); ++j) {
+ RunnablePluginInstance *instance = j->second;
+ j->second = 0;
+ delete instance;
+ }
+
+ for (PluginMap::iterator j = m_plugins.begin();
+ j != m_plugins.end(); ++j) {
+
+ InstrumentId id = j->first;
+
+ for (PluginList::iterator i = m_plugins[id].begin();
+ i != m_plugins[id].end(); ++i) {
+
+ RunnablePluginInstance *instance = *i;
+ *i = 0;
+ delete instance;
+ }
+ }
+
+ // and tell the driver to get rid of anything already scavenged.
+ m_driver->scavengePlugins();
+
+ if (m_bussMixer)
+ m_bussMixer->releaseLock();
+ releaseLock();
+}
+
+size_t
+AudioInstrumentMixer::getPluginLatency(unsigned int id)
+{
+ // Not RT safe
+
+ size_t latency = 0;
+
+ RunnablePluginInstance *synth = m_synths[id];
+ if (synth)
+ latency += m_synths[id]->getLatency();
+
+ for (PluginList::iterator i = m_plugins[id].begin();
+ i != m_plugins[id].end(); ++i) {
+ RunnablePluginInstance *plugin = *i;
+ if (plugin)
+ latency += plugin->getLatency();
+ }
+
+ return latency;
+}
+
+void
+AudioInstrumentMixer::generateBuffers()
+{
+ // Not RT safe
+
+ InstrumentId audioInstrumentBase;
+ int audioInstruments;
+ m_driver->getAudioInstrumentNumbers(audioInstrumentBase, audioInstruments);
+
+ InstrumentId synthInstrumentBase;
+ int synthInstruments;
+ m_driver->getSoftSynthInstrumentNumbers(synthInstrumentBase, synthInstruments);
+
+ unsigned int maxChannels = 0;
+
+ int bufferSamples = m_blockSize;
+
+ if (!m_driver->getLowLatencyMode()) {
+ RealTime bufferLength = m_driver->getAudioMixBufferLength();
+ int bufferSamples = RealTime::realTime2Frame(bufferLength, m_sampleRate);
+ bufferSamples = ((bufferSamples / m_blockSize) + 1) * m_blockSize;
+#ifdef DEBUG_MIXER
+
+ std::cerr << "AudioInstrumentMixer::generateBuffers: Buffer length is " << bufferLength << "; buffer samples " << bufferSamples << " (sample rate " << m_sampleRate << ")" << std::endl;
+#endif
+
+ }
+
+ for (int i = 0; i < audioInstruments + synthInstruments; ++i) {
+
+ InstrumentId id;
+ if (i < audioInstruments)
+ id = audioInstrumentBase + i;
+ else
+ id = synthInstrumentBase + (i - audioInstruments);
+
+ // Get a fader for this instrument - if we can't then this
+ // isn't a valid audio track.
+ MappedAudioFader *fader = m_driver->getMappedStudio()->getAudioFader(id);
+
+ if (!fader) {
+#ifdef DEBUG_MIXER
+ std::cerr << "AudioInstrumentMixer::generateBuffers: no fader for audio instrument " << id << std::endl;
+#endif
+
+ continue;
+ }
+
+ float fch = 2;
+ (void)fader->getProperty(MappedAudioFader::Channels, fch);
+ unsigned int channels = (unsigned int)fch;
+
+ BufferRec &rec = m_bufferMap[id];
+
+ rec.channels = channels;
+
+ // We always have stereo buffers (for output of pan)
+ // even on a mono instrument.
+ if (channels < 2)
+ channels = 2;
+ if (channels > maxChannels)
+ maxChannels = channels;
+
+ bool replaceBuffers = (rec.buffers.size() > channels);
+
+ if (!replaceBuffers) {
+ for (size_t i = 0; i < rec.buffers.size(); ++i) {
+ if (rec.buffers[i]->getSize() != bufferSamples) {
+ replaceBuffers = true;
+ break;
+ }
+ }
+ }
+
+ if (replaceBuffers) {
+ for (size_t i = 0; i < rec.buffers.size(); ++i) {
+ delete rec.buffers[i];
+ }
+ rec.buffers.clear();
+ }
+
+ while (rec.buffers.size() < channels) {
+
+ // All our ringbuffers are set up for two readers: the
+ // buss mix thread and the main process thread for
+ // e.g. JACK. The main process thread gets the zero-id
+ // reader, so it gets the same API as if this was a
+ // single-reader buffer; the buss mixer has to remember to
+ // explicitly request reader 1.
+
+ RingBuffer<sample_t, 2> *rb =
+ new RingBuffer<sample_t, 2>(bufferSamples);
+
+ if (!rb->mlock()) {
+ // std::cerr << "WARNING: AudioInstrumentMixer::generateBuffers: couldn't lock ring buffer into real memory, performance may be impaired" << std::endl;
+ }
+ rec.buffers.push_back(rb);
+ }
+
+ float level = 0.0;
+ (void)fader->getProperty(MappedAudioFader::FaderLevel, level);
+
+ float pan = 0.0;
+ (void)fader->getProperty(MappedAudioFader::Pan, pan);
+
+ setInstrumentLevels(id, level, pan);
+ }
+
+ // Make room for up to 16 busses here, to avoid reshuffling later
+ int busses = 16;
+ if (m_bussMixer)
+ busses = std::max(busses, m_bussMixer->getBussCount());
+ for (int i = 0; i < busses; ++i) {
+ PluginList &list = m_plugins[i + 1];
+ while (list.size() < Instrument::PLUGIN_COUNT) {
+ list.push_back(0);
+ }
+ }
+
+ while (m_processBuffers.size() > maxChannels) {
+ std::vector<sample_t *>::iterator bi = m_processBuffers.end();
+ --bi;
+ delete[] *bi;
+ m_processBuffers.erase(bi);
+ }
+ while (m_processBuffers.size() < maxChannels) {
+ m_processBuffers.push_back(new sample_t[m_blockSize]);
+ }
+}
+
+void
+AudioInstrumentMixer::fillBuffers(const RealTime &currentTime)
+{
+ // Not RT safe
+
+ emptyBuffers(currentTime);
+
+ getLock();
+
+#ifdef DEBUG_MIXER
+
+ std::cerr << "AudioInstrumentMixer::fillBuffers(" << currentTime << ")" << std::endl;
+#endif
+
+ bool discard;
+ processBlocks(discard);
+
+ releaseLock();
+}
+
+void
+AudioInstrumentMixer::allocateBuffers()
+{
+ // Not RT safe
+
+ getLock();
+
+#ifdef DEBUG_MIXER
+
+ std::cerr << "AudioInstrumentMixer::allocateBuffers()" << std::endl;
+#endif
+
+ generateBuffers();
+
+ releaseLock();
+}
+
+void
+AudioInstrumentMixer::emptyBuffers(RealTime currentTime)
+{
+ // Not RT safe
+
+ getLock();
+
+#ifdef DEBUG_MIXER
+
+ std::cerr << "AudioInstrumentMixer::emptyBuffers(" << currentTime << ")" << std::endl;
+#endif
+
+ generateBuffers();
+
+ InstrumentId audioInstrumentBase;
+ int audioInstruments;
+ m_driver->getAudioInstrumentNumbers(audioInstrumentBase, audioInstruments);
+
+ InstrumentId synthInstrumentBase;
+ int synthInstruments;
+ m_driver->getSoftSynthInstrumentNumbers(synthInstrumentBase, synthInstruments);
+
+ for (int i = 0; i < audioInstruments + synthInstruments; ++i) {
+
+ InstrumentId id;
+ if (i < audioInstruments)
+ id = audioInstrumentBase + i;
+ else
+ id = synthInstrumentBase + (i - audioInstruments);
+
+ m_bufferMap[id].dormant = true;
+ m_bufferMap[id].muted = false;
+ m_bufferMap[id].zeroFrames = 0;
+ m_bufferMap[id].filledTo = currentTime;
+
+ for (size_t i = 0; i < m_bufferMap[id].buffers.size(); ++i) {
+ m_bufferMap[id].buffers[i]->reset();
+ }
+ }
+
+ releaseLock();
+}
+
+void
+AudioInstrumentMixer::setInstrumentLevels(InstrumentId id, float dB, float pan)
+{
+ // No requirement to be RT safe
+
+ BufferRec &rec = m_bufferMap[id];
+
+ float volume = AudioLevel::dB_to_multiplier(dB);
+
+ rec.gainLeft = volume * ((pan > 0.0) ? (1.0 - (pan / 100.0)) : 1.0);
+ rec.gainRight = volume * ((pan < 0.0) ? ((pan + 100.0) / 100.0) : 1.0);
+ rec.volume = volume;
+}
+
+void
+AudioInstrumentMixer::updateInstrumentMuteStates()
+{
+ SequencerDataBlock *sdb = m_driver->getSequencerDataBlock();
+ if (sdb) {
+ ControlBlock *cb = sdb->getControlBlock();
+ if (cb) {
+
+ for (BufferMap::iterator i = m_bufferMap.begin();
+ i != m_bufferMap.end(); ++i) {
+
+ InstrumentId id = i->first;
+ BufferRec &rec = i->second;
+
+ if (id >= SoftSynthInstrumentBase) {
+ rec.muted = cb->isInstrumentMuted(id);
+ } else {
+ rec.muted = cb->isInstrumentUnused(id);
+ }
+ }
+ }
+ }
+}
+
+void
+AudioInstrumentMixer::processBlocks(bool &readSomething)
+{
+ // Needs to be RT safe
+
+#ifdef DEBUG_MIXER
+ if (m_driver->isPlaying())
+ std::cerr << "AudioInstrumentMixer::processBlocks" << std::endl;
+#endif
+
+ // Profiler profiler("processBlocks", true);
+
+ const AudioPlayQueue *queue = m_driver->getAudioQueue();
+
+ for (BufferMap::iterator i = m_bufferMap.begin();
+ i != m_bufferMap.end(); ++i) {
+
+ InstrumentId id = i->first;
+ BufferRec &rec = i->second;
+
+ // This "muted" flag actually only strictly means muted when
+ // applied to synth instruments. For audio instruments it's
+ // only true if the instrument is not in use at all (see
+ // updateInstrumentMuteStates above). It's not safe to base
+ // the empty calculation on muted state for audio tracks,
+ // because that causes buffering problems when the mute is
+ // toggled for an audio track while it's playing a file.
+
+ bool empty = false;
+
+ if (rec.muted) {
+ empty = true;
+ } else {
+ if (id >= SoftSynthInstrumentBase) {
+ empty = (!m_synths[id] || m_synths[id]->isBypassed());
+ } else {
+ empty = !queue->haveFilesForInstrument(id);
+ }
+
+ if (empty) {
+ for (PluginList::iterator j = m_plugins[id].begin();
+ j != m_plugins[id].end(); ++j) {
+ if (*j != 0) {
+ empty = false;
+ break;
+ }
+ }
+ }
+ }
+
+ if (!empty && rec.empty) {
+
+ // This instrument is becoming freshly non-empty. We need
+ // to set its filledTo field to match that of an existing
+ // non-empty instrument, if we can find one.
+
+ for (BufferMap::iterator j = m_bufferMap.begin();
+ j != m_bufferMap.end(); ++j) {
+
+ if (j->first == i->first)
+ continue;
+ if (j->second.empty)
+ continue;
+
+ rec.filledTo = j->second.filledTo;
+ break;
+ }
+ }
+
+ rec.empty = empty;
+
+ // For a while we were setting empty to true if the volume on
+ // the track was zero, but that breaks continuity if there is
+ // actually a file on the track -- processEmptyBlocks won't
+ // read it, so it'll fall behind if we put the volume up again.
+ }
+
+ bool more = true;
+
+ static const int MAX_FILES_PER_INSTRUMENT = 500;
+ static PlayableAudioFile *playing[MAX_FILES_PER_INSTRUMENT];
+
+ RealTime blockDuration = RealTime::frame2RealTime(m_blockSize, m_sampleRate);
+
+ while (more) {
+
+ more = false;
+
+ for (BufferMap::iterator i = m_bufferMap.begin();
+ i != m_bufferMap.end(); ++i) {
+
+ InstrumentId id = i->first;
+ BufferRec &rec = i->second;
+
+ if (rec.empty) {
+ rec.dormant = true;
+ continue;
+ }
+
+ size_t playCount = MAX_FILES_PER_INSTRUMENT;
+
+ if (id >= SoftSynthInstrumentBase)
+ playCount = 0;
+ else {
+ queue->getPlayingFilesForInstrument(rec.filledTo,
+ blockDuration, id,
+ playing, playCount);
+ }
+
+ if (processBlock(id, playing, playCount, readSomething)) {
+ more = true;
+ }
+ }
+ }
+}
+
+
+bool
+AudioInstrumentMixer::processBlock(InstrumentId id,
+ PlayableAudioFile **playing,
+ size_t playCount,
+ bool &readSomething)
+{
+ // Needs to be RT safe
+
+ // Profiler profiler("processBlock", true);
+
+ BufferRec &rec = m_bufferMap[id];
+ RealTime bufferTime = rec.filledTo;
+
+#ifdef DEBUG_MIXER
+ // if (m_driver->isPlaying()) {
+ if ((id % 100) == 0)
+ std::cerr << "AudioInstrumentMixer::processBlock(" << id << "): buffer time is " << bufferTime << std::endl;
+ // }
+#endif
+
+ unsigned int channels = rec.channels;
+ if (channels > rec.buffers.size())
+ channels = rec.buffers.size();
+ if (channels > m_processBuffers.size())
+ channels = m_processBuffers.size();
+ if (channels == 0) {
+#ifdef DEBUG_MIXER
+ if ((id % 100) == 0)
+ std::cerr << "AudioInstrumentMixer::processBlock(" << id << "): nominal channels " << rec.channels << ", ring buffers " << rec.buffers.size() << ", process buffers " << m_processBuffers.size() << std::endl;
+#endif
+
+ return false; // buffers just haven't been set up yet
+ }
+
+ unsigned int targetChannels = channels;
+ if (targetChannels < 2)
+ targetChannels = 2; // fill at least two buffers
+
+ size_t minWriteSpace = 0;
+ for (unsigned int ch = 0; ch < targetChannels; ++ch) {
+ size_t thisWriteSpace = rec.buffers[ch]->getWriteSpace();
+ if (ch == 0 || thisWriteSpace < minWriteSpace) {
+ minWriteSpace = thisWriteSpace;
+ if (minWriteSpace < m_blockSize) {
+#ifdef DEBUG_MIXER
+ // if (m_driver->isPlaying()) {
+ if ((id % 100) == 0)
+ std::cerr << "AudioInstrumentMixer::processBlock(" << id << "): only " << minWriteSpace << " write space on channel " << ch << " for block size " << m_blockSize << std::endl;
+ // }
+#endif
+
+ return false;
+ }
+ }
+ }
+
+ PluginList &plugins = m_plugins[id];
+
+#ifdef DEBUG_MIXER
+
+ if ((id % 100) == 0 && m_driver->isPlaying())
+ std::cerr << "AudioInstrumentMixer::processBlock(" << id << "): minWriteSpace is " << minWriteSpace << std::endl;
+#else
+#ifdef DEBUG_MIXER_LIGHTWEIGHT
+
+ if ((id % 100) == 0 && m_driver->isPlaying())
+ std::cout << minWriteSpace << "/" << rec.buffers[0]->getSize() << std::endl;
+#endif
+#endif
+
+#ifdef DEBUG_MIXER
+
+ if ((id % 100) == 0 && playCount > 0)
+ std::cerr << "AudioInstrumentMixer::processBlock(" << id << "): " << playCount << " audio file(s) to consider" << std::endl;
+#endif
+
+ bool haveBlock = true;
+ bool haveMore = false;
+
+ for (size_t fileNo = 0; fileNo < playCount; ++fileNo) {
+
+ bool acceptable = false;
+ PlayableAudioFile *file = playing[fileNo];
+
+ size_t frames = file->getSampleFramesAvailable();
+ acceptable = ((frames >= m_blockSize) || file->isFullyBuffered());
+
+ if (acceptable &&
+ (minWriteSpace >= m_blockSize * 2) &&
+ (frames >= m_blockSize * 2)) {
+
+#ifdef DEBUG_MIXER
+ if ((id % 100) == 0)
+ std::cerr << "AudioInstrumentMixer::processBlock(" << id << "): will be asking for more" << std::endl;
+#endif
+
+ haveMore = true;
+ }
+
+#ifdef DEBUG_MIXER
+ if ((id % 100) == 0)
+ std::cerr << "AudioInstrumentMixer::processBlock(" << id << "): file has " << frames << " frames available" << std::endl;
+#endif
+
+ if (!acceptable) {
+
+ std::cerr << "AudioInstrumentMixer::processBlock(" << id << "): file " << file->getAudioFile()->getFilename() << " has " << frames << " frames available, says isBuffered " << file->isBuffered() << std::endl;
+
+ if (!m_driver->getLowLatencyMode()) {
+
+ // Not a serious problem, just block on this
+ // instrument and return to it a little later.
+ haveBlock = false;
+
+ } else {
+ // In low latency mode, this is a serious problem if
+ // the file has been buffered and simply isn't filling
+ // fast enough. Otherwise we have to assume that the
+ // problem is something like a new file being dropped
+ // in by unmute during playback, in which case we have
+ // to accept that it won't be available for a while
+ // and just read silence from it instead.
+ if (file->isBuffered()) {
+ m_driver->reportFailure(MappedEvent::FailureDiscUnderrun);
+ haveBlock = false;
+ } else {
+ // ignore happily.
+ }
+ }
+ }
+ }
+
+ if (!haveBlock) {
+ return false; // blocked;
+ }
+
+#ifdef DEBUG_MIXER
+ if (!haveMore) {
+ if ((id % 100) == 0)
+ std::cerr << "AudioInstrumentMixer::processBlock(" << id << "): won't be asking for more" << std::endl;
+ }
+#endif
+
+ for (unsigned int ch = 0; ch < targetChannels; ++ch) {
+ memset(m_processBuffers[ch], 0, sizeof(sample_t) * m_blockSize);
+ }
+
+ RunnablePluginInstance *synth = m_synths[id];
+
+ if (synth && !synth->isBypassed()) {
+
+ synth->run(bufferTime);
+
+ unsigned int ch = 0;
+
+ while (ch < synth->getAudioOutputCount() && ch < channels) {
+ denormalKill(synth->getAudioOutputBuffers()[ch],
+ m_blockSize);
+ memcpy(m_processBuffers[ch],
+ synth->getAudioOutputBuffers()[ch],
+ m_blockSize * sizeof(sample_t));
+ ++ch;
+ }
+ }
+
+ if (haveBlock) {
+
+ // Mix in a block from each playing file on this instrument.
+
+ for (size_t fileNo = 0; fileNo < playCount; ++fileNo) {
+
+ PlayableAudioFile *file = playing[fileNo];
+
+ size_t offset = 0;
+ size_t blockSize = m_blockSize;
+
+ if (file->getStartTime() > bufferTime) {
+ offset = RealTime::realTime2Frame
+ (file->getStartTime() - bufferTime, m_sampleRate);
+ if (offset < blockSize)
+ blockSize -= offset;
+ else
+ blockSize = 0;
+#ifdef DEBUG_MIXER
+
+ std::cerr << "AudioInstrumentMixer::processBlock: file starts at offset " << offset << ", block size now " << blockSize << std::endl;
+#endif
+
+ }
+
+ //!!! This addSamples call is what is supposed to signal
+ // to a playable audio file when the end of the file has
+ // been reached. But for some playables it appears the
+ // file overruns, possibly due to rounding errors in
+ // sample rate conversion, and so we stop reading from it
+ // before it's actually done. I don't particularly mind
+ // that from a sound quality POV (after all it's badly
+ // resampled already) but unfortunately it means we leak
+ // pooled buffers.
+
+ if (blockSize > 0) {
+ file->addSamples(m_processBuffers, channels, blockSize, offset);
+ readSomething = true;
+ }
+ }
+ }
+
+ // Apply plugins. There are various copy-reducing
+ // optimisations available here, but we're not even going to
+ // think about them yet. Note that we force plugins to mono
+ // on a mono track, even though we have stereo output buffers
+ // -- stereo only comes into effect at the pan stage, and
+ // these are pre-fader plugins.
+
+ for (PluginList::iterator pli = plugins.begin();
+ pli != plugins.end(); ++pli) {
+
+ RunnablePluginInstance *plugin = *pli;
+ if (!plugin || plugin->isBypassed())
+ continue;
+
+ unsigned int ch = 0;
+
+ // If a plugin has more input channels than we have
+ // available, we duplicate up to stereo and leave any
+ // remaining channels empty.
+
+ while (ch < plugin->getAudioInputCount()) {
+
+ if (ch < channels || ch < 2) {
+ memcpy(plugin->getAudioInputBuffers()[ch],
+ m_processBuffers[ch % channels],
+ m_blockSize * sizeof(sample_t));
+ } else {
+ memset(plugin->getAudioInputBuffers()[ch], 0,
+ m_blockSize * sizeof(sample_t));
+ }
+ ++ch;
+ }
+
+#ifdef DEBUG_MIXER
+ std::cerr << "Running plugin with " << plugin->getAudioInputCount()
+ << " inputs, " << plugin->getAudioOutputCount() << " outputs" << std::endl;
+#endif
+
+ plugin->run(bufferTime);
+
+ ch = 0;
+
+ while (ch < plugin->getAudioOutputCount()) {
+
+ denormalKill(plugin->getAudioOutputBuffers()[ch],
+ m_blockSize);
+
+ if (ch < channels) {
+ memcpy(m_processBuffers[ch],
+ plugin->getAudioOutputBuffers()[ch],
+ m_blockSize * sizeof(sample_t));
+ } else if (ch == 1) {
+ // stereo output from plugin on a mono track
+ for (size_t i = 0; i < m_blockSize; ++i) {
+ m_processBuffers[0][i] +=
+ plugin->getAudioOutputBuffers()[ch][i];
+ m_processBuffers[0][i] /= 2;
+ }
+ } else {
+ break;
+ }
+
+ ++ch;
+ }
+ }
+
+ // special handling for pan on mono tracks
+
+ bool allZeros = true;
+
+ if (targetChannels == 2 && channels == 1) {
+
+ for (size_t i = 0; i < m_blockSize; ++i) {
+
+ sample_t sample = m_processBuffers[0][i];
+
+ m_processBuffers[0][i] = sample * rec.gainLeft;
+ m_processBuffers[1][i] = sample * rec.gainRight;
+
+ if (allZeros && sample != 0.0)
+ allZeros = false;
+ }
+
+ rec.buffers[0]->write(m_processBuffers[0], m_blockSize);
+ rec.buffers[1]->write(m_processBuffers[1], m_blockSize);
+
+ } else {
+
+ for (unsigned int ch = 0; ch < targetChannels; ++ch) {
+
+ float gain = ((ch == 0) ? rec.gainLeft :
+ (ch == 1) ? rec.gainRight : rec.volume);
+
+ for (size_t i = 0; i < m_blockSize; ++i) {
+
+ // handle volume and pan
+ m_processBuffers[ch][i] *= gain;
+
+ if (allZeros && m_processBuffers[ch][i] != 0.0)
+ allZeros = false;
+ }
+
+ rec.buffers[ch]->write(m_processBuffers[ch], m_blockSize);
+ }
+ }
+
+ bool dormant = true;
+
+ if (allZeros) {
+ rec.zeroFrames += m_blockSize;
+ for (unsigned int ch = 0; ch < targetChannels; ++ch) {
+ if (rec.buffers[ch]->getReadSpace() > rec.zeroFrames) {
+ dormant = false;
+ }
+ }
+ } else {
+ rec.zeroFrames = 0;
+ dormant = false;
+ }
+
+#ifdef DEBUG_MIXER
+ if ((id % 100) == 0 && m_driver->isPlaying())
+ std::cerr << "AudioInstrumentMixer::processBlock(" << id << "): setting dormant to " << dormant << std::endl;
+#endif
+
+ rec.dormant = dormant;
+ bufferTime = bufferTime + RealTime::frame2RealTime(m_blockSize,
+ m_sampleRate);
+
+ rec.filledTo = bufferTime;
+
+#ifdef DEBUG_MIXER
+
+ if ((id % 100) == 0)
+ std::cerr << "AudioInstrumentMixer::processBlock(" << id << "): done, returning " << haveMore << std::endl;
+#endif
+
+ return haveMore;
+}
+
+void
+AudioInstrumentMixer::kick(bool wantLock)
+{
+ // Needs to be RT safe if wantLock is not specified
+
+ if (wantLock)
+ getLock();
+
+ bool readSomething = false;
+ processBlocks(readSomething);
+ if (readSomething)
+ m_fileReader->signal();
+
+ if (wantLock)
+ releaseLock();
+}
+
+
+void
+AudioInstrumentMixer::threadRun()
+{
+ while (!m_exiting) {
+
+ if (m_driver->areClocksRunning()) {
+ kick(false);
+ }
+
+ RealTime t = m_driver->getAudioMixBufferLength();
+ t = t / 2;
+ if (t < RealTime(0, 10000000))
+ t = RealTime(0, 10000000); // 10ms minimum
+
+ struct timeval now;
+ gettimeofday(&now, 0);
+ t = t + RealTime(now.tv_sec, now.tv_usec * 1000);
+
+ struct timespec timeout;
+ timeout.tv_sec = t.sec;
+ timeout.tv_nsec = t.nsec;
+
+ pthread_cond_timedwait(&m_condition, &m_lock, &timeout);
+ pthread_testcancel();
+ }
+}
+
+
+
+AudioFileReader::AudioFileReader(SoundDriver *driver,
+ unsigned int sampleRate) :
+ AudioThread("AudioFileReader", driver, sampleRate)
+{
+ // nothing else here
+}
+
+AudioFileReader::~AudioFileReader()
+{}
+
+void
+AudioFileReader::fillBuffers(const RealTime &currentTime)
+{
+ getLock();
+
+ // Tell every audio file the play start time.
+
+ const AudioPlayQueue *queue = m_driver->getAudioQueue();
+
+ RealTime bufferLength = m_driver->getAudioReadBufferLength();
+ int bufferFrames = RealTime::realTime2Frame(bufferLength, m_sampleRate);
+
+ int poolSize = queue->getMaxBuffersRequired() * 2 + 4;
+ PlayableAudioFile::setRingBufferPoolSizes(poolSize, bufferFrames);
+
+ const AudioPlayQueue::FileSet &files = queue->getAllScheduledFiles();
+
+#ifdef DEBUG_READER
+
+ std::cerr << "AudioFileReader::fillBuffers: have " << files.size() << " audio files total" << std::endl;
+#endif
+
+ for (AudioPlayQueue::FileSet::const_iterator fi = files.begin();
+ fi != files.end(); ++fi) {
+ (*fi)->clearBuffers();
+ }
+
+ int allocated = 0;
+ for (AudioPlayQueue::FileSet::const_iterator fi = files.begin();
+ fi != files.end(); ++fi) {
+ (*fi)->fillBuffers(currentTime);
+ if ((*fi)->getEndTime() >= currentTime) {
+ if (++allocated == poolSize)
+ break;
+ } // else the file's ring buffers will have been returned
+ }
+
+ releaseLock();
+}
+
+bool
+AudioFileReader::kick(bool wantLock)
+{
+ if (wantLock)
+ getLock();
+
+ RealTime now = m_driver->getSequencerTime();
+ const AudioPlayQueue *queue = m_driver->getAudioQueue();
+
+ bool someFilled = false;
+
+ // Tell files that are playing or will be playing in the next few
+ // seconds to update.
+
+ AudioPlayQueue::FileSet playing;
+
+ queue->getPlayingFiles
+ (now, RealTime(3, 0) + m_driver->getAudioReadBufferLength(), playing);
+
+ for (AudioPlayQueue::FileSet::iterator fi = playing.begin();
+ fi != playing.end(); ++fi) {
+
+ if (!(*fi)->isBuffered()) {
+ // fillBuffers has not been called on this file. This
+ // happens when a file is unmuted during playback. The
+ // results are unpredictable because we can no longer
+ // synchronise with the correct JACK callback slice at
+ // this point, but this is better than allowing the file
+ // to update from its start as would otherwise happen.
+ (*fi)->fillBuffers(now);
+ someFilled = true;
+ } else {
+ if ((*fi)->updateBuffers())
+ someFilled = true;
+ }
+ }
+
+ if (wantLock)
+ releaseLock();
+
+ return someFilled;
+}
+
+void
+AudioFileReader::threadRun()
+{
+ while (!m_exiting) {
+
+ // struct timeval now;
+ // gettimeofday(&now, 0);
+ // RealTime t = RealTime(now.tv_sec, now.tv_usec * 1000);
+
+ bool someFilled = false;
+
+ if (m_driver->areClocksRunning()) {
+ someFilled = kick(false);
+ }
+
+ if (someFilled) {
+
+ releaseLock();
+ getLock();
+
+ } else {
+
+ RealTime bt = m_driver->getAudioReadBufferLength();
+ bt = bt / 2;
+ if (bt < RealTime(0, 10000000))
+ bt = RealTime(0, 10000000); // 10ms minimum
+
+ struct timeval now;
+ gettimeofday(&now, 0);
+ RealTime t = bt + RealTime(now.tv_sec, now.tv_usec * 1000);
+
+ struct timespec timeout;
+ timeout.tv_sec = t.sec;
+ timeout.tv_nsec = t.nsec;
+
+ pthread_cond_timedwait(&m_condition, &m_lock, &timeout);
+ pthread_testcancel();
+ }
+ }
+}
+
+
+
+AudioFileWriter::AudioFileWriter(SoundDriver *driver,
+ unsigned int sampleRate) :
+ AudioThread("AudioFileWriter", driver, sampleRate)
+{
+ InstrumentId instrumentBase;
+ int instrumentCount;
+ m_driver->getAudioInstrumentNumbers(instrumentBase, instrumentCount);
+
+ for (InstrumentId id = instrumentBase;
+ id < instrumentBase + instrumentCount; ++id) {
+
+ // prefill with zero files in all slots, so that we can
+ // refer to the map without a lock (as the number of
+ // instruments won't change)
+
+ m_files[id] = FilePair(0, 0);
+ }
+}
+
+AudioFileWriter::~AudioFileWriter()
+{}
+
+
+bool
+AudioFileWriter::openRecordFile(InstrumentId id,
+ const std::string &fileName)
+{
+ getLock();
+
+ if (m_files[id].first) {
+ releaseLock();
+ std::cerr << "AudioFileWriter::openRecordFile: already have record file for instrument " << id << "!" << std::endl;
+ return false; // already have one
+ }
+
+#ifdef DEBUG_WRITER
+ std::cerr << "AudioFileWriter::openRecordFile: instrument id is " << id << std::endl;
+#endif
+
+ MappedAudioFader *fader = m_driver->getMappedStudio()->getAudioFader(id);
+
+ RealTime bufferLength = m_driver->getAudioWriteBufferLength();
+ int bufferSamples = RealTime::realTime2Frame(bufferLength, m_sampleRate);
+ bufferSamples = ((bufferSamples / 1024) + 1) * 1024;
+
+ if (fader) {
+ float fch = 2;
+ (void)fader->getProperty(MappedAudioFader::Channels, fch);
+ int channels = (int)fch;
+
+ RIFFAudioFile::SubFormat format = m_driver->getAudioRecFileFormat();
+
+ int bytesPerSample = (format == RIFFAudioFile::PCM ? 2 : 4) * channels;
+ int bitsPerSample = (format == RIFFAudioFile::PCM ? 16 : 32);
+
+ AudioFile *recordFile = 0;
+
+ try {
+ recordFile =
+ new WAVAudioFile(fileName,
+ channels, // channels
+ m_sampleRate, // samples per second
+ m_sampleRate *
+ bytesPerSample, // bytes per second
+ bytesPerSample, // bytes per frame
+ bitsPerSample); // bits per sample
+
+ // open the file for writing
+ //
+ if (!recordFile->write()) {
+ std::cerr << "AudioFileWriter::openRecordFile: failed to open " << fileName << " for writing" << std::endl;
+ delete recordFile;
+ releaseLock();
+ return false;
+ }
+ } catch (SoundFile::BadSoundFileException e) {
+ std::cerr << "AudioFileWriter::openRecordFile: failed to open " << fileName << " for writing: " << e.getMessage() << std::endl;
+ delete recordFile;
+ releaseLock();
+ return false;
+ }
+
+ RecordableAudioFile *raf = new RecordableAudioFile(recordFile,
+ bufferSamples);
+ m_files[id].second = raf;
+ m_files[id].first = recordFile;
+
+#ifdef DEBUG_WRITER
+
+ std::cerr << "AudioFileWriter::openRecordFile: created " << channels << "-channel file at " << fileName << " (id is " << recordFile->getId() << ")" << std::endl;
+#endif
+
+ releaseLock();
+ return true;
+ }
+
+ std::cerr << "AudioFileWriter::openRecordFile: no audio fader for record instrument " << id << "!" << std::endl;
+ releaseLock();
+ return false;
+}
+
+
+void
+AudioFileWriter::write(InstrumentId id,
+ const sample_t *samples,
+ int channel,
+ size_t sampleCount)
+{
+ if (!m_files[id].first)
+ return ; // no file
+ if (m_files[id].second->buffer(samples, channel, sampleCount) < sampleCount) {
+ m_driver->reportFailure(MappedEvent::FailureDiscOverrun);
+ }
+}
+
+bool
+AudioFileWriter::closeRecordFile(InstrumentId id, AudioFileId &returnedId)
+{
+ if (!m_files[id].first)
+ return false;
+
+ returnedId = m_files[id].first->getId();
+ m_files[id].second->setStatus(RecordableAudioFile::DEFUNCT);
+
+#ifdef DEBUG_WRITER
+
+ std::cerr << "AudioFileWriter::closeRecordFile: instrument " << id << " file set defunct (file ID is " << returnedId << ")" << std::endl;
+#endif
+
+ // Don't reset the file pointers here; that will be done in the
+ // next call to kick(). Doesn't really matter when that happens,
+ // but let's encourage it to happen soon just for certainty.
+ signal();
+
+ return true;
+}
+
+bool
+AudioFileWriter::haveRecordFileOpen(InstrumentId id)
+{
+ InstrumentId instrumentBase;
+ int instrumentCount;
+ m_driver->getAudioInstrumentNumbers(instrumentBase, instrumentCount);
+
+ if (id < instrumentBase || id >= instrumentBase + instrumentCount) {
+ return false;
+ }
+
+ return (m_files[id].first &&
+ (m_files[id].second->getStatus() != RecordableAudioFile::DEFUNCT));
+}
+
+bool
+AudioFileWriter::haveRecordFilesOpen()
+{
+ InstrumentId instrumentBase;
+ int instrumentCount;
+ m_driver->getAudioInstrumentNumbers(instrumentBase, instrumentCount);
+
+ for (InstrumentId id = instrumentBase; id < instrumentBase + instrumentCount; ++id) {
+
+ if (m_files[id].first &&
+ (m_files[id].second->getStatus() != RecordableAudioFile::DEFUNCT)) {
+#ifdef DEBUG_WRITER
+ std::cerr << "AudioFileWriter::haveRecordFilesOpen: found open record file for instrument " << id << std::endl;
+#endif
+
+ return true;
+ }
+ }
+#ifdef DEBUG_WRITER
+ std::cerr << "AudioFileWriter::haveRecordFilesOpen: nope" << std::endl;
+#endif
+
+ return false;
+}
+
+void
+AudioFileWriter::kick(bool wantLock)
+{
+ if (wantLock)
+ getLock();
+
+ InstrumentId instrumentBase;
+ int instrumentCount;
+ m_driver->getAudioInstrumentNumbers(instrumentBase, instrumentCount);
+
+ for (InstrumentId id = instrumentBase;
+ id < instrumentBase + instrumentCount; ++id) {
+
+ if (!m_files[id].first)
+ continue;
+
+ RecordableAudioFile *raf = m_files[id].second;
+
+ if (raf->getStatus() == RecordableAudioFile::DEFUNCT) {
+
+#ifdef DEBUG_WRITER
+ std::cerr << "AudioFileWriter::kick: found defunct file on instrument " << id << std::endl;
+#endif
+
+ m_files[id].first = 0;
+ delete raf; // also deletes the AudioFile
+ m_files[id].second = 0;
+
+ } else {
+#ifdef DEBUG_WRITER
+ std::cerr << "AudioFileWriter::kick: writing file on instrument " << id << std::endl;
+#endif
+
+ raf->write();
+ }
+ }
+
+ if (wantLock)
+ releaseLock();
+}
+
+void
+AudioFileWriter::threadRun()
+{
+ while (!m_exiting) {
+
+ kick(false);
+
+ RealTime t = m_driver->getAudioWriteBufferLength();
+ t = t / 2;
+ if (t < RealTime(0, 10000000))
+ t = RealTime(0, 10000000); // 10ms minimum
+
+ struct timeval now;
+ gettimeofday(&now, 0);
+ t = t + RealTime(now.tv_sec, now.tv_usec * 1000);
+
+ struct timespec timeout;
+ timeout.tv_sec = t.sec;
+ timeout.tv_nsec = t.nsec;
+
+ pthread_cond_timedwait(&m_condition, &m_lock, &timeout);
+ pthread_testcancel();
+ }
+}
+
+
+}
+
diff --git a/src/sound/AudioProcess.h b/src/sound/AudioProcess.h
new file mode 100644
index 0000000..b517bc9
--- /dev/null
+++ b/src/sound/AudioProcess.h
@@ -0,0 +1,390 @@
+// -*- c-basic-offset: 4 -*-
+
+/*
+ Rosegarden
+ A sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <bownie@bownie.com>
+
+ 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_PROCESS_H_
+#define _AUDIO_PROCESS_H_
+
+#include "SoundDriver.h"
+#include "Instrument.h"
+#include "RealTime.h"
+#include "RingBuffer.h"
+#include "RunnablePluginInstance.h"
+#include "AudioPlayQueue.h"
+#include "RecordableAudioFile.h"
+
+namespace Rosegarden
+{
+
+class AudioThread
+{
+public:
+ typedef float sample_t;
+
+ AudioThread(std::string name, // for diagnostics
+ SoundDriver *driver,
+ unsigned int sampleRate);
+
+ virtual ~AudioThread();
+
+ // This is to be called by the owning class after construction.
+ void run();
+
+ // This is to be called by the owning class to cause the thread to
+ // exit and clean up, before destruction.
+ void terminate();
+
+ bool running() const { return m_running; }
+
+ int getLock();
+ int tryLock();
+ int releaseLock();
+ void signal();
+
+protected:
+ virtual void threadRun() = 0;
+ virtual int getPriority() { return 0; }
+
+ std::string m_name;
+
+ SoundDriver *m_driver;
+ unsigned int m_sampleRate;
+
+ pthread_t m_thread;
+ pthread_mutex_t m_lock;
+ pthread_cond_t m_condition;
+ bool m_running;
+ volatile bool m_exiting;
+
+private:
+ static void *staticThreadRun(void *arg);
+ static void staticThreadCleanup(void *arg);
+};
+
+
+class AudioInstrumentMixer;
+
+class AudioBussMixer : public AudioThread
+{
+public:
+ AudioBussMixer(SoundDriver *driver,
+ AudioInstrumentMixer *instrumentMixer,
+ unsigned int sampleRate,
+ unsigned int blockSize);
+
+ virtual ~AudioBussMixer();
+
+ void kick(bool wantLock = true, bool signalInstrumentMixer = true);
+
+ /**
+ * Prebuffer. This should be called only when the transport is
+ * not running. This also calls fillBuffers on the instrument
+ * mixer.
+ */
+ void fillBuffers(const RealTime &currentTime);
+
+ /**
+ * Empty and discard buffer contents.
+ */
+ void emptyBuffers();
+
+ int getBussCount() {
+ return m_bussCount;
+ }
+
+ /**
+ * A buss is "dormant" if every readable sample on every one of
+ * its buffers is zero. It can therefore be safely skipped during
+ * playback.
+ */
+ bool isBussDormant(int buss) {
+ return m_bufferMap[buss].dormant;
+ }
+
+ /**
+ * Busses are currently always stereo.
+ */
+ RingBuffer<sample_t> *getRingBuffer(int buss, unsigned int channel) {
+ if (channel < m_bufferMap[buss].buffers.size()) {
+ return m_bufferMap[buss].buffers[channel];
+ } else {
+ return 0;
+ }
+ }
+
+ /// For call from MappedStudio. Pan is in range -100.0 -> 100.0
+ void setBussLevels(int buss, float dB, float pan);
+
+ /// For call regularly from anywhere in a non-RT thread
+ void updateInstrumentConnections();
+
+protected:
+ virtual void threadRun();
+
+ void processBlocks();
+ void generateBuffers();
+
+ AudioInstrumentMixer *m_instrumentMixer;
+ unsigned int m_blockSize;
+ int m_bussCount;
+
+ std::vector<sample_t *> m_processBuffers;
+
+ struct BufferRec
+ {
+ BufferRec() : dormant(true), buffers(), instruments(),
+ gainLeft(0.0), gainRight(0.0) { }
+ ~BufferRec();
+
+ bool dormant;
+
+ std::vector<RingBuffer<sample_t> *> buffers;
+ std::vector<bool> instruments; // index is instrument id minus base
+
+ float gainLeft;
+ float gainRight;
+ };
+
+ typedef std::map<int, BufferRec> BufferMap;
+ BufferMap m_bufferMap;
+};
+
+
+class AudioFileReader;
+class AudioFileWriter;
+
+class AudioInstrumentMixer : public AudioThread
+{
+public:
+ typedef std::vector<RunnablePluginInstance *> PluginList;
+ typedef std::map<InstrumentId, PluginList> PluginMap;
+ typedef std::map<InstrumentId, RunnablePluginInstance *> SynthPluginMap;
+
+ AudioInstrumentMixer(SoundDriver *driver,
+ AudioFileReader *fileReader,
+ unsigned int sampleRate,
+ unsigned int blockSize);
+
+ virtual ~AudioInstrumentMixer();
+
+ void kick(bool wantLock = true);
+
+ void setBussMixer(AudioBussMixer *mixer) { m_bussMixer = mixer; }
+
+ void setPlugin(InstrumentId id, int position, QString identifier);
+ void removePlugin(InstrumentId id, int position);
+ void removeAllPlugins();
+
+ void setPluginPortValue(InstrumentId id, int position,
+ unsigned int port, float value);
+ float getPluginPortValue(InstrumentId id, int position,
+ unsigned int port);
+
+ void setPluginBypass(InstrumentId, int position, bool bypass);
+
+ QStringList getPluginPrograms(InstrumentId, int);
+ QString getPluginProgram(InstrumentId, int);
+ QString getPluginProgram(InstrumentId, int, int, int);
+ unsigned long getPluginProgram(InstrumentId, int, QString);
+ void setPluginProgram(InstrumentId, int, QString);
+
+ QString configurePlugin(InstrumentId, int, QString, QString);
+
+ void resetAllPlugins(bool discardEvents = false);
+ void discardPluginEvents();
+ void destroyAllPlugins();
+
+ RunnablePluginInstance *getSynthPlugin(InstrumentId id) { return m_synths[id]; }
+
+ /**
+ * Return the plugins intended for a particular buss. (By coincidence,
+ * this will also work for instruments, but it's not to be relied on.)
+ * It's purely by historical accident that the instrument mixer happens
+ * to hold buss plugins as well -- this could do with being refactored.
+ */
+ PluginList &getBussPlugins(unsigned int bussId) { return m_plugins[bussId]; }
+
+ /**
+ * Return the total of the plugin latencies for a given instrument
+ * or buss id.
+ */
+ size_t getPluginLatency(unsigned int id);
+
+ /**
+ * Prebuffer. This should be called only when the transport is
+ * not running.
+ */
+ void fillBuffers(const RealTime &currentTime);
+
+ /**
+ * Ensure plugins etc have enough buffers. This is also done by
+ * fillBuffers and only needs to be called here if the extra work
+ * involved in fillBuffers is not desirable.
+ */
+ void allocateBuffers();
+
+ /**
+ * Empty and discard buffer contents.
+ */
+ void emptyBuffers(RealTime currentTime = RealTime::zeroTime);
+
+ /**
+ * An instrument is "empty" if it has no audio files, synths or
+ * plugins assigned to it, and so cannot generate sound. Empty
+ * instruments can safely be ignored during playback.
+ */
+ bool isInstrumentEmpty(InstrumentId id) {
+ return m_bufferMap[id].empty;
+ }
+
+ /**
+ * An instrument is "dormant" if every readable sample on every
+ * one of its buffers is zero. Dormant instruments can safely be
+ * skipped rather than mixed during playback, but they should not
+ * be ignored (unless also empty).
+ */
+ bool isInstrumentDormant(InstrumentId id) {
+ return m_bufferMap[id].dormant;
+ }
+
+ /**
+ * We always have at least two channels (and hence buffers) by
+ * this point, because even on a mono instrument we still have a
+ * Pan setting which will have been applied by the time we get to
+ * these buffers.
+ */
+ RingBuffer<sample_t, 2> *getRingBuffer(InstrumentId id, unsigned int channel) {
+ if (channel < m_bufferMap[id].buffers.size()) {
+ return m_bufferMap[id].buffers[channel];
+ } else {
+ return 0;
+ }
+ }
+
+ /// For call from MappedStudio. Pan is in range -100.0 -> 100.0
+ void setInstrumentLevels(InstrumentId instrument, float dB, float pan);
+
+ /// For call regularly from anywhere in a non-RT thread
+ void updateInstrumentMuteStates();
+
+protected:
+ virtual void threadRun();
+
+ virtual int getPriority() { return 3; }
+
+ void processBlocks(bool &readSomething);
+ void processEmptyBlocks(InstrumentId id);
+ bool processBlock(InstrumentId id, PlayableAudioFile **, size_t, bool &readSomething);
+ void generateBuffers();
+
+ AudioFileReader *m_fileReader;
+ AudioBussMixer *m_bussMixer;
+ unsigned int m_blockSize;
+
+ // The plugin data structures will all be pre-sized and so of
+ // fixed size during normal run time; this will allow us to add
+ // and edit plugins without locking.
+ RunnablePluginInstance *getPluginInstance(InstrumentId, int);
+ PluginMap m_plugins;
+ SynthPluginMap m_synths;
+
+ // maintain the same number of these as the maximum number of
+ // channels on any audio instrument
+ std::vector<sample_t *> m_processBuffers;
+
+ struct BufferRec
+ {
+ BufferRec() : empty(true), dormant(true), zeroFrames(0),
+ filledTo(RealTime::zeroTime), channels(2),
+ buffers(), gainLeft(0.0), gainRight(0.0), volume(0.0),
+ muted(false) { }
+ ~BufferRec();
+
+ bool empty;
+ bool dormant;
+ size_t zeroFrames;
+
+ RealTime filledTo;
+ size_t channels;
+ std::vector<RingBuffer<sample_t, 2> *> buffers;
+
+ float gainLeft;
+ float gainRight;
+ float volume;
+ bool muted;
+ };
+
+ typedef std::map<InstrumentId, BufferRec> BufferMap;
+ BufferMap m_bufferMap;
+};
+
+
+class AudioFileReader : public AudioThread
+{
+public:
+ AudioFileReader(SoundDriver *driver,
+ unsigned int sampleRate);
+
+ virtual ~AudioFileReader();
+
+ bool kick(bool wantLock = true);
+
+ /**
+ * Prebuffer. This should be called only when the transport is
+ * not running.
+ */
+ void fillBuffers(const RealTime &currentTime);
+
+protected:
+ virtual void threadRun();
+};
+
+
+class AudioFileWriter : public AudioThread
+{
+public:
+ AudioFileWriter(SoundDriver *driver,
+ unsigned int sampleRate);
+
+ virtual ~AudioFileWriter();
+
+ void kick(bool wantLock = true);
+
+ bool openRecordFile(InstrumentId id, const std::string &fileName);
+ bool closeRecordFile(InstrumentId id, AudioFileId &returnedId);
+
+ bool haveRecordFileOpen(InstrumentId id);
+ bool haveRecordFilesOpen();
+
+ void write(InstrumentId id, const sample_t *, int channel, size_t samples);
+
+protected:
+ virtual void threadRun();
+
+ typedef std::pair<AudioFile *, RecordableAudioFile *> FilePair;
+ typedef std::map<InstrumentId, FilePair> FileMap;
+ FileMap m_files;
+};
+
+
+}
+
+#endif
+
diff --git a/src/sound/AudioTimeStretcher.cpp b/src/sound/AudioTimeStretcher.cpp
new file mode 100644
index 0000000..392693e
--- /dev/null
+++ b/src/sound/AudioTimeStretcher.cpp
@@ -0,0 +1,667 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
+
+/*
+ Sonic Visualiser
+ An audio file viewer and annotation editor.
+ Centre for Digital Music, Queen Mary, University of London.
+ This file copyright 2006 Chris Cannam and QMUL.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#include "AudioTimeStretcher.h"
+
+#include <iostream>
+#include <fstream>
+#include <cassert>
+#include <cstring>
+
+namespace Rosegarden
+{
+
+static double mod(double x, double y) { return x - (y * floor(x / y)); }
+static float modf(float x, float y) { return x - (y * floorf(x / y)); }
+
+static double princarg(double a) { return mod(a + M_PI, -2 * M_PI) + M_PI; }
+static float princargf(float a) { return modf(a + M_PI, -2 * M_PI) + M_PI; }
+
+
+//#define DEBUG_AUDIO_TIME_STRETCHER 1
+
+AudioTimeStretcher::AudioTimeStretcher(size_t sampleRate,
+ size_t channels,
+ float ratio,
+ bool sharpen,
+ size_t maxOutputBlockSize) :
+ m_sampleRate(sampleRate),
+ m_channels(channels),
+ m_maxOutputBlockSize(maxOutputBlockSize),
+ m_ratio(ratio),
+ m_sharpen(sharpen),
+ m_totalCount(0),
+ m_transientCount(0),
+ m_n2sum(0),
+ m_n2total(0),
+ m_adjustCount(50)
+{
+ pthread_mutex_t initialisingMutex = PTHREAD_MUTEX_INITIALIZER;
+ memcpy(&m_mutex, &initialisingMutex, sizeof(pthread_mutex_t));
+
+ initialise();
+}
+
+AudioTimeStretcher::~AudioTimeStretcher()
+{
+ std::cerr << "AudioTimeStretcher::~AudioTimeStretcher" << std::endl;
+
+ std::cerr << "AudioTimeStretcher::~AudioTimeStretcher: actual ratio = " << (m_totalCount > 0 ? (float (m_n2total) / float(m_totalCount * m_n1)) : 1.f) << ", ideal = " << m_ratio << ", nominal = " << getRatio() << ")" << std::endl;
+
+ cleanup();
+
+ pthread_mutex_destroy(&m_mutex);
+}
+
+void
+AudioTimeStretcher::initialise()
+{
+ std::cerr << "AudioTimeStretcher::initialise" << std::endl;
+
+ calculateParameters();
+
+ m_analysisWindow = new SampleWindow<float>(SampleWindow<float>::Hanning, m_wlen);
+ m_synthesisWindow = new SampleWindow<float>(SampleWindow<float>::Hanning, m_wlen);
+
+ m_prevPhase = new float *[m_channels];
+ m_prevAdjustedPhase = new float *[m_channels];
+
+ m_prevTransientMag = (float *)fftwf_malloc(sizeof(float) * (m_wlen / 2 + 1));
+ m_prevTransientScore = 0;
+ m_prevTransient = false;
+
+ m_tempbuf = (float *)fftwf_malloc(sizeof(float) * m_wlen);
+
+ m_time = new float *[m_channels];
+ m_freq = new fftwf_complex *[m_channels];
+ m_plan = new fftwf_plan[m_channels];
+ m_iplan = new fftwf_plan[m_channels];
+
+ m_inbuf = new RingBuffer<float> *[m_channels];
+ m_outbuf = new RingBuffer<float> *[m_channels];
+ m_mashbuf = new float *[m_channels];
+
+ m_modulationbuf = (float *)fftwf_malloc(sizeof(float) * m_wlen);
+
+ for (size_t c = 0; c < m_channels; ++c) {
+
+ m_prevPhase[c] = (float *)fftwf_malloc(sizeof(float) * (m_wlen / 2 + 1));
+ m_prevAdjustedPhase[c] = (float *)fftwf_malloc(sizeof(float) * (m_wlen / 2 + 1));
+
+ m_time[c] = (float *)fftwf_malloc(sizeof(float) * m_wlen);
+ m_freq[c] = (fftwf_complex *)fftwf_malloc(sizeof(fftwf_complex) *
+ (m_wlen / 2 + 1));
+
+ m_plan[c] = fftwf_plan_dft_r2c_1d(m_wlen, m_time[c], m_freq[c], FFTW_ESTIMATE);
+ m_iplan[c] = fftwf_plan_dft_c2r_1d(m_wlen, m_freq[c], m_time[c], FFTW_ESTIMATE);
+
+ m_outbuf[c] = new RingBuffer<float>
+ ((m_maxOutputBlockSize + m_wlen) * 2);
+ m_inbuf[c] = new RingBuffer<float>
+ (lrintf(m_outbuf[c]->getSize() / m_ratio) + m_wlen);
+
+ std::cerr << "making inbuf size " << m_inbuf[c]->getSize() << " (outbuf size is " << m_outbuf[c]->getSize() << ", ratio " << m_ratio << ")" << std::endl;
+
+
+ m_mashbuf[c] = (float *)fftwf_malloc(sizeof(float) * m_wlen);
+
+ for (size_t i = 0; i < m_wlen; ++i) {
+ m_mashbuf[c][i] = 0.0;
+ }
+
+ for (size_t i = 0; i <= m_wlen/2; ++i) {
+ m_prevPhase[c][i] = 0.0;
+ m_prevAdjustedPhase[c][i] = 0.0;
+ }
+ }
+
+ for (size_t i = 0; i < m_wlen; ++i) {
+ m_modulationbuf[i] = 0.0;
+ }
+
+ for (size_t i = 0; i <= m_wlen/2; ++i) {
+ m_prevTransientMag[i] = 0.0;
+ }
+}
+
+void
+AudioTimeStretcher::calculateParameters()
+{
+ std::cerr << "AudioTimeStretcher::calculateParameters" << std::endl;
+
+ m_wlen = 1024;
+
+ //!!! In transient sharpening mode, we need to pick the window
+ //length so as to be more or less fixed in audio duration (i.e. we
+ //need to exploit the sample rate)
+
+ //!!! have to work out the relationship between wlen and transient
+ //threshold
+
+ if (m_ratio < 1) {
+ if (m_ratio < 0.4) {
+ m_n1 = 1024;
+ m_wlen = 2048;
+ } else if (m_ratio < 0.8) {
+ m_n1 = 512;
+ } else {
+ m_n1 = 256;
+ }
+ if (shouldSharpen()) {
+ m_wlen = 2048;
+ }
+ m_n2 = lrintf(m_n1 * m_ratio);
+ } else {
+ if (m_ratio > 2) {
+ m_n2 = 512;
+ m_wlen = 4096;
+ } else if (m_ratio > 1.6) {
+ m_n2 = 384;
+ m_wlen = 2048;
+ } else {
+ m_n2 = 256;
+ }
+ if (shouldSharpen()) {
+ if (m_wlen < 2048) m_wlen = 2048;
+ }
+ m_n1 = lrintf(m_n2 / m_ratio);
+ if (m_n1 == 0) {
+ m_n1 = 1;
+ m_n2 = m_ratio;
+ }
+ }
+
+ m_transientThreshold = lrintf(m_wlen / 4.5);
+
+ m_totalCount = 0;
+ m_transientCount = 0;
+ m_n2sum = 0;
+ m_n2total = 0;
+ m_n2list.clear();
+
+ std::cerr << "AudioTimeStretcher: channels = " << m_channels
+ << ", ratio = " << m_ratio
+ << ", n1 = " << m_n1 << ", n2 = " << m_n2 << ", wlen = "
+ << m_wlen << ", max = " << m_maxOutputBlockSize << std::endl;
+// << ", outbuflen = " << m_outbuf[0]->getSize() << std::endl;
+}
+
+void
+AudioTimeStretcher::cleanup()
+{
+ std::cerr << "AudioTimeStretcher::cleanup" << std::endl;
+
+ for (size_t c = 0; c < m_channels; ++c) {
+
+ fftwf_destroy_plan(m_plan[c]);
+ fftwf_destroy_plan(m_iplan[c]);
+
+ fftwf_free(m_time[c]);
+ fftwf_free(m_freq[c]);
+
+ fftwf_free(m_mashbuf[c]);
+ fftwf_free(m_prevPhase[c]);
+ fftwf_free(m_prevAdjustedPhase[c]);
+
+ delete m_inbuf[c];
+ delete m_outbuf[c];
+ }
+
+ fftwf_free(m_tempbuf);
+ fftwf_free(m_modulationbuf);
+ fftwf_free(m_prevTransientMag);
+
+ delete[] m_prevPhase;
+ delete[] m_prevAdjustedPhase;
+ delete[] m_inbuf;
+ delete[] m_outbuf;
+ delete[] m_mashbuf;
+ delete[] m_time;
+ delete[] m_freq;
+ delete[] m_plan;
+ delete[] m_iplan;
+
+ delete m_analysisWindow;
+ delete m_synthesisWindow;
+}
+
+void
+AudioTimeStretcher::setRatio(float ratio)
+{
+ pthread_mutex_lock(&m_mutex);
+
+ size_t formerWlen = m_wlen;
+ m_ratio = ratio;
+
+ std::cerr << "AudioTimeStretcher::setRatio: new ratio " << ratio
+ << std::endl;
+
+ calculateParameters();
+
+ if (m_wlen == formerWlen) {
+
+ // This is the only container whose size depends on m_ratio
+
+ RingBuffer<float> **newin = new RingBuffer<float> *[m_channels];
+
+ size_t formerSize = m_inbuf[0]->getSize();
+ size_t newSize = lrintf(m_outbuf[0]->getSize() / m_ratio) + m_wlen;
+
+ std::cerr << "resizing inbuf from " << formerSize << " to "
+ << newSize << " (outbuf size is " << m_outbuf[0]->getSize() << ", ratio " << m_ratio << ")" << std::endl;
+
+ if (formerSize != newSize) {
+
+ size_t ready = m_inbuf[0]->getReadSpace();
+
+ for (size_t c = 0; c < m_channels; ++c) {
+ newin[c] = new RingBuffer<float>(newSize);
+ }
+
+ if (ready > 0) {
+
+ size_t copy = std::min(ready, newSize);
+ float *tmp = new float[ready];
+
+ for (size_t c = 0; c < m_channels; ++c) {
+ m_inbuf[c]->read(tmp, ready);
+ newin[c]->write(tmp + ready - copy, copy);
+ }
+
+ delete[] tmp;
+ }
+
+ for (size_t c = 0; c < m_channels; ++c) {
+ delete m_inbuf[c];
+ }
+
+ delete[] m_inbuf;
+ m_inbuf = newin;
+ }
+
+ } else {
+
+ std::cerr << "wlen changed" << std::endl;
+ cleanup();
+ initialise();
+ }
+
+ pthread_mutex_unlock(&m_mutex);
+}
+
+size_t
+AudioTimeStretcher::getProcessingLatency() const
+{
+ return getWindowSize() - getInputIncrement();
+}
+
+size_t
+AudioTimeStretcher::getRequiredInputSamples() const
+{
+ size_t rv;
+ pthread_mutex_lock(&m_mutex);
+
+ if (m_inbuf[0]->getReadSpace() >= m_wlen) rv = 0;
+ else rv = m_wlen - m_inbuf[0]->getReadSpace();
+
+ pthread_mutex_unlock(&m_mutex);
+ return rv;
+}
+
+void
+AudioTimeStretcher::putInput(float **input, size_t samples)
+{
+ pthread_mutex_lock(&m_mutex);
+
+ // We need to add samples from input to our internal buffer. When
+ // we have m_windowSize samples in the buffer, we can process it,
+ // move the samples back by m_n1 and write the output onto our
+ // internal output buffer. If we have (samples * ratio) samples
+ // in that, we can write m_n2 of them back to output and return
+ // (otherwise we have to write zeroes).
+
+ // When we process, we write m_wlen to our fixed output buffer
+ // (m_mashbuf). We then pull out the first m_n2 samples from that
+ // buffer, push them into the output ring buffer, and shift
+ // m_mashbuf left by that amount.
+
+ // The processing latency is then m_wlen - m_n2.
+
+ size_t consumed = 0;
+
+ while (consumed < samples) {
+
+ size_t writable = m_inbuf[0]->getWriteSpace();
+ writable = std::min(writable, samples - consumed);
+
+ if (writable == 0) {
+#ifdef DEBUG_AUDIO_TIME_STRETCHER
+ std::cerr << "WARNING: AudioTimeStretcher::putInput: writable == 0 (inbuf has " << m_inbuf[0]->getReadSpace() << " samples available for reading, space for " << m_inbuf[0]->getWriteSpace() << " more)" << std::endl;
+#endif
+ if (m_inbuf[0]->getReadSpace() < m_wlen ||
+ m_outbuf[0]->getWriteSpace() < m_n2) {
+ std::cerr << "WARNING: AudioTimeStretcher::putInput: Inbuf has " << m_inbuf[0]->getReadSpace() << ", outbuf has space for " << m_outbuf[0]->getWriteSpace() << " (n2 = " << m_n2 << ", wlen = " << m_wlen << "), won't be able to process" << std::endl;
+ break;
+ }
+ } else {
+
+#ifdef DEBUG_AUDIO_TIME_STRETCHER
+ std::cerr << "writing " << writable << " from index " << consumed << " to inbuf, consumed will be " << consumed + writable << std::endl;
+#endif
+
+ for (size_t c = 0; c < m_channels; ++c) {
+ m_inbuf[c]->write(input[c] + consumed, writable);
+ }
+ consumed += writable;
+ }
+
+ while (m_inbuf[0]->getReadSpace() >= m_wlen &&
+ m_outbuf[0]->getWriteSpace() >= m_n2) {
+
+ // We know we have at least m_wlen samples available
+ // in m_inbuf. We need to peek m_wlen of them for
+ // processing, and then read m_n1 to advance the read
+ // pointer.
+
+ for (size_t c = 0; c < m_channels; ++c) {
+
+ size_t got = m_inbuf[c]->peek(m_tempbuf, m_wlen);
+ assert(got == m_wlen);
+
+ analyseBlock(c, m_tempbuf);
+ }
+
+ bool transient = false;
+ if (shouldSharpen()) transient = isTransient();
+
+ size_t n2 = m_n2;
+
+ if (transient) {
+ n2 = m_n1;
+ }
+
+ ++m_totalCount;
+ if (transient) ++m_transientCount;
+
+ m_n2sum += n2;
+ m_n2total += n2;
+
+ if (m_totalCount > 50 && m_transientCount < m_totalCount) {
+
+ int fixed = m_transientCount * m_n1;
+
+ float idealTotal = m_totalCount * m_n1 * m_ratio;
+ float idealSquashy = idealTotal - fixed;
+
+ float squashyCount = m_totalCount - m_transientCount;
+
+ float fn2 = idealSquashy / squashyCount;
+
+ n2 = int(fn2);
+
+ float remainder = fn2 - n2;
+ if (drand48() < remainder) ++n2;
+
+#ifdef DEBUG_AUDIO_TIME_STRETCHER
+ if (n2 != m_n2) {
+ std::cerr << m_n2 << " -> " << n2 << " (ideal = " << (idealSquashy / squashyCount) << ")" << std::endl;
+ }
+#endif
+ }
+
+ for (size_t c = 0; c < m_channels; ++c) {
+
+ synthesiseBlock(c, m_mashbuf[c],
+ c == 0 ? m_modulationbuf : 0,
+ m_prevTransient ? m_n1 : m_n2);
+
+
+#ifdef DEBUG_AUDIO_TIME_STRETCHER
+ std::cerr << "writing first " << m_n2 << " from mashbuf, skipping " << m_n1 << " on inbuf " << std::endl;
+#endif
+ m_inbuf[c]->skip(m_n1);
+
+ for (size_t i = 0; i < n2; ++i) {
+ if (m_modulationbuf[i] > 0.f) {
+ m_mashbuf[c][i] /= m_modulationbuf[i];
+ }
+ }
+
+ m_outbuf[c]->write(m_mashbuf[c], n2);
+
+ for (size_t i = 0; i < m_wlen - n2; ++i) {
+ m_mashbuf[c][i] = m_mashbuf[c][i + n2];
+ }
+
+ for (size_t i = m_wlen - n2; i < m_wlen; ++i) {
+ m_mashbuf[c][i] = 0.0f;
+ }
+ }
+
+ m_prevTransient = transient;
+
+ for (size_t i = 0; i < m_wlen - n2; ++i) {
+ m_modulationbuf[i] = m_modulationbuf[i + n2];
+ }
+
+ for (size_t i = m_wlen - n2; i < m_wlen; ++i) {
+ m_modulationbuf[i] = 0.0f;
+ }
+
+ if (!transient) m_n2 = n2;
+ }
+
+
+#ifdef DEBUG_AUDIO_TIME_STRETCHER
+ std::cerr << "loop ended: inbuf read space " << m_inbuf[0]->getReadSpace() << ", outbuf write space " << m_outbuf[0]->getWriteSpace() << std::endl;
+#endif
+ }
+
+#ifdef DEBUG_AUDIO_TIME_STRETCHER
+ std::cerr << "AudioTimeStretcher::putInput returning" << std::endl;
+#endif
+
+ pthread_mutex_unlock(&m_mutex);
+
+// std::cerr << "ratio: nominal: " << getRatio() << " actual: "
+// << m_total2 << "/" << m_total1 << " = " << float(m_total2) / float(m_total1) << " ideal: " << m_ratio << std::endl;
+}
+
+size_t
+AudioTimeStretcher::getAvailableOutputSamples() const
+{
+ pthread_mutex_lock(&m_mutex);
+
+ size_t rv = m_outbuf[0]->getReadSpace();
+
+ pthread_mutex_unlock(&m_mutex);
+ return rv;
+}
+
+void
+AudioTimeStretcher::getOutput(float **output, size_t samples)
+{
+ pthread_mutex_lock(&m_mutex);
+
+ if (m_outbuf[0]->getReadSpace() < samples) {
+ std::cerr << "WARNING: AudioTimeStretcher::getOutput: not enough data (yet?) (" << m_outbuf[0]->getReadSpace() << " < " << samples << ")" << std::endl;
+ size_t fill = samples - m_outbuf[0]->getReadSpace();
+ for (size_t c = 0; c < m_channels; ++c) {
+ for (size_t i = 0; i < fill; ++i) {
+ output[c][i] = 0.0;
+ }
+ m_outbuf[c]->read(output[c] + fill, m_outbuf[c]->getReadSpace());
+ }
+ } else {
+#ifdef DEBUG_AUDIO_TIME_STRETCHER
+ std::cerr << "enough data - writing " << samples << " from outbuf" << std::endl;
+#endif
+ for (size_t c = 0; c < m_channels; ++c) {
+ m_outbuf[c]->read(output[c], samples);
+ }
+ }
+
+#ifdef DEBUG_AUDIO_TIME_STRETCHER
+ std::cerr << "AudioTimeStretcher::getOutput returning" << std::endl;
+#endif
+
+ pthread_mutex_unlock(&m_mutex);
+}
+
+void
+AudioTimeStretcher::analyseBlock(size_t c, float *buf)
+{
+ size_t i;
+
+ // buf contains m_wlen samples
+
+#ifdef DEBUG_AUDIO_TIME_STRETCHER
+ std::cerr << "AudioTimeStretcher::analyseBlock (channel " << c << ")" << std::endl;
+#endif
+
+ m_analysisWindow->cut(buf);
+
+ for (i = 0; i < m_wlen/2; ++i) {
+ float temp = buf[i];
+ buf[i] = buf[i + m_wlen/2];
+ buf[i + m_wlen/2] = temp;
+ }
+
+ for (i = 0; i < m_wlen; ++i) {
+ m_time[c][i] = buf[i];
+ }
+
+ fftwf_execute(m_plan[c]); // m_time -> m_freq
+}
+
+bool
+AudioTimeStretcher::isTransient()
+{
+ int count = 0;
+
+ for (size_t i = 0; i <= m_wlen/2; ++i) {
+
+ float real = 0.f, imag = 0.f;
+
+ for (size_t c = 0; c < m_channels; ++c) {
+ real += m_freq[c][i][0];
+ imag += m_freq[c][i][1];
+ }
+
+ float sqrmag = (real * real + imag * imag);
+
+ if (m_prevTransientMag[i] > 0.f) {
+ float diff = 10.f * log10f(sqrmag / m_prevTransientMag[i]);
+ if (diff > 3.f) ++count;
+ }
+
+ m_prevTransientMag[i] = sqrmag;
+ }
+
+ bool isTransient = false;
+
+// if (count > m_transientThreshold &&
+// count > m_prevTransientScore * 1.2) {
+ if (count > m_prevTransientScore &&
+ count > m_transientThreshold &&
+ count - m_prevTransientScore > m_wlen / 20) {
+ isTransient = true;
+
+#ifdef DEBUG_AUDIO_TIME_STRETCHER
+ std::cerr << "isTransient (count = " << count << ", prev = " << m_prevTransientScore << ", diff = " << count - m_prevTransientScore << ", ratio = " << (m_totalCount > 0 ? (float (m_n2sum) / float(m_totalCount * m_n1)) : 1.f) << ", ideal = " << m_ratio << ", nominal = " << getRatio() << ")" << std::endl;
+// } else {
+// std::cerr << " !transient (count = " << count << ", prev = " << m_prevTransientScore << ", diff = " << count - m_prevTransientScore << ")" << std::endl;
+#endif
+ }
+
+ m_prevTransientScore = count;
+
+ return isTransient;
+}
+
+void
+AudioTimeStretcher::synthesiseBlock(size_t c,
+ float *out,
+ float *modulation,
+ size_t lastStep)
+{
+ bool unchanged = (lastStep == m_n1);
+
+ for (size_t i = 0; i <= m_wlen/2; ++i) {
+
+ float phase = princargf(atan2f(m_freq[c][i][1], m_freq[c][i][0]));
+ float adjustedPhase = phase;
+
+// float binfreq = float(m_sampleRate * i) / m_wlen;
+
+ if (!unchanged) {
+
+ float mag = sqrtf(m_freq[c][i][0] * m_freq[c][i][0] +
+ m_freq[c][i][1] * m_freq[c][i][1]);
+
+ float omega = (2 * M_PI * m_n1 * i) / m_wlen;
+
+ float expectedPhase = m_prevPhase[c][i] + omega;
+
+ float phaseError = princargf(phase - expectedPhase);
+
+ float phaseIncrement = (omega + phaseError) / m_n1;
+
+ adjustedPhase = m_prevAdjustedPhase[c][i] +
+ lastStep * phaseIncrement;
+
+ float real = mag * cosf(adjustedPhase);
+ float imag = mag * sinf(adjustedPhase);
+ m_freq[c][i][0] = real;
+ m_freq[c][i][1] = imag;
+ }
+
+ m_prevPhase[c][i] = phase;
+ m_prevAdjustedPhase[c][i] = adjustedPhase;
+ }
+
+ fftwf_execute(m_iplan[c]); // m_freq -> m_time, inverse fft
+
+ for (size_t i = 0; i < m_wlen/2; ++i) {
+ float temp = m_time[c][i];
+ m_time[c][i] = m_time[c][i + m_wlen/2];
+ m_time[c][i + m_wlen/2] = temp;
+ }
+
+ for (size_t i = 0; i < m_wlen; ++i) {
+ m_time[c][i] = m_time[c][i] / m_wlen;
+ }
+
+ m_synthesisWindow->cut(m_time[c]);
+
+ for (size_t i = 0; i < m_wlen; ++i) {
+ out[i] += m_time[c][i];
+ }
+
+ if (modulation) {
+
+ float area = m_analysisWindow->getArea();
+
+ for (size_t i = 0; i < m_wlen; ++i) {
+ float val = m_synthesisWindow->getValue(i);
+ modulation[i] += val * area;
+ }
+ }
+}
+
+
+
+}
+
diff --git a/src/sound/AudioTimeStretcher.h b/src/sound/AudioTimeStretcher.h
new file mode 100644
index 0000000..c5d0170
--- /dev/null
+++ b/src/sound/AudioTimeStretcher.h
@@ -0,0 +1,221 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
+
+/*
+ Rosegarden
+ A MIDI and audio sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+/*
+ This file is derived from
+
+ Sonic Visualiser
+ An audio file viewer and annotation editor.
+ Centre for Digital Music, Queen Mary, University of London.
+ This file copyright 2006 Chris Cannam and QMUL.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either 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_TIME_STRETCHER_H_
+#define _AUDIO_TIME_STRETCHER_H_
+
+#include "SampleWindow.h"
+#include "RingBuffer.h"
+
+#include <fftw3.h>
+#include <pthread.h>
+#include <list>
+
+namespace Rosegarden
+{
+
+/**
+ * A time stretcher that alters the performance speed of audio,
+ * preserving pitch.
+ *
+ * This is based on the straightforward phase vocoder with phase
+ * unwrapping (as in e.g. the DAFX book pp275-), with optional
+ * percussive transient detection to avoid smearing percussive notes
+ * and resynchronise phases, and adding a stream API for real-time
+ * use. Principles and methods from Chris Duxbury, AES 2002 and 2004
+ * thesis; Emmanuel Ravelli, DAFX 2005; Dan Barry, ISSC 2005 on
+ * percussion detection; code by Chris Cannam.
+ */
+
+class AudioTimeStretcher
+{
+public:
+ AudioTimeStretcher(size_t sampleRate,
+ size_t channels,
+ float ratio,
+ bool sharpen,
+ size_t maxOutputBlockSize);
+ virtual ~AudioTimeStretcher();
+
+ /**
+ * Return the number of samples that would need to be added via
+ * putInput in order to provoke the time stretcher into doing some
+ * time stretching and making more output samples available.
+ * This will be an estimate, if transient sharpening is on; the
+ * caller may need to do the put/get/test cycle more than once.
+ */
+ size_t getRequiredInputSamples() const;
+
+ /**
+ * Put (and possibly process) a given number of input samples.
+ * Number should usually equal the value returned from
+ * getRequiredInputSamples().
+ */
+ void putInput(float **input, size_t samples);
+
+ /**
+ * Get the number of processed samples ready for reading.
+ */
+ size_t getAvailableOutputSamples() const;
+
+ /**
+ * Get some processed samples.
+ */
+ void getOutput(float **output, size_t samples);
+
+ //!!! and reset?
+
+ /**
+ * Change the time stretch ratio.
+ */
+ void setRatio(float ratio);
+
+ /**
+ * Get the hop size for input.
+ */
+ size_t getInputIncrement() const { return m_n1; }
+
+ /**
+ * Get the hop size for output.
+ */
+ size_t getOutputIncrement() const { return m_n2; }
+
+ /**
+ * Get the window size for FFT processing.
+ */
+ size_t getWindowSize() const { return m_wlen; }
+
+ /**
+ * Get the stretch ratio.
+ */
+ float getRatio() const { return float(m_n2) / float(m_n1); }
+
+ /**
+ * Return whether this time stretcher will attempt to sharpen transients.
+ */
+ bool getSharpening() const { return m_sharpen; }
+
+ /**
+ * Return the number of channels for this time stretcher.
+ */
+ size_t getChannelCount() const { return m_channels; }
+
+ /**
+ * Get the latency added by the time stretcher, in sample frames.
+ * This will be exact if transient sharpening is off, or approximate
+ * if it is on.
+ */
+ size_t getProcessingLatency() const;
+
+protected:
+ /**
+ * Process a single phase vocoder frame from "in" into
+ * m_freq[channel].
+ */
+ void analyseBlock(size_t channel, float *in); // into m_freq[channel]
+
+ /**
+ * Examine m_freq[0..m_channels-1] and return whether a percussive
+ * transient is found.
+ */
+ bool isTransient();
+
+ /**
+ * Resynthesise from m_freq[channel] adding in to "out",
+ * adjusting phases on the basis of a prior step size of lastStep.
+ * Also add the window shape in to the modulation array (if
+ * present) -- for use in ensuring the output has the correct
+ * magnitude afterwards.
+ */
+ void synthesiseBlock(size_t channel, float *out, float *modulation,
+ size_t lastStep);
+
+ void initialise();
+ void calculateParameters();
+ void cleanup();
+
+ bool shouldSharpen() {
+ return m_sharpen && (m_ratio > 0.25);
+ }
+
+ size_t m_sampleRate;
+ size_t m_channels;
+ size_t m_maxOutputBlockSize;
+ float m_ratio;
+ bool m_sharpen;
+ size_t m_n1;
+ size_t m_n2;
+ size_t m_wlen;
+ SampleWindow<float> *m_analysisWindow;
+ SampleWindow<float> *m_synthesisWindow;
+
+ int m_totalCount;
+ int m_transientCount;
+
+ int m_n2sum;
+ int m_n2total;
+ std::list<int> m_n2list;
+ int m_adjustCount;
+
+ float **m_prevPhase;
+ float **m_prevAdjustedPhase;
+
+ float *m_prevTransientMag;
+ int m_prevTransientScore;
+ int m_transientThreshold;
+ bool m_prevTransient;
+
+ float *m_tempbuf;
+ float **m_time;
+ fftwf_complex **m_freq;
+ fftwf_plan *m_plan;
+ fftwf_plan *m_iplan;
+
+ RingBuffer<float> **m_inbuf;
+ RingBuffer<float> **m_outbuf;
+ float **m_mashbuf;
+ float *m_modulationbuf;
+
+ mutable pthread_mutex_t m_mutex;
+};
+
+}
+
+
+#endif
diff --git a/src/sound/Audit.cpp b/src/sound/Audit.cpp
new file mode 100644
index 0000000..25a6c8b
--- /dev/null
+++ b/src/sound/Audit.cpp
@@ -0,0 +1,30 @@
+// -*- c-indentation-style:"stroustrup" c-basic-offset: 4 -*-
+
+/*
+ Rosegarden
+ A sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <bownie@bownie.com>
+
+ 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 "Audit.h"
+
+namespace Rosegarden
+{
+
+std::string Audit::m_audit;
+
+}
+
diff --git a/src/sound/Audit.h b/src/sound/Audit.h
new file mode 100644
index 0000000..4e0a20e
--- /dev/null
+++ b/src/sound/Audit.h
@@ -0,0 +1,60 @@
+// -*- c-indentation-style:"stroustrup" c-basic-offset: 4 -*-
+
+/*
+ Rosegarden
+ A sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <bownie@bownie.com>
+
+ 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 _AUDIT_H_
+#define _AUDIT_H_
+
+#include <string>
+#include <iostream>
+
+#if (__GNUC__ < 3)
+#include <strstream>
+#define stringstream strstream
+#else
+#include <sstream>
+#endif
+
+// A staggeringly simple-minded audit trail implementation.
+
+namespace Rosegarden {
+
+class Audit : public std::stringstream
+{
+public:
+ Audit() { }
+
+ virtual ~Audit() {
+#if (__GNUC__ < 3)
+ *this << std::ends;
+#endif
+ std::cerr << str();
+ m_audit += str();
+ }
+
+ static std::string getAudit() { return m_audit; }
+
+protected:
+ static std::string m_audit;
+};
+
+}
+
+#endif
diff --git a/src/sound/BWFAudioFile.cpp b/src/sound/BWFAudioFile.cpp
new file mode 100644
index 0000000..c38820f
--- /dev/null
+++ b/src/sound/BWFAudioFile.cpp
@@ -0,0 +1,171 @@
+// -*- c-basic-offset: 4 -*-
+
+/*
+ Rosegarden
+ A sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <bownie@bownie.com>
+
+ 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 "BWFAudioFile.h"
+#include "RealTime.h"
+
+#if (__GNUC__ < 3)
+#include <strstream>
+#define stringstream strstream
+#else
+#include <sstream>
+#endif
+
+using std::cout;
+using std::cerr;
+using std::endl;
+
+
+namespace Rosegarden
+{
+
+BWFAudioFile::BWFAudioFile(const unsigned int &id,
+ const std::string &name,
+ const std::string &fileName):
+ RIFFAudioFile(id, name, fileName)
+{
+ m_type = WAV;
+
+}
+
+BWFAudioFile::BWFAudioFile(const std::string &fileName,
+ unsigned int channels = 1,
+ unsigned int sampleRate = 48000,
+ unsigned int bytesPerSecond = 6000,
+ unsigned int bytesPerFrame = 2,
+ unsigned int bitsPerSample = 16):
+ RIFFAudioFile(0, "", fileName)
+{
+ m_type = WAV;
+ m_bitsPerSample = bitsPerSample;
+ m_sampleRate = sampleRate;
+ m_bytesPerSecond = bytesPerSecond;
+ m_bytesPerFrame = bytesPerFrame;
+ m_channels = channels;
+}
+
+BWFAudioFile::~BWFAudioFile()
+{}
+
+bool
+BWFAudioFile::open()
+{
+ // if already open
+ if (m_inFile && (*m_inFile))
+ return true;
+
+ m_inFile = new std::ifstream(m_fileName.c_str(),
+ std::ios::in | std::ios::binary);
+
+ if (!(*m_inFile)) {
+ m_type = UNKNOWN;
+ return false;
+ }
+
+ // Get the file size and store it for comparison later
+ //
+ m_fileSize = m_fileInfo->size();
+
+ try {
+ parseHeader();
+ } catch (BadSoundFileException s) {
+ //throw(s);
+ return false;
+ }
+
+ return true;
+}
+
+// Open the file for writing, write out the header and move
+// to the data chunk to accept samples. We fill in all the
+// totals when we close().
+//
+bool
+BWFAudioFile::write()
+{
+ // close if we're open
+ if (m_outFile) {
+ m_outFile->close();
+ delete m_outFile;
+ }
+
+ // open for writing
+ m_outFile = new std::ofstream(m_fileName.c_str(),
+ std::ios::out | std::ios::binary);
+
+ if (!(*m_outFile))
+ return false;
+
+ // write out format header chunk and prepare for sample writing
+ //
+ writeFormatChunk();
+
+ return true;
+}
+
+void
+BWFAudioFile::close()
+{
+ if (m_outFile == 0)
+ return ;
+
+ m_outFile->seekp(0, std::ios::end);
+ unsigned int totalSize = m_outFile->tellp();
+
+ // seek to first length position
+ m_outFile->seekp(4, std::ios::beg);
+
+ // write complete file size minus 8 bytes to here
+ putBytes(m_outFile, getLittleEndianFromInteger(totalSize - 8, 4));
+
+ // reseek from start forward 40
+ m_outFile->seekp(40, std::ios::beg);
+
+ // write the data chunk size to end
+ putBytes(m_outFile, getLittleEndianFromInteger(totalSize - 44, 4));
+
+ m_outFile->close();
+
+ delete m_outFile;
+ m_outFile = 0;
+}
+
+// Set the AudioFile meta data according to WAV file format specification.
+//
+void
+BWFAudioFile::parseHeader()
+{
+ // Read the format chunk and populate the file data. A plain WAV
+ // file only has this chunk. Exceptions tumble through.
+ //
+ readFormatChunk();
+
+}
+
+std::streampos
+BWFAudioFile::getDataOffset()
+{
+ return 0;
+}
+
+
+
+}
diff --git a/src/sound/BWFAudioFile.h b/src/sound/BWFAudioFile.h
new file mode 100644
index 0000000..d6717aa
--- /dev/null
+++ b/src/sound/BWFAudioFile.h
@@ -0,0 +1,94 @@
+// -*- c-basic-offset: 4 -*-
+
+/*
+ Rosegarden
+ A sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <bownie@bownie.com>
+
+ 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.
+*/
+
+
+// Specialisation of a RIFF file - the WAV defines a format chunk
+// holding audio file meta data and a data chunk with interleaved
+// sample bytes.
+//
+
+#include "RIFFAudioFile.h"
+
+
+#ifndef _BWFAUDIOFILE_H_
+#define _BWFAUDIOFILE_H_
+
+namespace Rosegarden
+{
+
+class BWFAudioFile : public RIFFAudioFile
+{
+public:
+ BWFAudioFile(const unsigned int &id,
+ const std::string &name,
+ const std::string &fileName);
+
+ BWFAudioFile(const std::string &fileName,
+ unsigned int channels,
+ unsigned int sampleRate,
+ unsigned int bytesPerSecond,
+ unsigned int bytesPerFrame,
+ unsigned int bitsPerSample);
+
+ ~BWFAudioFile();
+
+ // Override these methods for the WAV
+ //
+ virtual bool open();
+ virtual bool write();
+ virtual void close();
+
+ // Get all header information
+ //
+ void parseHeader();
+
+ //
+ //
+ //virtual std::vector<float> getPreview(const RealTime &resolution);
+
+ // Offset to start of sample data
+ //
+ virtual std::streampos getDataOffset();
+
+ // Peak file name
+ //
+ virtual std::string getPeakFilename()
+ { return (m_fileName + std::string(".pk")); }
+
+
+ //!!! NOT IMPLEMENTED YET
+ //
+ virtual bool decode(const unsigned char *sourceData,
+ size_t sourceBytes,
+ size_t targetSampleRate,
+ size_t targetChannels,
+ size_t targetFrames,
+ std::vector<float *> &targetData,
+ bool addToResultBuffers = false) { return false; }
+
+protected:
+
+};
+
+}
+
+
+#endif // _BWFUDIOFILE_H_
diff --git a/src/sound/ControlBlock.cpp b/src/sound/ControlBlock.cpp
new file mode 100644
index 0000000..d0bb6a8
--- /dev/null
+++ b/src/sound/ControlBlock.cpp
@@ -0,0 +1,181 @@
+// -*- c-basic-offset: 4 -*-
+
+/*
+ Rosegarden
+ A sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <bownie@bownie.com>
+
+ 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 <cstring>
+
+#include "ControlBlock.h"
+
+namespace Rosegarden
+{
+
+ControlBlock::ControlBlock(unsigned int maxTrackId)
+ : m_maxTrackId(maxTrackId),
+ m_solo(false),
+ m_routing(true),
+ m_thruFilter(0),
+ m_recordFilter(0),
+ m_selectedTrack(0)
+{
+ m_metronomeInfo.muted = true;
+ m_metronomeInfo.instrumentId = 0;
+ for (unsigned int i = 0; i < CONTROLBLOCK_MAX_NB_TRACKS; ++i) {
+ m_trackInfo[i].muted = true;
+ m_trackInfo[i].deleted = true;
+ m_trackInfo[i].armed = true;
+ m_trackInfo[i].instrumentId = 0;
+ }
+}
+
+ControlBlock::ControlBlock()
+{
+ // DO NOT initialize anything - this ctor is meant to be used by
+ // the sequencer, through a placement new over an mmapped file.
+}
+
+void ControlBlock::updateTrackData(Track* t)
+{
+ if (t) {
+ setInstrumentForTrack(t->getId(), t->getInstrument());
+ setTrackArmed(t->getId(), t->isArmed());
+ setTrackMuted(t->getId(), t->isMuted());
+ setTrackDeleted(t->getId(), false);
+ setTrackChannelFilter(t->getId(), t->getMidiInputChannel());
+ setTrackDeviceFilter(t->getId(), t->getMidiInputDevice());
+ if (t->getId() > m_maxTrackId)
+ m_maxTrackId = t->getId();
+ }
+}
+
+void ControlBlock::setInstrumentForTrack(TrackId trackId, InstrumentId instId)
+{
+ if (trackId < CONTROLBLOCK_MAX_NB_TRACKS)
+ m_trackInfo[trackId].instrumentId = instId;
+}
+
+InstrumentId ControlBlock::getInstrumentForTrack(TrackId trackId) const
+{
+ if (trackId < CONTROLBLOCK_MAX_NB_TRACKS)
+ return m_trackInfo[trackId].instrumentId;
+ return 0;
+}
+
+void ControlBlock::setTrackMuted(TrackId trackId, bool mute)
+{
+ if (trackId < CONTROLBLOCK_MAX_NB_TRACKS)
+ m_trackInfo[trackId].muted = mute;
+}
+
+bool ControlBlock::isTrackMuted(TrackId trackId) const
+{
+ if (trackId < CONTROLBLOCK_MAX_NB_TRACKS)
+ return m_trackInfo[trackId].muted;
+ return true;
+}
+
+void ControlBlock::setTrackArmed(TrackId trackId, bool armed)
+{
+ if (trackId < CONTROLBLOCK_MAX_NB_TRACKS)
+ m_trackInfo[trackId].armed = armed;
+}
+
+bool ControlBlock::isTrackArmed(TrackId trackId) const
+{
+ if (trackId < CONTROLBLOCK_MAX_NB_TRACKS)
+ return m_trackInfo[trackId].armed;
+ return false;
+}
+
+void ControlBlock::setTrackDeleted(TrackId trackId, bool deleted)
+{
+ if (trackId < CONTROLBLOCK_MAX_NB_TRACKS)
+ m_trackInfo[trackId].deleted = deleted;
+}
+
+bool ControlBlock::isTrackDeleted(TrackId trackId) const
+{
+ if (trackId < CONTROLBLOCK_MAX_NB_TRACKS)
+ return m_trackInfo[trackId].deleted;
+ return true;
+}
+
+void ControlBlock::setTrackChannelFilter(TrackId trackId, char channel)
+{
+ if (trackId < CONTROLBLOCK_MAX_NB_TRACKS)
+ m_trackInfo[trackId].channelFilter = channel;
+}
+
+char ControlBlock::getTrackChannelFilter(TrackId trackId) const
+{
+ if (trackId < CONTROLBLOCK_MAX_NB_TRACKS)
+ return m_trackInfo[trackId].channelFilter;
+ return -1;
+}
+
+void ControlBlock::setTrackDeviceFilter(TrackId trackId, DeviceId device)
+{
+ if (trackId < CONTROLBLOCK_MAX_NB_TRACKS)
+ m_trackInfo[trackId].deviceFilter = device;
+}
+
+DeviceId ControlBlock::getTrackDeviceFilter(TrackId trackId) const
+{
+ if (trackId < CONTROLBLOCK_MAX_NB_TRACKS)
+ return m_trackInfo[trackId].deviceFilter;
+ return Device::ALL_DEVICES;
+}
+
+bool ControlBlock::isInstrumentMuted(InstrumentId instrumentId) const
+{
+ for (unsigned int i = 0; i <= m_maxTrackId; ++i) {
+ if (m_trackInfo[i].instrumentId == instrumentId &&
+ !m_trackInfo[i].deleted && !m_trackInfo[i].muted)
+ return false;
+ }
+ return true;
+}
+
+bool ControlBlock::isInstrumentUnused(InstrumentId instrumentId) const
+{
+ for (unsigned int i = 0; i <= m_maxTrackId; ++i) {
+ if (m_trackInfo[i].instrumentId == instrumentId &&
+ !m_trackInfo[i].deleted)
+ return false;
+ }
+ return true;
+}
+
+InstrumentId ControlBlock::getInstrumentForEvent(unsigned int dev, unsigned int chan)
+{
+ for (unsigned int i = 0; i <= m_maxTrackId; ++i) {
+ if (!m_trackInfo[i].deleted && m_trackInfo[i].armed) {
+ if (((m_trackInfo[i].deviceFilter == Device::ALL_DEVICES) ||
+ (m_trackInfo[i].deviceFilter == dev)) &&
+ ((m_trackInfo[i].channelFilter == -1) ||
+ (m_trackInfo[i].channelFilter == chan)))
+ return m_trackInfo[i].instrumentId;
+ }
+ }
+ // There is not a matching filter, return the selected track instrument
+ return getInstrumentForTrack(getSelectedTrack());
+}
+
+
+}
diff --git a/src/sound/ControlBlock.h b/src/sound/ControlBlock.h
new file mode 100644
index 0000000..6db0ee5
--- /dev/null
+++ b/src/sound/ControlBlock.h
@@ -0,0 +1,128 @@
+// -*- c-basic-offset: 4 -*-
+
+/*
+ Rosegarden
+ A sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <bownie@bownie.com>
+
+ 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 _CONTROLBLOCK_H_
+#define _CONTROLBLOCK_H_
+
+#include "MidiProgram.h"
+#include "Track.h"
+
+namespace Rosegarden
+{
+
+/**
+ * ONLY PUT PLAIN DATA HERE - NO POINTERS EVER
+ */
+struct TrackInfo
+{
+ bool deleted;
+ bool muted;
+ bool armed;
+ char channelFilter;
+ DeviceId deviceFilter;
+ InstrumentId instrumentId;
+};
+
+#define CONTROLBLOCK_MAX_NB_TRACKS 1024 // can't be a symbol
+
+/**
+ * Sequencer control block, mmapped by the GUI
+ */
+class ControlBlock
+{
+public:
+ /// ctor for GUI
+ ControlBlock(unsigned int maxTrackId);
+
+ /// ctor for sequencer - all data is read from mmapped file
+ ControlBlock();
+
+ unsigned int getMaxTrackId() const { return m_maxTrackId; }
+ void updateTrackData(Track*);
+
+ void setInstrumentForTrack(TrackId trackId, InstrumentId);
+ InstrumentId getInstrumentForTrack(TrackId trackId) const;
+
+ void setTrackArmed(TrackId trackId, bool);
+ bool isTrackArmed(TrackId trackId) const;
+
+ void setTrackMuted(TrackId trackId, bool);
+ bool isTrackMuted(TrackId trackId) const;
+
+ void setTrackDeleted(TrackId trackId, bool);
+ bool isTrackDeleted(TrackId trackId) const;
+
+ void setTrackChannelFilter(TrackId trackId, char);
+ char getTrackChannelFilter(TrackId trackId) const;
+
+ void setTrackDeviceFilter(TrackId trackId, DeviceId);
+ DeviceId getTrackDeviceFilter(TrackId trackId) const;
+
+ bool isInstrumentMuted(InstrumentId instrumentId) const;
+ bool isInstrumentUnused(InstrumentId instrumentId) const;
+
+ void setInstrumentForMetronome(InstrumentId instId) { m_metronomeInfo.instrumentId = instId; }
+ InstrumentId getInstrumentForMetronome() const { return m_metronomeInfo.instrumentId; }
+
+ void setMetronomeMuted(bool mute) { m_metronomeInfo.muted = mute; }
+ bool isMetronomeMuted() const { return m_metronomeInfo.muted; }
+
+ bool isSolo() const { return m_solo; }
+ void setSolo(bool value) { m_solo = value; }
+ TrackId getSelectedTrack() const { return m_selectedTrack; }
+ void setSelectedTrack(TrackId track) { m_selectedTrack = track; }
+
+ void setThruFilter(MidiFilter filter) { m_thruFilter = filter; }
+ MidiFilter getThruFilter() const { return m_thruFilter; }
+
+ void setRecordFilter(MidiFilter filter) { m_recordFilter = filter; }
+ MidiFilter getRecordFilter() const { return m_recordFilter; }
+
+ void setMidiRoutingEnabled(bool enabled) { m_routing = enabled; }
+ bool isMidiRoutingEnabled() const { return m_routing; }
+
+ /**
+ * Gets an InstrumentId for the given DeviceId and Channel. If there
+ * is an armed track having a matching device and channel filters,
+ * this method returns the instrument assigned to the track, even if
+ * there are more tracks matching the same filters. If there is not a
+ * single match, it returns the instrument assigned to the selected
+ * track.
+ */
+ InstrumentId getInstrumentForEvent(unsigned int dev,
+ unsigned int chan);
+
+protected:
+ //--------------- Data members ---------------------------------
+ // PUT ONLY PLAIN DATA HERE - NO POINTERS EVER
+ unsigned int m_maxTrackId;
+ bool m_solo;
+ bool m_routing;
+ MidiFilter m_thruFilter;
+ MidiFilter m_recordFilter;
+ TrackId m_selectedTrack;
+ TrackInfo m_metronomeInfo;
+ TrackInfo m_trackInfo[CONTROLBLOCK_MAX_NB_TRACKS]; // should be high enough for the moment
+};
+
+}
+
+#endif
diff --git a/src/sound/DSSIPluginFactory.cpp b/src/sound/DSSIPluginFactory.cpp
new file mode 100644
index 0000000..8447450
--- /dev/null
+++ b/src/sound/DSSIPluginFactory.cpp
@@ -0,0 +1,396 @@
+// -*- c-basic-offset: 4 -*-
+
+/*
+ Rosegarden
+ A sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <bownie@bownie.com>
+
+ 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 "DSSIPluginFactory.h"
+#include <iostream>
+#include <cstdlib>
+
+#ifdef HAVE_DSSI
+
+#include <dlfcn.h>
+#include "AudioPluginInstance.h"
+#include "DSSIPluginInstance.h"
+#include "MappedStudio.h"
+#include "PluginIdentifier.h"
+
+#ifdef HAVE_LIBLRDF
+#include "lrdf.h"
+#endif // HAVE_LIBLRDF
+
+namespace Rosegarden
+{
+
+DSSIPluginFactory::DSSIPluginFactory() :
+ LADSPAPluginFactory()
+{
+ // nothing else to do
+}
+
+DSSIPluginFactory::~DSSIPluginFactory()
+{
+ // nothing else to do here either
+}
+
+void
+DSSIPluginFactory::enumeratePlugins(MappedObjectPropertyList &list)
+{
+ for (std::vector<QString>::iterator i = m_identifiers.begin();
+ i != m_identifiers.end(); ++i) {
+
+ const DSSI_Descriptor *ddesc = getDSSIDescriptor(*i);
+ if (!ddesc)
+ continue;
+
+ const LADSPA_Descriptor *descriptor = ddesc->LADSPA_Plugin;
+ if (!descriptor)
+ continue;
+
+ // std::cerr << "DSSIPluginFactory::enumeratePlugins: Name " << (descriptor->Name ? descriptor->Name : "NONE" ) << std::endl;
+
+ list.push_back(*i);
+ list.push_back(descriptor->Name);
+ list.push_back(QString("%1").arg(descriptor->UniqueID));
+ list.push_back(descriptor->Label);
+ list.push_back(descriptor->Maker);
+ list.push_back(descriptor->Copyright);
+ list.push_back((ddesc->run_synth || ddesc->run_multiple_synths) ? "true" : "false");
+ list.push_back(ddesc->run_multiple_synths ? "true" : "false");
+ list.push_back(m_taxonomy[descriptor->UniqueID]);
+ list.push_back(QString("%1").arg(descriptor->PortCount));
+
+ for (unsigned long p = 0; p < descriptor->PortCount; ++p) {
+
+ int type = 0;
+ if (LADSPA_IS_PORT_CONTROL(descriptor->PortDescriptors[p])) {
+ type |= PluginPort::Control;
+ } else {
+ type |= PluginPort::Audio;
+ }
+ if (LADSPA_IS_PORT_INPUT(descriptor->PortDescriptors[p])) {
+ type |= PluginPort::Input;
+ } else {
+ type |= PluginPort::Output;
+ }
+
+ list.push_back(QString("%1").arg(p));
+ list.push_back(descriptor->PortNames[p]);
+ list.push_back(QString("%1").arg(type));
+ list.push_back(QString("%1").arg(getPortDisplayHint(descriptor, p)));
+ list.push_back(QString("%1").arg(getPortMinimum(descriptor, p)));
+ list.push_back(QString("%1").arg(getPortMaximum(descriptor, p)));
+ list.push_back(QString("%1").arg(getPortDefault(descriptor, p)));
+ }
+ }
+
+ unloadUnusedLibraries();
+}
+
+
+void
+DSSIPluginFactory::populatePluginSlot(QString identifier, MappedPluginSlot &slot)
+{
+ const LADSPA_Descriptor *descriptor = getLADSPADescriptor(identifier);
+ if (!descriptor)
+ return ;
+
+ if (descriptor) {
+
+ slot.setProperty(MappedPluginSlot::Label, descriptor->Label);
+ slot.setProperty(MappedPluginSlot::PluginName, descriptor->Name);
+ slot.setProperty(MappedPluginSlot::Author, descriptor->Maker);
+ slot.setProperty(MappedPluginSlot::Copyright, descriptor->Copyright);
+ slot.setProperty(MappedPluginSlot::PortCount, descriptor->PortCount);
+ slot.setProperty(MappedPluginSlot::Category, m_taxonomy[descriptor->UniqueID]);
+
+ slot.destroyChildren();
+
+ for (unsigned long i = 0; i < descriptor->PortCount; i++) {
+
+ if (LADSPA_IS_PORT_CONTROL(descriptor->PortDescriptors[i]) &&
+ LADSPA_IS_PORT_INPUT(descriptor->PortDescriptors[i])) {
+
+ MappedStudio *studio = dynamic_cast<MappedStudio *>(slot.getParent());
+ if (!studio) {
+ std::cerr << "WARNING: DSSIPluginFactory::populatePluginSlot: can't find studio" << std::endl;
+ return ;
+ }
+
+ MappedPluginPort *port =
+ dynamic_cast<MappedPluginPort *>
+ (studio->createObject(MappedObject::PluginPort));
+
+ slot.addChild(port);
+ port->setParent(&slot);
+
+ port->setProperty(MappedPluginPort::PortNumber, i);
+ port->setProperty(MappedPluginPort::Name,
+ descriptor->PortNames[i]);
+ port->setProperty(MappedPluginPort::Maximum,
+ getPortMaximum(descriptor, i));
+ port->setProperty(MappedPluginPort::Minimum,
+ getPortMinimum(descriptor, i));
+ port->setProperty(MappedPluginPort::Default,
+ getPortDefault(descriptor, i));
+ port->setProperty(MappedPluginPort::DisplayHint,
+ getPortDisplayHint(descriptor, i));
+ }
+ }
+ }
+
+ //!!! leak here if the plugin is not instantiated too...?
+}
+
+RunnablePluginInstance *
+DSSIPluginFactory::instantiatePlugin(QString identifier,
+ int instrument,
+ int position,
+ unsigned int sampleRate,
+ unsigned int blockSize,
+ unsigned int channels)
+{
+ const DSSI_Descriptor *descriptor = getDSSIDescriptor(identifier);
+
+ if (descriptor) {
+
+ DSSIPluginInstance *instance =
+ new DSSIPluginInstance
+ (this, instrument, identifier, position, sampleRate, blockSize, channels,
+ descriptor);
+
+ m_instances.insert(instance);
+
+ return instance;
+ }
+
+ return 0;
+}
+
+
+const DSSI_Descriptor *
+DSSIPluginFactory::getDSSIDescriptor(QString identifier)
+{
+ QString type, soname, label;
+ PluginIdentifier::parseIdentifier(identifier, type, soname, label);
+
+ if (m_libraryHandles.find(soname) == m_libraryHandles.end()) {
+ loadLibrary(soname);
+ if (m_libraryHandles.find(soname) == m_libraryHandles.end()) {
+ std::cerr << "WARNING: DSSIPluginFactory::getDSSIDescriptor: loadLibrary failed for " << soname << std::endl;
+ return 0;
+ }
+ }
+
+ void *libraryHandle = m_libraryHandles[soname];
+
+ DSSI_Descriptor_Function fn = (DSSI_Descriptor_Function)
+ dlsym(libraryHandle, "dssi_descriptor");
+
+ if (!fn) {
+ std::cerr << "WARNING: DSSIPluginFactory::getDSSIDescriptor: No descriptor function in library " << soname << std::endl;
+ return 0;
+ }
+
+ const DSSI_Descriptor *descriptor = 0;
+
+ int index = 0;
+ while ((descriptor = fn(index))) {
+ if (descriptor->LADSPA_Plugin->Label == label)
+ return descriptor;
+ ++index;
+ }
+
+ std::cerr << "WARNING: DSSIPluginFactory::getDSSIDescriptor: No such plugin as " << label << " in library " << soname << std::endl;
+
+ return 0;
+}
+
+const LADSPA_Descriptor *
+DSSIPluginFactory::getLADSPADescriptor(QString identifier)
+{
+ const DSSI_Descriptor *dssiDescriptor = getDSSIDescriptor(identifier);
+ if (dssiDescriptor)
+ return dssiDescriptor->LADSPA_Plugin;
+ else
+ return 0;
+}
+
+
+std::vector<QString>
+DSSIPluginFactory::getPluginPath()
+{
+ std::vector<QString> pathList;
+ std::string path;
+
+ char *cpath = getenv("DSSI_PATH");
+ if (cpath)
+ path = cpath;
+
+ if (path == "") {
+ path = "/usr/local/lib/dssi:/usr/lib/dssi";
+ char *home = getenv("HOME");
+ if (home)
+ path = std::string(home) + "/.dssi:" + path;
+ }
+
+ std::string::size_type index = 0, newindex = 0;
+
+ while ((newindex = path.find(':', index)) < path.size()) {
+ pathList.push_back(path.substr(index, newindex - index).c_str());
+ index = newindex + 1;
+ }
+
+ pathList.push_back(path.substr(index).c_str());
+
+ return pathList;
+}
+
+
+#ifdef HAVE_LIBLRDF
+std::vector<QString>
+DSSIPluginFactory::getLRDFPath(QString &baseUri)
+{
+ std::vector<QString> pathList = getPluginPath();
+ std::vector<QString> lrdfPaths;
+
+ lrdfPaths.push_back("/usr/local/share/dssi/rdf");
+ lrdfPaths.push_back("/usr/share/dssi/rdf");
+
+ lrdfPaths.push_back("/usr/local/share/ladspa/rdf");
+ lrdfPaths.push_back("/usr/share/ladspa/rdf");
+
+ for (std::vector<QString>::iterator i = pathList.begin();
+ i != pathList.end(); ++i) {
+ lrdfPaths.push_back(*i + "/rdf");
+ }
+
+#ifdef DSSI_BASE
+ baseUri = DSSI_BASE;
+#else
+
+ baseUri = "http://dssi.sourceforge.net/ontology#";
+#endif
+
+ return lrdfPaths;
+}
+#endif
+
+
+void
+DSSIPluginFactory::discoverPlugins(QString soName)
+{
+ void *libraryHandle = dlopen(soName.data(), RTLD_LAZY);
+
+ if (!libraryHandle) {
+ std::cerr << "WARNING: DSSIPluginFactory::discoverPlugins: couldn't dlopen "
+ << soName << " - " << dlerror() << std::endl;
+ return ;
+ }
+
+ DSSI_Descriptor_Function fn = (DSSI_Descriptor_Function)
+ dlsym(libraryHandle, "dssi_descriptor");
+
+ if (!fn) {
+ std::cerr << "WARNING: DSSIPluginFactory::discoverPlugins: No descriptor function in " << soName << std::endl;
+ return ;
+ }
+
+ const DSSI_Descriptor *descriptor = 0;
+
+ int index = 0;
+ while ((descriptor = fn(index))) {
+
+ const LADSPA_Descriptor * ladspaDescriptor = descriptor->LADSPA_Plugin;
+ if (!ladspaDescriptor) {
+ std::cerr << "WARNING: DSSIPluginFactory::discoverPlugins: No LADSPA descriptor for plugin " << index << " in " << soName << std::endl;
+ ++index;
+ continue;
+ }
+
+#ifdef HAVE_LIBLRDF
+ char *def_uri = 0;
+ lrdf_defaults *defs = 0;
+
+ QString category = m_taxonomy[ladspaDescriptor->UniqueID];
+
+ if (category == "" && ladspaDescriptor->Name != 0) {
+ std::string name = ladspaDescriptor->Name;
+ if (name.length() > 4 &&
+ name.substr(name.length() - 4) == " VST") {
+ if (descriptor->run_synth || descriptor->run_multiple_synths) {
+ category = "VST instruments";
+ } else {
+ category = "VST effects";
+ }
+ m_taxonomy[ladspaDescriptor->UniqueID] = category;
+ }
+ }
+
+ // std::cerr << "Plugin id is " << ladspaDescriptor->UniqueID
+ // << ", category is \"" << (category ? category : QString("(none)"))
+ // << "\", name is " << ladspaDescriptor->Name
+ // << ", label is " << ladspaDescriptor->Label
+ // << std::endl;
+
+ def_uri = lrdf_get_default_uri(ladspaDescriptor->UniqueID);
+ if (def_uri) {
+ defs = lrdf_get_setting_values(def_uri);
+ }
+
+ int controlPortNumber = 1;
+
+ for (unsigned long i = 0; i < ladspaDescriptor->PortCount; i++) {
+
+ if (LADSPA_IS_PORT_CONTROL(ladspaDescriptor->PortDescriptors[i])) {
+
+ if (def_uri && defs) {
+
+ for (int j = 0; j < defs->count; j++) {
+ if (defs->items[j].pid == controlPortNumber) {
+ // std::cerr << "Default for this port (" << defs->items[j].pid << ", " << defs->items[j].label << ") is " << defs->items[j].value << "; applying this to port number " << i << " with name " << ladspaDescriptor->PortNames[i] << std::endl;
+ m_portDefaults[ladspaDescriptor->UniqueID][i] =
+ defs->items[j].value;
+ }
+ }
+ }
+
+ ++controlPortNumber;
+ }
+ }
+#endif // HAVE_LIBLRDF
+
+ QString identifier = PluginIdentifier::createIdentifier
+ ("dssi", soName, ladspaDescriptor->Label);
+ m_identifiers.push_back(identifier);
+
+ ++index;
+ }
+
+ if (dlclose(libraryHandle) != 0) {
+ std::cerr << "WARNING: DSSIPluginFactory::discoverPlugins - can't unload " << libraryHandle << std::endl;
+ return ;
+ }
+}
+
+
+}
+
+#endif // HAVE_DSSI
+
diff --git a/src/sound/DSSIPluginFactory.h b/src/sound/DSSIPluginFactory.h
new file mode 100644
index 0000000..8c1bd7c
--- /dev/null
+++ b/src/sound/DSSIPluginFactory.h
@@ -0,0 +1,72 @@
+// -*- c-basic-offset: 4 -*-
+
+/*
+ Rosegarden
+ A sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <bownie@bownie.com>
+
+ 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 _DSSI_PLUGIN_FACTORY_H_
+#define _DSSI_PLUGIN_FACTORY_H_
+
+#ifdef HAVE_DSSI
+
+#include "LADSPAPluginFactory.h"
+#include <dssi.h>
+
+namespace Rosegarden
+{
+
+class DSSIPluginInstance;
+
+class DSSIPluginFactory : public LADSPAPluginFactory
+{
+public:
+ virtual ~DSSIPluginFactory();
+
+ virtual void enumeratePlugins(MappedObjectPropertyList &list);
+
+ virtual void populatePluginSlot(QString identifier, MappedPluginSlot &slot);
+
+ virtual RunnablePluginInstance *instantiatePlugin(QString identifier,
+ int instrumentId,
+ int position,
+ unsigned int sampleRate,
+ unsigned int blockSize,
+ unsigned int channels);
+
+protected:
+ DSSIPluginFactory();
+ friend class PluginFactory;
+
+ virtual std::vector<QString> getPluginPath();
+
+#ifdef HAVE_LIBLRDF
+ virtual std::vector<QString> getLRDFPath(QString &baseUri);
+#endif
+
+ virtual void discoverPlugins(QString soName);
+
+ virtual const LADSPA_Descriptor *getLADSPADescriptor(QString identifier);
+ virtual const DSSI_Descriptor *getDSSIDescriptor(QString identifier);
+};
+
+}
+
+#endif
+
+#endif
+
diff --git a/src/sound/DSSIPluginInstance.cpp b/src/sound/DSSIPluginInstance.cpp
new file mode 100644
index 0000000..2ceb0df
--- /dev/null
+++ b/src/sound/DSSIPluginInstance.cpp
@@ -0,0 +1,1208 @@
+// -*- c-basic-offset: 4 -*-
+
+/*
+ Rosegarden
+ A sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <bownie@bownie.com>
+
+ 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 <iostream>
+#include <cassert>
+#include <cstdlib>
+
+#include "DSSIPluginInstance.h"
+#include "PluginIdentifier.h"
+#include "LADSPAPluginFactory.h"
+
+#ifdef HAVE_DSSI
+
+//#define DEBUG_DSSI 1
+//#define DEBUG_DSSI_PROCESS 1
+
+namespace Rosegarden
+{
+
+#define EVENT_BUFFER_SIZE 1023
+
+DSSIPluginInstance::GroupMap DSSIPluginInstance::m_groupMap;
+snd_seq_event_t **DSSIPluginInstance::m_groupLocalEventBuffers = 0;
+size_t DSSIPluginInstance::m_groupLocalEventBufferCount = 0;
+Scavenger<ScavengerArrayWrapper<snd_seq_event_t *> > DSSIPluginInstance::m_bufferScavenger(2, 10);
+
+
+DSSIPluginInstance::DSSIPluginInstance(PluginFactory *factory,
+ InstrumentId instrument,
+ QString identifier,
+ int position,
+ unsigned long sampleRate,
+ size_t blockSize,
+ int idealChannelCount,
+ const DSSI_Descriptor* descriptor) :
+ RunnablePluginInstance(factory, identifier),
+ m_instrument(instrument),
+ m_position(position),
+ m_descriptor(descriptor),
+ m_programCacheValid(false),
+ m_eventBuffer(EVENT_BUFFER_SIZE),
+ m_blockSize(blockSize),
+ m_idealChannelCount(idealChannelCount),
+ m_sampleRate(sampleRate),
+ m_latencyPort(0),
+ m_run(false),
+ m_runSinceReset(false),
+ m_bypassed(false),
+ m_grouped(false)
+{
+ pthread_mutex_t initialisingMutex = PTHREAD_MUTEX_INITIALIZER;
+ memcpy(&m_processLock, &initialisingMutex, sizeof(pthread_mutex_t));
+
+#ifdef DEBUG_DSSI
+
+ std::cerr << "DSSIPluginInstance::DSSIPluginInstance(" << identifier << ")"
+ << std::endl;
+#endif
+
+ init();
+
+ m_inputBuffers = new sample_t * [m_audioPortsIn.size()];
+ m_outputBuffers = new sample_t * [m_outputBufferCount];
+
+ for (size_t i = 0; i < m_audioPortsIn.size(); ++i) {
+ m_inputBuffers[i] = new sample_t[blockSize];
+ }
+ for (size_t i = 0; i < m_outputBufferCount; ++i) {
+ m_outputBuffers[i] = new sample_t[blockSize];
+ }
+
+ m_ownBuffers = true;
+
+ m_pending.lsb = m_pending.msb = m_pending.program = -1;
+
+ instantiate(sampleRate);
+ if (isOK()) {
+ connectPorts();
+ activate();
+ initialiseGroupMembership();
+ }
+}
+
+DSSIPluginInstance::DSSIPluginInstance(PluginFactory *factory,
+ InstrumentId instrument,
+ QString identifier,
+ int position,
+ unsigned long sampleRate,
+ size_t blockSize,
+ sample_t **inputBuffers,
+ sample_t **outputBuffers,
+ const DSSI_Descriptor* descriptor) :
+ RunnablePluginInstance(factory, identifier),
+ m_instrument(instrument),
+ m_position(position),
+ m_descriptor(descriptor),
+ m_eventBuffer(EVENT_BUFFER_SIZE),
+ m_blockSize(blockSize),
+ m_inputBuffers(inputBuffers),
+ m_outputBuffers(outputBuffers),
+ m_ownBuffers(false),
+ m_idealChannelCount(0),
+ m_sampleRate(sampleRate),
+ m_latencyPort(0),
+ m_run(false),
+ m_runSinceReset(false),
+ m_bypassed(false),
+ m_grouped(false)
+{
+#ifdef DEBUG_DSSI
+ std::cerr << "DSSIPluginInstance::DSSIPluginInstance[buffers supplied](" << identifier << ")"
+ << std::endl;
+#endif
+
+ init();
+
+ m_pending.lsb = m_pending.msb = m_pending.program = -1;
+
+ instantiate(sampleRate);
+ if (isOK()) {
+ connectPorts();
+ activate();
+ if (m_descriptor->run_multiple_synths) {
+ m_grouped = true;
+ initialiseGroupMembership();
+ }
+ }
+}
+
+
+void
+DSSIPluginInstance::init()
+{
+#ifdef DEBUG_DSSI
+ std::cerr << "DSSIPluginInstance::init" << std::endl;
+#endif
+
+ // Discover ports numbers and identities
+ //
+ const LADSPA_Descriptor *descriptor = m_descriptor->LADSPA_Plugin;
+
+ for (unsigned long i = 0; i < descriptor->PortCount; ++i) {
+ if (LADSPA_IS_PORT_AUDIO(descriptor->PortDescriptors[i])) {
+ if (LADSPA_IS_PORT_INPUT(descriptor->PortDescriptors[i])) {
+ m_audioPortsIn.push_back(i);
+ } else {
+ m_audioPortsOut.push_back(i);
+ }
+ } else
+ if (LADSPA_IS_PORT_CONTROL(descriptor->PortDescriptors[i])) {
+ if (LADSPA_IS_PORT_INPUT(descriptor->PortDescriptors[i])) {
+
+ LADSPA_Data *data = new LADSPA_Data(0.0);
+
+ m_controlPortsIn.push_back(std::pair<unsigned long, LADSPA_Data*>
+ (i, data));
+
+ m_backupControlPortsIn.push_back(0.0);
+ m_portChangedSinceProgramChange.push_back(false);
+
+ } else {
+ LADSPA_Data *data = new LADSPA_Data(0.0);
+ m_controlPortsOut.push_back(
+ std::pair<unsigned long, LADSPA_Data*>(i, data));
+ if (!strcmp(descriptor->PortNames[i], "latency") ||
+ !strcmp(descriptor->PortNames[i], "_latency")) {
+#ifdef DEBUG_DSSI
+ std::cerr << "Wooo! We have a latency port!" << std::endl;
+#endif
+
+ m_latencyPort = data;
+ }
+ }
+ }
+#ifdef DEBUG_DSSI
+ else
+ std::cerr << "DSSIPluginInstance::DSSIPluginInstance - "
+ << "unrecognised port type" << std::endl;
+#endif
+
+ }
+
+ m_outputBufferCount = std::max(m_idealChannelCount, m_audioPortsOut.size());
+}
+
+size_t
+DSSIPluginInstance::getLatency()
+{
+#ifdef DEBUG_DSSI
+ // std::cerr << "DSSIPluginInstance::getLatency(): m_latencyPort " << m_latencyPort << ", m_run " << m_run << std::endl;
+#endif
+
+ if (m_latencyPort) {
+ if (!m_run) {
+ for (int i = 0; i < getAudioInputCount(); ++i) {
+ for (int j = 0; j < m_blockSize; ++j) {
+ m_inputBuffers[i][j] = 0.f;
+ }
+ }
+ run(RealTime::zeroTime);
+ }
+#ifdef DEBUG_DSSI
+
+ std::cerr << "DSSIPluginInstance::getLatency(): latency is " << (size_t)(*m_latencyPort + 0.1) << std::endl;
+#endif
+
+ return (size_t)(*m_latencyPort + 0.1);
+ }
+ return 0;
+}
+
+void
+DSSIPluginInstance::silence()
+{
+#ifdef DEBUG_DSSI
+ std::cerr << "DSSIPluginInstance::silence: m_run " << m_run << ", m_runSinceReset " << m_runSinceReset << std::endl;
+#endif
+
+ if (m_run && !m_runSinceReset) {
+ return ;
+ }
+ if (m_instanceHandle != 0) {
+ deactivate();
+ activate();
+ }
+ m_runSinceReset = false;
+}
+
+void
+DSSIPluginInstance::discardEvents()
+{
+#ifdef DEBUG_DSSI
+ std::cerr << "DSSIPluginInstance::discardEvents" << std::endl;
+#endif
+
+ m_eventBuffer.reset();
+}
+
+void
+DSSIPluginInstance::setIdealChannelCount(size_t channels)
+{
+#ifdef DEBUG_DSSI
+ std::cerr << "DSSIPluginInstance::setIdealChannelCount: channel count "
+ << channels << " (was " << m_idealChannelCount << ")" << std::endl;
+#endif
+
+ if (channels == m_idealChannelCount) {
+ silence();
+ return ;
+ }
+
+ if (m_instanceHandle != 0) {
+ deactivate();
+ }
+
+ m_idealChannelCount = channels;
+
+ if (channels > m_outputBufferCount) {
+
+ for (size_t i = 0; i < m_outputBufferCount; ++i) {
+ delete[] m_outputBuffers[i];
+ }
+
+ delete[] m_outputBuffers;
+
+ m_outputBufferCount = channels;
+
+ m_outputBuffers = new sample_t * [m_outputBufferCount];
+
+ for (size_t i = 0; i < m_outputBufferCount; ++i) {
+ m_outputBuffers[i] = new sample_t[m_blockSize];
+ }
+
+ connectPorts();
+ }
+
+ if (m_instanceHandle != 0) {
+ activate();
+ }
+}
+
+void
+DSSIPluginInstance::detachFromGroup()
+{
+ if (!m_grouped)
+ return ;
+ m_groupMap[m_identifier].erase(this);
+ m_grouped = false;
+}
+
+void
+DSSIPluginInstance::initialiseGroupMembership()
+{
+ if (!m_descriptor->run_multiple_synths) {
+ m_grouped = false;
+ return ;
+ }
+
+ //!!! GroupMap is not actually thread-safe.
+
+ size_t pluginsInGroup = m_groupMap[m_identifier].size();
+
+ if (++pluginsInGroup > m_groupLocalEventBufferCount) {
+
+ size_t nextBufferCount = pluginsInGroup * 2;
+
+ snd_seq_event_t **eventLocalBuffers = new snd_seq_event_t * [nextBufferCount];
+
+ for (size_t i = 0; i < m_groupLocalEventBufferCount; ++i) {
+ eventLocalBuffers[i] = m_groupLocalEventBuffers[i];
+ }
+ for (size_t i = m_groupLocalEventBufferCount; i < nextBufferCount; ++i) {
+ eventLocalBuffers[i] = new snd_seq_event_t[EVENT_BUFFER_SIZE];
+ }
+
+ if (m_groupLocalEventBuffers) {
+ m_bufferScavenger.claim(new ScavengerArrayWrapper<snd_seq_event_t *>
+ (m_groupLocalEventBuffers));
+ }
+
+ m_groupLocalEventBuffers = eventLocalBuffers;
+ m_groupLocalEventBufferCount = nextBufferCount;
+ }
+
+ m_grouped = true;
+ m_groupMap[m_identifier].insert(this);
+}
+
+DSSIPluginInstance::~DSSIPluginInstance()
+{
+// std::cerr << "DSSIPluginInstance::~DSSIPluginInstance" << std::endl;
+
+ detachFromGroup();
+
+ if (m_instanceHandle != 0) {
+ deactivate();
+ }
+
+ cleanup();
+
+ for (unsigned int i = 0; i < m_controlPortsIn.size(); ++i)
+ delete m_controlPortsIn[i].second;
+
+ for (unsigned int i = 0; i < m_controlPortsOut.size(); ++i)
+ delete m_controlPortsOut[i].second;
+
+ m_controlPortsIn.clear();
+ m_controlPortsOut.clear();
+
+ if (m_ownBuffers) {
+ for (size_t i = 0; i < m_audioPortsIn.size(); ++i) {
+ delete[] m_inputBuffers[i];
+ }
+ for (size_t i = 0; i < m_outputBufferCount; ++i) {
+ delete[] m_outputBuffers[i];
+ }
+
+ delete[] m_inputBuffers;
+ delete[] m_outputBuffers;
+ }
+
+ m_audioPortsIn.clear();
+ m_audioPortsOut.clear();
+}
+
+
+void
+DSSIPluginInstance::instantiate(unsigned long sampleRate)
+{
+#ifdef DEBUG_DSSI
+ std::cout << "DSSIPluginInstance::instantiate - plugin unique id = "
+ << m_descriptor->LADSPA_Plugin->UniqueID << std::endl;
+#endif
+
+ if (!m_descriptor)
+ return ;
+
+ const LADSPA_Descriptor *descriptor = m_descriptor->LADSPA_Plugin;
+
+ if (!descriptor->instantiate) {
+ std::cerr << "Bad plugin: plugin id " << descriptor->UniqueID
+ << ":" << descriptor->Label
+ << " has no instantiate method!" << std::endl;
+ return ;
+ }
+
+ m_instanceHandle = descriptor->instantiate(descriptor, sampleRate);
+
+ if (m_instanceHandle) {
+
+ if (m_descriptor->get_midi_controller_for_port) {
+
+ for (unsigned long i = 0; i < descriptor->PortCount; ++i) {
+
+ if (LADSPA_IS_PORT_CONTROL(descriptor->PortDescriptors[i]) &&
+ LADSPA_IS_PORT_INPUT(descriptor->PortDescriptors[i])) {
+
+ int controller = m_descriptor->get_midi_controller_for_port
+ (m_instanceHandle, i);
+
+ if (controller != 0 && controller != 32 &&
+ DSSI_IS_CC(controller)) {
+
+ m_controllerMap[DSSI_CC_NUMBER(controller)] = i;
+ }
+ }
+ }
+ }
+ }
+}
+
+void
+DSSIPluginInstance::checkProgramCache()
+{
+ if (m_programCacheValid)
+ return ;
+ m_cachedPrograms.clear();
+
+#ifdef DEBUG_DSSI
+
+ std::cerr << "DSSIPluginInstance::checkProgramCache" << std::endl;
+#endif
+
+ if (!m_descriptor || !m_descriptor->get_program) {
+ m_programCacheValid = true;
+ return ;
+ }
+
+ unsigned long index = 0;
+ const DSSI_Program_Descriptor *programDescriptor;
+ while ((programDescriptor = m_descriptor->get_program(m_instanceHandle, index))) {
+ ++index;
+ ProgramDescriptor d;
+ d.bank = programDescriptor->Bank;
+ d.program = programDescriptor->Program;
+ d.name = QString("%1. %2").arg(index).arg(programDescriptor->Name);
+ m_cachedPrograms.push_back(d);
+ }
+
+#ifdef DEBUG_DSSI
+ std::cerr << "DSSIPluginInstance::checkProgramCache: have " << m_cachedPrograms.size() << " programs" << std::endl;
+#endif
+
+ m_programCacheValid = true;
+}
+
+QStringList
+DSSIPluginInstance::getPrograms()
+{
+#ifdef DEBUG_DSSI
+ std::cerr << "DSSIPluginInstance::getPrograms" << std::endl;
+#endif
+
+ if (!m_descriptor)
+ return QStringList();
+
+ checkProgramCache();
+
+ QStringList programs;
+
+ for (std::vector<ProgramDescriptor>::iterator i = m_cachedPrograms.begin();
+ i != m_cachedPrograms.end(); ++i) {
+ programs.push_back(i->name);
+ }
+
+ return programs;
+}
+
+QString
+DSSIPluginInstance::getProgram(int bank, int program)
+{
+#ifdef DEBUG_DSSI
+ std::cerr << "DSSIPluginInstance::getProgram(" << bank << "," << program << ")" << std::endl;
+#endif
+
+ if (!m_descriptor)
+ return QString();
+
+ checkProgramCache();
+
+ for (std::vector<ProgramDescriptor>::iterator i = m_cachedPrograms.begin();
+ i != m_cachedPrograms.end(); ++i) {
+ if (i->bank == bank && i->program == program)
+ return i->name;
+ }
+
+ return QString();
+}
+
+unsigned long
+DSSIPluginInstance::getProgram(QString name)
+{
+#ifdef DEBUG_DSSI
+ std::cerr << "DSSIPluginInstance::getProgram(" << name << ")" << std::endl;
+#endif
+
+ if (!m_descriptor)
+ return 0;
+
+ checkProgramCache();
+
+ unsigned long rv;
+
+ for (std::vector<ProgramDescriptor>::iterator i = m_cachedPrograms.begin();
+ i != m_cachedPrograms.end(); ++i) {
+ if (i->name == name) {
+ rv = i->bank;
+ rv = (rv << 16) + i->program;
+ return rv;
+ }
+ }
+
+ return 0;
+}
+
+QString
+DSSIPluginInstance::getCurrentProgram()
+{
+ return m_program;
+}
+
+void
+DSSIPluginInstance::selectProgram(QString program)
+{
+ selectProgramAux(program, true);
+}
+
+void
+DSSIPluginInstance::selectProgramAux(QString program, bool backupPortValues)
+{
+#ifdef DEBUG_DSSI
+ std::cerr << "DSSIPluginInstance[" << this << "]::selectProgram(" << program << ", " << backupPortValues << ")" << std::endl;
+#endif
+
+ if (!m_descriptor)
+ return ;
+
+ checkProgramCache();
+
+ if (!m_descriptor->select_program)
+ return ;
+
+ bool found = false;
+ unsigned long bankNo = 0, programNo = 0;
+
+ for (std::vector<ProgramDescriptor>::iterator i = m_cachedPrograms.begin();
+ i != m_cachedPrograms.end(); ++i) {
+
+ if (i->name == program) {
+
+ bankNo = i->bank;
+ programNo = i->program;
+ found = true;
+
+#ifdef DEBUG_DSSI
+
+ std::cerr << "DSSIPluginInstance::selectProgram(" << program << "): found at bank " << bankNo << ", program " << programNo << std::endl;
+#endif
+
+ break;
+ }
+ }
+
+ if (!found)
+ return ;
+ m_program = program;
+
+ // DSSI select_program is an audio context call
+ pthread_mutex_lock(&m_processLock);
+ m_descriptor->select_program(m_instanceHandle, bankNo, programNo);
+ pthread_mutex_unlock(&m_processLock);
+
+#ifdef DEBUG_DSSI
+
+ std::cerr << "DSSIPluginInstance::selectProgram(" << program << "): made select_program(" << bankNo << "," << programNo << ") call" << std::endl;
+#endif
+
+ if (backupPortValues) {
+ for (size_t i = 0; i < m_backupControlPortsIn.size(); ++i) {
+ m_backupControlPortsIn[i] = *m_controlPortsIn[i].second;
+ m_portChangedSinceProgramChange[i] = false;
+ }
+ }
+}
+
+void
+DSSIPluginInstance::activate()
+{
+#ifdef DEBUG_DSSI
+ std::cerr << "DSSIPluginInstance[" << this << "]::activate" << std::endl;
+#endif
+
+ if (!m_descriptor || !m_descriptor->LADSPA_Plugin->activate)
+ return ;
+ m_descriptor->LADSPA_Plugin->activate(m_instanceHandle);
+
+ for (size_t i = 0; i < m_backupControlPortsIn.size(); ++i) {
+ if (m_portChangedSinceProgramChange[i]) {
+#ifdef DEBUG_DSSI
+ std::cerr << "DSSIPluginInstance::activate: setting port " << m_controlPortsIn[i].first << " to " << m_backupControlPortsIn[i] << std::endl;
+#endif
+
+ *m_controlPortsIn[i].second = m_backupControlPortsIn[i];
+ }
+ }
+
+ if (m_program) {
+#ifdef DEBUG_DSSI
+ std::cerr << "DSSIPluginInstance::activate: restoring program " << m_program << std::endl;
+#endif
+
+ selectProgramAux(m_program, false);
+
+ for (size_t i = 0; i < m_backupControlPortsIn.size(); ++i) {
+ if (m_portChangedSinceProgramChange[i]) {
+#ifdef DEBUG_DSSI
+ std::cerr << "DSSIPluginInstance::activate: setting port " << m_controlPortsIn[i].first << " to " << m_backupControlPortsIn[i] << std::endl;
+#endif
+
+ *m_controlPortsIn[i].second = m_backupControlPortsIn[i];
+ }
+ }
+ }
+}
+
+void
+DSSIPluginInstance::connectPorts()
+{
+ if (!m_descriptor || !m_descriptor->LADSPA_Plugin->connect_port)
+ return ;
+#ifdef DEBUG_DSSI
+
+ std::cerr << "DSSIPluginInstance::connectPorts: " << m_audioPortsIn.size()
+ << " audio ports in, " << m_audioPortsOut.size() << " out, "
+ << m_outputBufferCount << " output buffers" << std::endl;
+#endif
+
+ assert(sizeof(LADSPA_Data) == sizeof(float));
+ assert(sizeof(sample_t) == sizeof(float));
+
+ int inbuf = 0, outbuf = 0;
+
+ for (unsigned int i = 0; i < m_audioPortsIn.size(); ++i) {
+ m_descriptor->LADSPA_Plugin->connect_port
+ (m_instanceHandle,
+ m_audioPortsIn[i],
+ (LADSPA_Data *)m_inputBuffers[inbuf]);
+ ++inbuf;
+ }
+
+ for (unsigned int i = 0; i < m_audioPortsOut.size(); ++i) {
+ m_descriptor->LADSPA_Plugin->connect_port
+ (m_instanceHandle,
+ m_audioPortsOut[i],
+ (LADSPA_Data *)m_outputBuffers[outbuf]);
+ ++outbuf;
+ }
+
+ for (unsigned int i = 0; i < m_controlPortsIn.size(); ++i) {
+ m_descriptor->LADSPA_Plugin->connect_port
+ (m_instanceHandle,
+ m_controlPortsIn[i].first,
+ m_controlPortsIn[i].second);
+ }
+
+ for (unsigned int i = 0; i < m_controlPortsOut.size(); ++i) {
+ m_descriptor->LADSPA_Plugin->connect_port
+ (m_instanceHandle,
+ m_controlPortsOut[i].first,
+ m_controlPortsOut[i].second);
+ }
+}
+
+void
+DSSIPluginInstance::setPortValue(unsigned int portNumber, float value)
+{
+#ifdef DEBUG_DSSI
+ std::cerr << "DSSIPluginInstance[" << this << "]::setPortValue(" << portNumber << ") to " << value << std::endl;
+#endif
+
+ for (unsigned int i = 0; i < m_controlPortsIn.size(); ++i) {
+ if (m_controlPortsIn[i].first == portNumber) {
+ LADSPAPluginFactory *f = dynamic_cast<LADSPAPluginFactory *>(m_factory);
+ if (f) {
+ if (value < f->getPortMinimum(m_descriptor->LADSPA_Plugin, portNumber)) {
+ value = f->getPortMinimum(m_descriptor->LADSPA_Plugin, portNumber);
+ }
+ if (value > f->getPortMaximum(m_descriptor->LADSPA_Plugin, portNumber)) {
+ value = f->getPortMaximum(m_descriptor->LADSPA_Plugin, portNumber);
+ }
+ }
+ (*m_controlPortsIn[i].second) = value;
+ m_backupControlPortsIn[i] = value;
+ m_portChangedSinceProgramChange[i] = true;
+ }
+ }
+}
+
+void
+DSSIPluginInstance::setPortValueFromController(unsigned int port, int cv)
+{
+#ifdef DEBUG_DSSI
+ std::cerr << "DSSIPluginInstance::setPortValueFromController(" << port << ") to " << cv << std::endl;
+#endif
+
+ const LADSPA_Descriptor *p = m_descriptor->LADSPA_Plugin;
+ LADSPA_PortRangeHintDescriptor d = p->PortRangeHints[port].HintDescriptor;
+ LADSPA_Data lb = p->PortRangeHints[port].LowerBound;
+ LADSPA_Data ub = p->PortRangeHints[port].UpperBound;
+
+ float value = (float)cv;
+
+ if (!LADSPA_IS_HINT_BOUNDED_BELOW(d)) {
+ if (!LADSPA_IS_HINT_BOUNDED_ABOVE(d)) {
+ /* unbounded: might as well leave the value alone. */
+ } else {
+ /* bounded above only. just shift the range. */
+ value = ub - 127.0f + value;
+ }
+ } else {
+ if (!LADSPA_IS_HINT_BOUNDED_ABOVE(d)) {
+ /* bounded below only. just shift the range. */
+ value = lb + value;
+ } else {
+ /* bounded both ends. more interesting. */
+ /* XXX !!! todo: fill in logarithmic, sample rate &c */
+ value = lb + ((ub - lb) * value / 127.0f);
+ }
+ }
+
+ setPortValue(port, value);
+}
+
+float
+DSSIPluginInstance::getPortValue(unsigned int portNumber)
+{
+#ifdef DEBUG_DSSI
+ std::cerr << "DSSIPluginInstance::getPortValue(" << portNumber << ")" << std::endl;
+#endif
+
+ for (unsigned int i = 0; i < m_controlPortsIn.size(); ++i) {
+ if (m_controlPortsIn[i].first == portNumber) {
+ return (*m_controlPortsIn[i].second);
+ }
+ }
+
+ return 0.0;
+}
+
+QString
+DSSIPluginInstance::configure(QString key,
+ QString value)
+{
+ if (!m_descriptor || !m_descriptor->configure)
+ return QString();
+
+ if (key == PluginIdentifier::RESERVED_PROJECT_DIRECTORY_KEY) {
+#ifdef DSSI_PROJECT_DIRECTORY_KEY
+ key = DSSI_PROJECT_DIRECTORY_KEY;
+#else
+
+ return QString();
+#endif
+
+ }
+
+
+#ifdef DEBUG_DSSI
+ std::cerr << "DSSIPluginInstance::configure(" << key << "," << value << ")" << std::endl;
+#endif
+
+ char *message = m_descriptor->configure(m_instanceHandle, key.data(), value.data());
+
+ m_programCacheValid = false;
+
+ QString qm;
+
+ // Ignore return values from reserved key configuration calls such
+ // as project directory
+#ifdef DSSI_RESERVED_CONFIGURE_PREFIX
+
+ if (key.startsWith(DSSI_RESERVED_CONFIGURE_PREFIX)) {
+ return qm;
+ }
+#endif
+
+ if (message) {
+ if (m_descriptor->LADSPA_Plugin && m_descriptor->LADSPA_Plugin->Label) {
+ qm = QString(m_descriptor->LADSPA_Plugin->Label) + ": ";
+ }
+ qm = qm + message;
+ free(message);
+ }
+
+ return qm;
+}
+
+void
+DSSIPluginInstance::sendEvent(const RealTime &eventTime,
+ const void *e)
+{
+ snd_seq_event_t *event = (snd_seq_event_t *)e;
+#ifdef DEBUG_DSSI_PROCESS
+
+ std::cerr << "DSSIPluginInstance::sendEvent at " << eventTime << std::endl;
+#endif
+
+ snd_seq_event_t ev(*event);
+
+ ev.time.time.tv_sec = eventTime.sec;
+ ev.time.time.tv_nsec = eventTime.nsec;
+
+ // DSSI doesn't use MIDI channels, it uses run_multiple_synths instead.
+ ev.data.note.channel = 0;
+
+ m_eventBuffer.write(&ev, 1);
+}
+
+bool
+DSSIPluginInstance::handleController(snd_seq_event_t *ev)
+{
+ int controller = ev->data.control.param;
+
+#ifdef DEBUG_DSSI_PROCESS
+
+ std::cerr << "DSSIPluginInstance::handleController " << controller << std::endl;
+#endif
+
+ if (controller == 0) { // bank select MSB
+
+ m_pending.msb = ev->data.control.value;
+
+ } else if (controller == 32) { // bank select LSB
+
+ m_pending.lsb = ev->data.control.value;
+
+ } else if (controller > 0 && controller < 128) {
+
+ if (m_controllerMap.find(controller) != m_controllerMap.end()) {
+ int port = m_controllerMap[controller];
+ setPortValueFromController(port, ev->data.control.value);
+ } else {
+ return true; // pass through to plugin
+ }
+ }
+
+ return false;
+}
+
+void
+DSSIPluginInstance::run(const RealTime &blockTime)
+{
+ static snd_seq_event_t localEventBuffer[EVENT_BUFFER_SIZE];
+ int evCount = 0;
+
+ bool needLock = false;
+ if (m_descriptor->select_program)
+ needLock = true;
+
+ if (needLock) {
+ if (pthread_mutex_trylock(&m_processLock) != 0) {
+ for (size_t ch = 0; ch < m_audioPortsOut.size(); ++ch) {
+ memset(m_outputBuffers[ch], 0, m_blockSize * sizeof(sample_t));
+ }
+ return ;
+ }
+ }
+
+ if (m_grouped) {
+ runGrouped(blockTime);
+ goto done;
+ }
+
+ if (!m_descriptor || !m_descriptor->run_synth) {
+ m_eventBuffer.skip(m_eventBuffer.getReadSpace());
+ if (m_descriptor->LADSPA_Plugin->run) {
+ m_descriptor->LADSPA_Plugin->run(m_instanceHandle, m_blockSize);
+ } else {
+ for (size_t ch = 0; ch < m_audioPortsOut.size(); ++ch) {
+ memset(m_outputBuffers[ch], 0, m_blockSize * sizeof(sample_t));
+ }
+ }
+ m_run = true;
+ m_runSinceReset = true;
+ if (needLock)
+ pthread_mutex_unlock(&m_processLock);
+ return ;
+ }
+
+#ifdef DEBUG_DSSI_PROCESS
+ std::cerr << "DSSIPluginInstance::run(" << blockTime << ")" << std::endl;
+#endif
+
+#ifdef DEBUG_DSSI_PROCESS
+
+ if (m_eventBuffer.getReadSpace() > 0) {
+ std::cerr << "DSSIPluginInstance::run: event buffer has "
+ << m_eventBuffer.getReadSpace() << " event(s) in it" << std::endl;
+ }
+#endif
+
+ while (m_eventBuffer.getReadSpace() > 0) {
+
+ snd_seq_event_t *ev = localEventBuffer + evCount;
+ *ev = m_eventBuffer.peek();
+ bool accept = true;
+
+ RealTime evTime(ev->time.time.tv_sec, ev->time.time.tv_nsec);
+
+ int frameOffset = 0;
+ if (evTime > blockTime) {
+ frameOffset = RealTime::realTime2Frame(evTime - blockTime, m_sampleRate);
+ }
+
+#ifdef DEBUG_DSSI_PROCESS
+ std::cerr << "DSSIPluginInstance::run: evTime " << evTime << ", blockTime " << blockTime << ", frameOffset " << frameOffset
+ << ", blockSize " << m_blockSize << std::endl;
+ std::cerr << "Type: " << int(ev->type) << ", pitch: " << int(ev->data.note.note) << ", velocity: " << int(ev->data.note.velocity) << std::endl;
+#endif
+
+ if (frameOffset >= int(m_blockSize))
+ break;
+ if (frameOffset < 0)
+ frameOffset = 0;
+
+ ev->time.tick = frameOffset;
+ m_eventBuffer.skip(1);
+
+ if (ev->type == SND_SEQ_EVENT_CONTROLLER) {
+ accept = handleController(ev);
+ } else if (ev->type == SND_SEQ_EVENT_PGMCHANGE) {
+ m_pending.program = ev->data.control.value;
+ accept = false;
+ }
+
+ if (accept) {
+ if (++evCount >= EVENT_BUFFER_SIZE)
+ break;
+ }
+ }
+
+ if (m_pending.program >= 0 && m_descriptor->select_program) {
+
+ int program = m_pending.program;
+ int bank = m_pending.lsb + 128 * m_pending.msb;
+
+#ifdef DEBUG_DSSI
+
+ std::cerr << "DSSIPluginInstance::run: making select_program(" << bank << "," << program << " call" << std::endl;
+#endif
+
+ m_pending.lsb = m_pending.msb = m_pending.program = -1;
+ m_descriptor->select_program(m_instanceHandle, bank, program);
+
+#ifdef DEBUG_DSSI
+
+ std::cerr << "DSSIPluginInstance::run: made select_program(" << bank << "," << program << " call" << std::endl;
+#endif
+
+ }
+
+#ifdef DEBUG_DSSI_PROCESS
+ std::cerr << "DSSIPluginInstance::run: running with " << evCount << " events"
+ << std::endl;
+#endif
+
+ m_descriptor->run_synth(m_instanceHandle, m_blockSize,
+ localEventBuffer, evCount);
+
+#ifdef DEBUG_DSSI_PROCESS
+ // for (int i = 0; i < m_blockSize; ++i) {
+ // std::cout << m_outputBuffers[0][i] << " ";
+ // if (i % 8 == 0) std::cout << std::endl;
+ // }
+#endif
+
+done:
+ if (needLock)
+ pthread_mutex_unlock(&m_processLock);
+
+ if (m_audioPortsOut.size() == 0) {
+ // copy inputs to outputs
+ for (size_t ch = 0; ch < m_idealChannelCount; ++ch) {
+ size_t sch = ch % m_audioPortsIn.size();
+ for (size_t i = 0; i < m_blockSize; ++i) {
+ m_outputBuffers[ch][i] = m_inputBuffers[sch][i];
+ }
+ }
+ } else if (m_idealChannelCount < m_audioPortsOut.size()) {
+ if (m_idealChannelCount == 1) {
+ // mix down to mono
+ for (size_t ch = 1; ch < m_audioPortsOut.size(); ++ch) {
+ for (size_t i = 0; i < m_blockSize; ++i) {
+ m_outputBuffers[0][i] += m_outputBuffers[ch][i];
+ }
+ }
+ }
+ } else if (m_idealChannelCount > m_audioPortsOut.size()) {
+ // duplicate
+ for (size_t ch = m_audioPortsOut.size(); ch < m_idealChannelCount; ++ch) {
+ size_t sch = (ch - m_audioPortsOut.size()) % m_audioPortsOut.size();
+ for (size_t i = 0; i < m_blockSize; ++i) {
+ m_outputBuffers[ch][i] = m_outputBuffers[sch][i];
+ }
+ }
+ }
+
+ m_lastRunTime = blockTime;
+ m_run = true;
+ m_runSinceReset = true;
+}
+
+void
+DSSIPluginInstance::runGrouped(const RealTime &blockTime)
+{
+ // If something else in our group has just been called for this
+ // block time (but we haven't) then we should just write out the
+ // results and return; if we have just been called for this block
+ // time or nothing else in the group has been, we should run the
+ // whole group.
+
+ bool needRun = true;
+
+ PluginSet &s = m_groupMap[m_identifier];
+
+#ifdef DEBUG_DSSI_PROCESS
+
+ std::cerr << "DSSIPluginInstance::runGrouped(" << blockTime << "): this is " << this << "; " << s.size() << " elements in m_groupMap[" << m_identifier << "]" << std::endl;
+#endif
+
+ if (m_lastRunTime != blockTime) {
+ for (PluginSet::iterator i = s.begin(); i != s.end(); ++i) {
+ DSSIPluginInstance *instance = *i;
+ if (instance != this && instance->m_lastRunTime == blockTime) {
+#ifdef DEBUG_DSSI_PROCESS
+ std::cerr << "DSSIPluginInstance::runGrouped(" << blockTime << "): plugin " << instance << " has already been run" << std::endl;
+#endif
+
+ needRun = false;
+ }
+ }
+ }
+
+ if (!needRun) {
+#ifdef DEBUG_DSSI_PROCESS
+ std::cerr << "DSSIPluginInstance::runGrouped(" << blockTime << "): already run, returning" << std::endl;
+#endif
+
+ return ;
+ }
+
+#ifdef DEBUG_DSSI_PROCESS
+ std::cerr << "DSSIPluginInstance::runGrouped(" << blockTime << "): I'm the first, running" << std::endl;
+#endif
+
+ size_t index = 0;
+ unsigned long *counts = (unsigned long *)
+ alloca(m_groupLocalEventBufferCount * sizeof(unsigned long));
+ LADSPA_Handle *instances = (LADSPA_Handle *)
+ alloca(m_groupLocalEventBufferCount * sizeof(LADSPA_Handle));
+
+ for (PluginSet::iterator i = s.begin(); i != s.end(); ++i) {
+
+ if (index >= m_groupLocalEventBufferCount)
+ break;
+
+ DSSIPluginInstance *instance = *i;
+ counts[index] = 0;
+ instances[index] = instance->m_instanceHandle;
+
+#ifdef DEBUG_DSSI_PROCESS
+
+ std::cerr << "DSSIPluginInstance::runGrouped(" << blockTime << "): running " << instance << std::endl;
+#endif
+
+ if (instance->m_pending.program >= 0 &&
+ instance->m_descriptor->select_program) {
+ int program = instance->m_pending.program;
+ int bank = instance->m_pending.lsb + 128 * instance->m_pending.msb;
+ instance->m_pending.lsb = instance->m_pending.msb = instance->m_pending.program = -1;
+ instance->m_descriptor->select_program
+ (instance->m_instanceHandle, bank, program);
+ }
+
+ while (instance->m_eventBuffer.getReadSpace() > 0) {
+
+ snd_seq_event_t *ev = m_groupLocalEventBuffers[index] + counts[index];
+ *ev = instance->m_eventBuffer.peek();
+ bool accept = true;
+
+ RealTime evTime(ev->time.time.tv_sec, ev->time.time.tv_nsec);
+
+ int frameOffset = 0;
+ if (evTime > blockTime) {
+ frameOffset = RealTime::realTime2Frame(evTime - blockTime, m_sampleRate);
+ }
+
+#ifdef DEBUG_DSSI_PROCESS
+ std::cerr << "DSSIPluginInstance::runGrouped: evTime " << evTime << ", frameOffset " << frameOffset
+ << ", block size " << m_blockSize << std::endl;
+#endif
+
+ if (frameOffset >= int(m_blockSize))
+ break;
+ if (frameOffset < 0)
+ frameOffset = 0;
+
+ ev->time.tick = frameOffset;
+ instance->m_eventBuffer.skip(1);
+
+ if (ev->type == SND_SEQ_EVENT_CONTROLLER) {
+ accept = instance->handleController(ev);
+ } else if (ev->type == SND_SEQ_EVENT_PGMCHANGE) {
+ instance->m_pending.program = ev->data.control.value;
+ accept = false;
+ }
+
+ if (accept) {
+ if (++counts[index] >= EVENT_BUFFER_SIZE)
+ break;
+ }
+ }
+
+ ++index;
+ }
+
+ m_descriptor->run_multiple_synths(index,
+ instances,
+ m_blockSize,
+ m_groupLocalEventBuffers,
+ counts);
+}
+
+
+void
+DSSIPluginInstance::deactivate()
+{
+#ifdef DEBUG_DSSI
+ std::cerr << "DSSIPluginInstance::deactivate " << m_identifier << std::endl;
+#endif
+
+ if (!m_descriptor || !m_descriptor->LADSPA_Plugin->deactivate)
+ return ;
+
+ for (size_t i = 0; i < m_backupControlPortsIn.size(); ++i) {
+ m_backupControlPortsIn[i] = *m_controlPortsIn[i].second;
+ }
+
+ m_descriptor->LADSPA_Plugin->deactivate(m_instanceHandle);
+#ifdef DEBUG_DSSI
+
+ std::cerr << "DSSIPluginInstance::deactivate " << m_identifier << " done" << std::endl;
+#endif
+
+ m_bufferScavenger.scavenge();
+}
+
+void
+DSSIPluginInstance::cleanup()
+{
+#ifdef DEBUG_DSSI
+ std::cerr << "DSSIPluginInstance::cleanup " << m_identifier << std::endl;
+#endif
+
+ if (!m_descriptor)
+ return ;
+
+ if (!m_descriptor->LADSPA_Plugin->cleanup) {
+ std::cerr << "Bad plugin: plugin id "
+ << m_descriptor->LADSPA_Plugin->UniqueID
+ << ":" << m_descriptor->LADSPA_Plugin->Label
+ << " has no cleanup method!" << std::endl;
+ return ;
+ }
+
+ m_descriptor->LADSPA_Plugin->cleanup(m_instanceHandle);
+ m_instanceHandle = 0;
+#ifdef DEBUG_DSSI
+
+ std::cerr << "DSSIPluginInstance::cleanup " << m_identifier << " done" << std::endl;
+#endif
+}
+
+
+
+}
+
+#endif // HAVE_DSSI
+
+
diff --git a/src/sound/DSSIPluginInstance.h b/src/sound/DSSIPluginInstance.h
new file mode 100644
index 0000000..eca6327
--- /dev/null
+++ b/src/sound/DSSIPluginInstance.h
@@ -0,0 +1,193 @@
+// -*- c-basic-offset: 4 -*-
+
+/*
+ Rosegarden
+ A sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <bownie@bownie.com>
+
+ 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 _DSSIPLUGININSTANCE_H_
+#define _DSSIPLUGININSTANCE_H_
+
+#include <vector>
+#include <set>
+#include <map>
+#include <qstring.h>
+#include "Instrument.h"
+
+#ifdef HAVE_DSSI
+
+#include <dssi.h>
+#include "RingBuffer.h"
+#include "RunnablePluginInstance.h"
+#include "Scavenger.h"
+#include <pthread.h>
+
+namespace Rosegarden
+{
+
+class DSSIPluginInstance : public RunnablePluginInstance
+{
+public:
+ virtual ~DSSIPluginInstance();
+
+ virtual bool isOK() const { return m_instanceHandle != 0; }
+
+ InstrumentId getInstrument() const { return m_instrument; }
+ virtual QString getIdentifier() const { return m_identifier; }
+ int getPosition() const { return m_position; }
+
+ virtual void run(const RealTime &);
+
+ virtual void setPortValue(unsigned int portNumber, float value);
+ virtual float getPortValue(unsigned int portNumber);
+ virtual QString configure(QString key, QString value);
+ virtual void sendEvent(const RealTime &eventTime,
+ const void *event);
+
+ virtual size_t getBufferSize() { return m_blockSize; }
+ virtual size_t getAudioInputCount() { return m_audioPortsIn.size(); }
+ virtual size_t getAudioOutputCount() { return m_idealChannelCount; }
+ virtual sample_t **getAudioInputBuffers() { return m_inputBuffers; }
+ virtual sample_t **getAudioOutputBuffers() { return m_outputBuffers; }
+
+ virtual QStringList getPrograms();
+ virtual QString getCurrentProgram();
+ virtual QString getProgram(int bank, int program);
+ virtual unsigned long getProgram(QString name);
+ virtual void selectProgram(QString program);
+
+ virtual bool isBypassed() const { return m_bypassed; }
+ virtual void setBypassed(bool bypassed) { m_bypassed = bypassed; }
+
+ virtual size_t getLatency();
+
+ virtual void silence();
+ virtual void discardEvents();
+ virtual void setIdealChannelCount(size_t channels); // may re-instantiate
+
+ virtual bool isInGroup() const { return m_grouped; }
+ virtual void detachFromGroup();
+
+protected:
+ // To be constructed only by DSSIPluginFactory
+ friend class DSSIPluginFactory;
+
+ // Constructor that creates the buffers internally
+ //
+ DSSIPluginInstance(PluginFactory *factory,
+ InstrumentId instrument,
+ QString identifier,
+ int position,
+ unsigned long sampleRate,
+ size_t blockSize,
+ int idealChannelCount,
+ const DSSI_Descriptor* descriptor);
+
+ // Constructor that uses shared buffers
+ //
+ DSSIPluginInstance(PluginFactory *factory,
+ InstrumentId instrument,
+ QString identifier,
+ int position,
+ unsigned long sampleRate,
+ size_t blockSize,
+ sample_t **inputBuffers,
+ sample_t **outputBuffers,
+ const DSSI_Descriptor* descriptor);
+
+ void init();
+ void instantiate(unsigned long sampleRate);
+ void cleanup();
+ void activate();
+ void deactivate();
+ void connectPorts();
+
+ bool handleController(snd_seq_event_t *ev);
+ void setPortValueFromController(unsigned int portNumber, int controlValue);
+ void selectProgramAux(QString program, bool backupPortValues);
+ void checkProgramCache();
+
+ void initialiseGroupMembership();
+ void runGrouped(const RealTime &);
+
+ InstrumentId m_instrument;
+ int m_position;
+ LADSPA_Handle m_instanceHandle;
+ const DSSI_Descriptor *m_descriptor;
+
+ std::vector<std::pair<unsigned long, LADSPA_Data*> > m_controlPortsIn;
+ std::vector<std::pair<unsigned long, LADSPA_Data*> > m_controlPortsOut;
+
+ std::vector<LADSPA_Data> m_backupControlPortsIn;
+ std::vector<bool> m_portChangedSinceProgramChange;
+
+ std::map<int, int> m_controllerMap;
+
+ std::vector<int> m_audioPortsIn;
+ std::vector<int> m_audioPortsOut;
+
+ struct ProgramControl {
+ int msb;
+ int lsb;
+ int program;
+ };
+ ProgramControl m_pending;
+
+ struct ProgramDescriptor {
+ int bank;
+ int program;
+ QString name;
+ };
+ std::vector<ProgramDescriptor> m_cachedPrograms;
+ bool m_programCacheValid;
+
+ RingBuffer<snd_seq_event_t> m_eventBuffer;
+
+ size_t m_blockSize;
+ sample_t **m_inputBuffers;
+ sample_t **m_outputBuffers;
+ bool m_ownBuffers;
+ size_t m_idealChannelCount;
+ size_t m_outputBufferCount;
+ size_t m_sampleRate;
+ float *m_latencyPort;
+
+ bool m_run;
+ bool m_runSinceReset;
+
+ bool m_bypassed;
+ QString m_program;
+ bool m_grouped;
+ RealTime m_lastRunTime;
+
+ pthread_mutex_t m_processLock;
+
+ typedef std::set<DSSIPluginInstance *> PluginSet;
+ typedef std::map<QString, PluginSet> GroupMap;
+ static GroupMap m_groupMap;
+ static snd_seq_event_t **m_groupLocalEventBuffers;
+ static size_t m_groupLocalEventBufferCount;
+
+ static Scavenger<ScavengerArrayWrapper<snd_seq_event_t *> > m_bufferScavenger;
+};
+
+};
+
+#endif // HAVE_DSSI
+
+#endif // _DSSIPLUGININSTANCE_H_
+
diff --git a/src/sound/DummyDriver.h b/src/sound/DummyDriver.h
new file mode 100644
index 0000000..838e7bd
--- /dev/null
+++ b/src/sound/DummyDriver.h
@@ -0,0 +1,166 @@
+// -*- c-indentation-style:"stroustrup" c-basic-offset: 4 -*-
+/*
+ Rosegarden
+ A sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <bownie@bownie.com>
+
+ 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 "SoundDriver.h"
+
+// An empty sound driver for when we don't want sound support
+// but still want to build the sequencer.
+//
+
+#ifndef _DUMMYDRIVER_H_
+#define _DUMMYDRIVER_H_
+
+namespace Rosegarden
+{
+
+class DummyDriver : public SoundDriver
+{
+public:
+ DummyDriver(MappedStudio *studio):
+ SoundDriver(studio, std::string("DummyDriver - no sound")) { }
+ DummyDriver(MappedStudio *studio, const std::string & name):
+ SoundDriver(studio, std::string("DummyDriver: " + name)) { }
+ virtual ~DummyDriver() { }
+
+ virtual bool initialise() { m_recordComposition.clear(); return true; }
+ virtual void initialisePlayback(const RealTime & /*position*/) { }
+ virtual void stopPlayback() { }
+ virtual void punchOut() { }
+ virtual void resetPlayback(const RealTime & /*old position*/,
+ const RealTime & /*position*/) { }
+ virtual void allNotesOff() { }
+
+ virtual RealTime getSequencerTime() { return RealTime(0, 0);}
+
+ virtual MappedComposition* getMappedComposition()
+ { return &m_recordComposition;}
+
+ virtual void processEventsOut(const MappedComposition & /*mC*/) { }
+
+ virtual void processEventsOut(const MappedComposition &,
+ const RealTime &,
+ const RealTime &) { }
+
+ // Activate a recording state
+ //
+ virtual bool record(RecordStatus /*recordStatus*/,
+ const std::vector<InstrumentId> */*armedInstruments = 0*/,
+ const std::vector<QString> */*audioFileNames = 0*/)
+ { return false; }
+
+ // Process anything that's pending
+ //
+ virtual void processPending() { }
+
+ // Sample rate
+ //
+ virtual unsigned int getSampleRate() const { return 0; }
+
+ // Return the last recorded audio level
+ //
+ virtual float getLastRecordedAudioLevel() { return 0.0; }
+
+ // Plugin instance management
+ //
+ virtual void setPluginInstance(InstrumentId /*id*/,
+ QString /*pluginIdent*/,
+ int /*position*/) { }
+
+ virtual void removePluginInstance(InstrumentId /*id*/,
+ int /*position*/) { }
+
+ virtual void removePluginInstances() { }
+
+ virtual void setPluginInstancePortValue(InstrumentId /*id*/,
+ int /*position*/,
+ unsigned long /*portNumber*/,
+ float /*value*/) { }
+
+ virtual float getPluginInstancePortValue(InstrumentId ,
+ int ,
+ unsigned long ) { return 0; }
+
+ virtual void setPluginInstanceBypass(InstrumentId /*id*/,
+ int /*position*/,
+ bool /*value*/) { }
+
+ virtual QStringList getPluginInstancePrograms(InstrumentId ,
+ int ) { return QStringList(); }
+
+ virtual QString getPluginInstanceProgram(InstrumentId,
+ int ) { return QString(); }
+
+ virtual QString getPluginInstanceProgram(InstrumentId,
+ int,
+ int,
+ int) { return QString(); }
+
+ virtual unsigned long getPluginInstanceProgram(InstrumentId,
+ int ,
+ QString) { return 0; }
+
+ virtual void setPluginInstanceProgram(InstrumentId,
+ int ,
+ QString ) { }
+
+ virtual QString configurePlugin(InstrumentId,
+ int,
+ QString ,
+ QString ) { return QString(); }
+
+ virtual void setAudioBussLevels(int ,
+ float ,
+ float ) { }
+
+ virtual void setAudioInstrumentLevels(InstrumentId,
+ float,
+ float) { }
+
+ virtual bool checkForNewClients() { return false; }
+
+ virtual void setLoop(const RealTime &/*loopStart*/,
+ const RealTime &/*loopEnd*/) { }
+
+ virtual std::vector<PlayableAudioFile*> getPlayingAudioFiles()
+ { return std::vector<PlayableAudioFile*>(); }
+
+ virtual void getAudioInstrumentNumbers(InstrumentId &i, int &n) {
+ i = 0; n = 0;
+ }
+ virtual void getSoftSynthInstrumentNumbers(InstrumentId &i, int &n) {
+ i = 0; n = 0;
+ }
+
+ virtual void claimUnwantedPlugin(void *plugin) { }
+ virtual void scavengePlugins() { }
+
+ virtual bool areClocksRunning() const { return true; }
+
+protected:
+ virtual void processMidiOut(const MappedComposition & /*mC*/,
+ const RealTime &, const RealTime &) { }
+ virtual void generateInstruments() { }
+
+};
+
+}
+
+#endif // _DUMMYDRIVER_H_
+
diff --git a/src/sound/ExternalTransport.h b/src/sound/ExternalTransport.h
new file mode 100644
index 0000000..f40a5a2
--- /dev/null
+++ b/src/sound/ExternalTransport.h
@@ -0,0 +1,67 @@
+// -*- c-basic-offset: 4 -*-
+/*
+ Rosegarden
+ A sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <bownie@bownie.com>
+
+ 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 _EXTERNAL_TRANSPORT_H_
+#define _EXTERNAL_TRANSPORT_H_
+
+namespace Rosegarden {
+
+/**
+ * Simple interface that we can pass to low-level audio code and on
+ * which it can call back when something external requests a transport
+ * change. The callback is asynchronous, and there's a method for the
+ * low-level code to use to find out whether its request has finished
+ * synchronising yet.
+ *
+ * (Each of the transportXX functions returns a token which can then
+ * be passed to isTransportSyncComplete.)
+ */
+
+class ExternalTransport
+{
+public:
+ typedef unsigned long TransportToken;
+
+ enum TransportRequest {
+ TransportNoChange,
+ TransportStop,
+ TransportStart,
+ TransportPlay,
+ TransportRecord,
+ TransportJumpToTime, // time arg required
+ TransportStartAtTime, // time arg required
+ TransportStopAtTime // time arg required
+ };
+
+ virtual TransportToken transportChange(TransportRequest) = 0;
+ virtual TransportToken transportJump(TransportRequest, RealTime) = 0;
+
+ virtual bool isTransportSyncComplete(TransportToken token) = 0;
+
+ // The value returned here is a constant (within the context of a
+ // particular ExternalTransport object) that is guaranteed never
+ // to be returned by any of the transport request methods.
+ virtual TransportToken getInvalidTransportToken() const = 0;
+};
+
+}
+
+#endif
+
diff --git a/src/sound/JackDriver.cpp b/src/sound/JackDriver.cpp
new file mode 100644
index 0000000..24eb6fe
--- /dev/null
+++ b/src/sound/JackDriver.cpp
@@ -0,0 +1,2480 @@
+
+// -*- c-basic-offset: 4 -*-
+
+/*
+ Rosegarden
+ A sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <bownie@bownie.com>
+
+ 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 "JackDriver.h"
+#include "AlsaDriver.h"
+#include "MappedStudio.h"
+#include "AudioProcess.h"
+#include "Profiler.h"
+#include "AudioLevel.h"
+#include "Audit.h"
+#include "PluginFactory.h"
+
+#ifdef HAVE_ALSA
+#ifdef HAVE_LIBJACK
+
+//#define DEBUG_JACK_DRIVER 1
+//#define DEBUG_JACK_TRANSPORT 1
+//#define DEBUG_JACK_PROCESS 1
+//#define DEBUG_JACK_XRUN 1
+
+namespace Rosegarden
+{
+
+#if (defined(DEBUG_JACK_DRIVER) || defined(DEBUG_JACK_PROCESS) || defined(DEBUG_JACK_TRANSPORT))
+static unsigned long framesThisPlay = 0;
+static RealTime startTime;
+#endif
+
+JackDriver::JackDriver(AlsaDriver *alsaDriver) :
+ m_client(0),
+ m_bufferSize(0),
+ m_sampleRate(0),
+ m_tempOutBuffer(0),
+ m_jackTransportEnabled(false),
+ m_jackTransportMaster(false),
+ m_waiting(false),
+ m_waitingState(JackTransportStopped),
+ m_waitingToken(0),
+ m_ignoreProcessTransportCount(0),
+ m_bussMixer(0),
+ m_instrumentMixer(0),
+ m_fileReader(0),
+ m_fileWriter(0),
+ m_alsaDriver(alsaDriver),
+ m_masterLevel(1.0),
+ m_directMasterAudioInstruments(0L),
+ m_directMasterSynthInstruments(0L),
+ m_haveAsyncAudioEvent(false),
+ m_kickedOutAt(0),
+ m_framesProcessed(0),
+ m_ok(false)
+{
+ assert(sizeof(sample_t) == sizeof(float));
+ initialise();
+}
+
+JackDriver::~JackDriver()
+{
+#ifdef DEBUG_JACK_DRIVER
+ std::cerr << "JackDriver::~JackDriver" << std::endl;
+#endif
+
+ m_ok = false; // prevent any more work in process()
+
+ if (m_client) {
+#ifdef DEBUG_JACK_DRIVER
+ std::cerr << "JackDriver::shutdown - deactivating JACK client"
+ << std::endl;
+#endif
+
+ if (jack_deactivate(m_client)) {
+ std::cerr << "JackDriver::shutdown - deactivation failed"
+ << std::endl;
+ }
+ }
+
+#ifdef DEBUG_JACK_DRIVER
+ std::cerr << "JackDriver::~JackDriver: terminating buss mixer" << std::endl;
+#endif
+
+ AudioBussMixer *bussMixer = m_bussMixer;
+ m_bussMixer = 0;
+ if (bussMixer)
+ bussMixer->terminate();
+
+#ifdef DEBUG_JACK_DRIVER
+ std::cerr << "JackDriver::~JackDriver: terminating instrument mixer" << std::endl;
+#endif
+
+ AudioInstrumentMixer *instrumentMixer = m_instrumentMixer;
+ m_instrumentMixer = 0;
+ if (instrumentMixer) {
+ instrumentMixer->terminate();
+ instrumentMixer->destroyAllPlugins();
+ }
+
+#ifdef DEBUG_JACK_DRIVER
+ std::cerr << "JackDriver::~JackDriver: terminating file reader" << std::endl;
+#endif
+
+ AudioFileReader *reader = m_fileReader;
+ m_fileReader = 0;
+ if (reader)
+ reader->terminate();
+
+#ifdef DEBUG_JACK_DRIVER
+ std::cerr << "JackDriver::~JackDriver: terminating file writer" << std::endl;
+#endif
+
+ AudioFileWriter *writer = m_fileWriter;
+ m_fileWriter = 0;
+ if (writer)
+ writer->terminate();
+
+ if (m_client) {
+
+#ifdef DEBUG_JACK_DRIVER
+ std::cerr << "JackDriver::shutdown - tearing down JACK client"
+ << std::endl;
+#endif
+
+ for (unsigned int i = 0; i < m_inputPorts.size(); ++i) {
+#ifdef DEBUG_JACK_DRIVER
+ std::cerr << "unregistering input " << i << std::endl;
+#endif
+
+ if (jack_port_unregister(m_client, m_inputPorts[i])) {
+ std::cerr << "JackDriver::shutdown - "
+ << "can't unregister input port " << i + 1
+ << std::endl;
+ }
+ }
+
+ for (unsigned int i = 0; i < m_outputSubmasters.size(); ++i) {
+#ifdef DEBUG_JACK_DRIVER
+ std::cerr << "unregistering output sub " << i << std::endl;
+#endif
+
+ if (jack_port_unregister(m_client, m_outputSubmasters[i])) {
+ std::cerr << "JackDriver::shutdown - "
+ << "can't unregister output submaster " << i + 1 << std::endl;
+ }
+ }
+
+ for (unsigned int i = 0; i < m_outputMonitors.size(); ++i) {
+#ifdef DEBUG_JACK_DRIVER
+ std::cerr << "unregistering output mon " << i << std::endl;
+#endif
+
+ if (jack_port_unregister(m_client, m_outputMonitors[i])) {
+ std::cerr << "JackDriver::shutdown - "
+ << "can't unregister output monitor " << i + 1 << std::endl;
+ }
+ }
+
+ for (unsigned int i = 0; i < m_outputMasters.size(); ++i) {
+#ifdef DEBUG_JACK_DRIVER
+ std::cerr << "unregistering output master " << i << std::endl;
+#endif
+
+ if (jack_port_unregister(m_client, m_outputMasters[i])) {
+ std::cerr << "JackDriver::shutdown - "
+ << "can't unregister output master " << i + 1 << std::endl;
+ }
+ }
+
+#ifdef DEBUG_JACK_DRIVER
+ std::cerr << "closing client" << std::endl;
+#endif
+
+ jack_client_close(m_client);
+ std::cerr << "done" << std::endl;
+ m_client = 0;
+ }
+
+#ifdef DEBUG_JACK_DRIVER
+ std::cerr << "JackDriver: deleting mixers etc" << std::endl;
+#endif
+
+ delete bussMixer;
+ delete instrumentMixer;
+ delete reader;
+ delete writer;
+
+#ifdef DEBUG_JACK_DRIVER
+
+ std::cerr << "JackDriver::~JackDriver exiting" << std::endl;
+#endif
+}
+
+void
+JackDriver::initialise(bool reinitialise)
+{
+ m_ok = false;
+
+ Audit audit;
+ audit << std::endl;
+
+ std::string jackClientName = "rosegarden";
+
+ // attempt connection to JACK server
+ //
+ if ((m_client = jack_client_new(jackClientName.c_str())) == 0) {
+ audit << "JackDriver::initialiseAudio - "
+ << "JACK server not running"
+ << std::endl;
+ return ;
+ }
+
+ InstrumentId instrumentBase;
+ int instrumentCount;
+ m_alsaDriver->getAudioInstrumentNumbers(instrumentBase, instrumentCount);
+ for (InstrumentId id = instrumentBase;
+ id < instrumentBase + instrumentCount; ++id) {
+ // prefill so that we can refer to the map without a lock (as
+ // the number of instruments won't change)
+ m_recordInputs[id] = RecordInputDesc(1000, -1, 0.0);
+ }
+
+ // set callbacks
+ //
+ jack_set_process_callback(m_client, jackProcessStatic, this);
+ jack_set_buffer_size_callback(m_client, jackBufferSize, this);
+ jack_set_sample_rate_callback(m_client, jackSampleRate, this);
+ jack_on_shutdown(m_client, jackShutdown, this);
+ jack_set_xrun_callback(m_client, jackXRun, this);
+ jack_set_sync_callback(m_client, jackSyncCallback, this);
+
+ // get and report the sample rate and buffer size
+ //
+ m_sampleRate = jack_get_sample_rate(m_client);
+ m_bufferSize = jack_get_buffer_size(m_client);
+
+ audit << "JackDriver::initialiseAudio - JACK sample rate = "
+ << m_sampleRate << "Hz, buffer size = " << m_bufferSize
+ << std::endl;
+
+ PluginFactory::setSampleRate(m_sampleRate);
+
+ // Get the initial buffer size before we activate the client
+ //
+
+ if (!reinitialise) {
+
+ // create processing buffer(s)
+ //
+ m_tempOutBuffer = new sample_t[m_bufferSize];
+
+ audit << "JackDriver::initialiseAudio - "
+ << "creating disk thread" << std::endl;
+
+ m_fileReader = new AudioFileReader(m_alsaDriver, m_sampleRate);
+ m_fileWriter = new AudioFileWriter(m_alsaDriver, m_sampleRate);
+ m_instrumentMixer = new AudioInstrumentMixer
+ (m_alsaDriver, m_fileReader, m_sampleRate, m_bufferSize);
+ m_bussMixer = new AudioBussMixer
+ (m_alsaDriver, m_instrumentMixer, m_sampleRate, m_bufferSize);
+ m_instrumentMixer->setBussMixer(m_bussMixer);
+
+ // We run the file reader whatever, but we only run the other
+ // threads (instrument mixer, buss mixer, file writer) when we
+ // actually need them. (See updateAudioData and createRecordFile.)
+ m_fileReader->run();
+ }
+
+ // Create and connect the default numbers of ports. We always create
+ // one stereo pair each of master and monitor outs, and then we create
+ // record ins, fader outs and submaster outs according to the user's
+ // preferences. Since we don't know the user's preferences yet, we'll
+ // start by creating one pair of record ins and no fader or submaster
+ // outs.
+ //
+ m_outputMasters.clear();
+ m_outputMonitors.clear();
+ m_outputSubmasters.clear();
+ m_outputInstruments.clear();
+ m_inputPorts.clear();
+
+ if (!createMainOutputs()) { // one stereo pair master, one pair monitor
+ audit << "JackDriver::initialise - "
+ << "failed to create main outputs!" << std::endl;
+ return ;
+ }
+
+ if (!createRecordInputs(1)) {
+ audit << "JackDriver::initialise - "
+ << "failed to create record inputs!" << std::endl;
+ return ;
+ }
+
+ if (jack_activate(m_client)) {
+ audit << "JackDriver::initialise - "
+ << "client activation failed" << std::endl;
+ return ;
+ }
+
+ // Now set up the default connections.
+
+ std::string playback_1, playback_2;
+
+ const char **ports =
+ jack_get_ports(m_client, NULL, NULL,
+ JackPortIsPhysical | JackPortIsInput);
+
+ if (ports) {
+ if (ports[0])
+ playback_1 = std::string(ports[0]);
+ if (ports[1])
+ playback_2 = std::string(ports[1]);
+
+ // count ports
+ unsigned int i = 0;
+ for (i = 0; ports[i]; i++)
+ ;
+ audit << "JackDriver::initialiseAudio - "
+ << "found " << i << " JACK physical outputs"
+ << std::endl;
+ } else
+ audit << "JackDriver::initialiseAudio - "
+ << "no JACK physical outputs found"
+ << std::endl;
+ free(ports);
+
+ if (playback_1 != "") {
+ audit << "JackDriver::initialiseAudio - "
+ << "connecting from "
+ << "\"" << jack_port_name(m_outputMasters[0])
+ << "\" to \"" << playback_1.c_str() << "\""
+ << std::endl;
+
+ // connect our client up to the ALSA ports - first left output
+ //
+ if (jack_connect(m_client, jack_port_name(m_outputMasters[0]),
+ playback_1.c_str())) {
+ audit << "JackDriver::initialiseAudio - "
+ << "cannot connect to JACK output port" << std::endl;
+ return ;
+ }
+
+ /*
+ if (jack_connect(m_client, jack_port_name(m_outputMonitors[0]),
+ playback_1.c_str()))
+ {
+ audit << "JackDriver::initialiseAudio - "
+ << "cannot connect to JACK output port" << std::endl;
+ return;
+ }
+ */
+ }
+
+ if (playback_2 != "") {
+ audit << "JackDriver::initialiseAudio - "
+ << "connecting from "
+ << "\"" << jack_port_name(m_outputMasters[1])
+ << "\" to \"" << playback_2.c_str() << "\""
+ << std::endl;
+
+ if (jack_connect(m_client, jack_port_name(m_outputMasters[1]),
+ playback_2.c_str())) {
+ audit << "JackDriver::initialiseAudio - "
+ << "cannot connect to JACK output port" << std::endl;
+ }
+
+ /*
+ if (jack_connect(m_client, jack_port_name(m_outputMonitors[1]),
+ playback_2.c_str()))
+ {
+ audit << "JackDriver::initialiseAudio - "
+ << "cannot connect to JACK output port" << std::endl;
+ }
+ */
+ }
+
+
+ std::string capture_1, capture_2;
+
+ ports =
+ jack_get_ports(m_client, NULL, NULL,
+ JackPortIsPhysical | JackPortIsOutput);
+
+ if (ports) {
+ if (ports[0])
+ capture_1 = std::string(ports[0]);
+ if (ports[1])
+ capture_2 = std::string(ports[1]);
+
+ // count ports
+ unsigned int i = 0;
+ for (i = 0; ports[i]; i++)
+ ;
+ audit << "JackDriver::initialiseAudio - "
+ << "found " << i << " JACK physical inputs"
+ << std::endl;
+ } else
+ audit << "JackDriver::initialiseAudio - "
+ << "no JACK physical inputs found"
+ << std::endl;
+ free(ports);
+
+ if (capture_1 != "") {
+
+ audit << "JackDriver::initialiseAudio - "
+ << "connecting from "
+ << "\"" << capture_1.c_str()
+ << "\" to \"" << jack_port_name(m_inputPorts[0]) << "\""
+ << std::endl;
+
+ if (jack_connect(m_client, capture_1.c_str(),
+ jack_port_name(m_inputPorts[0]))) {
+ audit << "JackDriver::initialiseAudio - "
+ << "cannot connect to JACK input port" << std::endl;
+ }
+ }
+
+ if (capture_2 != "") {
+
+ audit << "JackDriver::initialiseAudio - "
+ << "connecting from "
+ << "\"" << capture_2.c_str()
+ << "\" to \"" << jack_port_name(m_inputPorts[1]) << "\""
+ << std::endl;
+
+ if (jack_connect(m_client, capture_2.c_str(),
+ jack_port_name(m_inputPorts[1]))) {
+ audit << "JackDriver::initialiseAudio - "
+ << "cannot connect to JACK input port" << std::endl;
+ }
+ }
+
+ audit << "JackDriver::initialiseAudio - "
+ << "initialised JACK audio subsystem"
+ << std::endl;
+
+ m_ok = true;
+}
+
+bool
+JackDriver::createMainOutputs()
+{
+ if (!m_client)
+ return false;
+
+ jack_port_t *port = jack_port_register
+ (m_client, "master out L",
+ JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput, 0);
+ if (!port)
+ return false;
+ m_outputMasters.push_back(port);
+
+ port = jack_port_register
+ (m_client, "master out R",
+ JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput, 0);
+ if (!port)
+ return false;
+ m_outputMasters.push_back(port);
+
+ port = jack_port_register
+ (m_client, "record monitor out L",
+ JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput, 0);
+ if (!port)
+ return false;
+ m_outputMonitors.push_back(port);
+
+ port = jack_port_register
+ (m_client, "record monitor out R",
+ JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput, 0);
+ if (!port)
+ return false;
+ m_outputMonitors.push_back(port);
+
+ return true;
+}
+
+bool
+JackDriver::createFaderOutputs(int audioPairs, int synthPairs)
+{
+ if (!m_client)
+ return false;
+
+ int pairs = audioPairs + synthPairs;
+ int pairsNow = m_outputInstruments.size() / 2;
+ if (pairs == pairsNow)
+ return true;
+
+ for (int i = pairsNow; i < pairs; ++i) {
+
+ char namebuffer[22];
+ jack_port_t *port;
+
+ if (i < audioPairs) {
+ snprintf(namebuffer, 21, "audio fader %d out L", i + 1);
+ } else {
+ snprintf(namebuffer, 21, "synth fader %d out L", i - audioPairs + 1);
+ }
+
+ port = jack_port_register(m_client,
+ namebuffer,
+ JACK_DEFAULT_AUDIO_TYPE,
+ JackPortIsOutput,
+ 0);
+ if (!port)
+ return false;
+ m_outputInstruments.push_back(port);
+
+ if (i < audioPairs) {
+ snprintf(namebuffer, 21, "audio fader %d out R", i + 1);
+ } else {
+ snprintf(namebuffer, 21, "synth fader %d out R", i - audioPairs + 1);
+ }
+
+ port = jack_port_register(m_client,
+ namebuffer,
+ JACK_DEFAULT_AUDIO_TYPE,
+ JackPortIsOutput,
+ 0);
+ if (!port)
+ return false;
+ m_outputInstruments.push_back(port);
+ }
+
+ while ((int)m_outputInstruments.size() > pairs * 2) {
+ std::vector<jack_port_t *>::iterator itr = m_outputInstruments.end();
+ --itr;
+ jack_port_unregister(m_client, *itr);
+ m_outputInstruments.erase(itr);
+ }
+
+ return true;
+}
+
+bool
+JackDriver::createSubmasterOutputs(int pairs)
+{
+ if (!m_client)
+ return false;
+
+ int pairsNow = m_outputSubmasters.size() / 2;
+ if (pairs == pairsNow)
+ return true;
+
+ for (int i = pairsNow; i < pairs; ++i) {
+
+ char namebuffer[22];
+ jack_port_t *port;
+
+ snprintf(namebuffer, 21, "submaster %d out L", i + 1);
+ port = jack_port_register(m_client,
+ namebuffer,
+ JACK_DEFAULT_AUDIO_TYPE,
+ JackPortIsOutput,
+ 0);
+ if (!port)
+ return false;
+ m_outputSubmasters.push_back(port);
+
+ snprintf(namebuffer, 21, "submaster %d out R", i + 1);
+ port = jack_port_register(m_client,
+ namebuffer,
+ JACK_DEFAULT_AUDIO_TYPE,
+ JackPortIsOutput,
+ 0);
+ if (!port)
+ return false;
+ m_outputSubmasters.push_back(port);
+ }
+
+ while ((int)m_outputSubmasters.size() > pairs * 2) {
+ std::vector<jack_port_t *>::iterator itr = m_outputSubmasters.end();
+ --itr;
+ jack_port_unregister(m_client, *itr);
+ m_outputSubmasters.erase(itr);
+ }
+
+ return true;
+}
+
+bool
+JackDriver::createRecordInputs(int pairs)
+{
+ if (!m_client)
+ return false;
+
+ int pairsNow = m_inputPorts.size() / 2;
+ if (pairs == pairsNow)
+ return true;
+
+ for (int i = pairsNow; i < pairs; ++i) {
+
+ char namebuffer[22];
+ jack_port_t *port;
+
+ snprintf(namebuffer, 21, "record in %d L", i + 1);
+ port = jack_port_register(m_client,
+ namebuffer,
+ JACK_DEFAULT_AUDIO_TYPE,
+ JackPortIsInput,
+ 0);
+ if (!port)
+ return false;
+ m_inputPorts.push_back(port);
+
+ snprintf(namebuffer, 21, "record in %d R", i + 1);
+ port = jack_port_register(m_client,
+ namebuffer,
+ JACK_DEFAULT_AUDIO_TYPE,
+ JackPortIsInput,
+ 0);
+ if (!port)
+ return false;
+ m_inputPorts.push_back(port);
+ }
+
+ while ((int)m_outputSubmasters.size() > pairs * 2) {
+ std::vector<jack_port_t *>::iterator itr = m_outputSubmasters.end();
+ --itr;
+ jack_port_unregister(m_client, *itr);
+ m_outputSubmasters.erase(itr);
+ }
+
+ return true;
+}
+
+
+void
+JackDriver::setAudioPorts(bool faderOuts, bool submasterOuts)
+{
+ if (!m_client)
+ return ;
+
+ Audit audit;
+#ifdef DEBUG_JACK_DRIVER
+
+ std::cerr << "JackDriver::setAudioPorts(" << faderOuts << "," << submasterOuts << ")" << std::endl;
+#endif
+
+ if (!m_client) {
+ std::cerr << "JackDriver::setAudioPorts(" << faderOuts << "," << submasterOuts << "): no client yet" << std::endl;
+ return ;
+ }
+
+ if (faderOuts) {
+ InstrumentId instrumentBase;
+ int audioInstruments;
+ int synthInstruments;
+ m_alsaDriver->getAudioInstrumentNumbers(instrumentBase, audioInstruments);
+ m_alsaDriver->getSoftSynthInstrumentNumbers(instrumentBase, synthInstruments);
+ if (!createFaderOutputs(audioInstruments, synthInstruments)) {
+ m_ok = false;
+ audit << "Failed to create fader outs!" << std::endl;
+ return ;
+ }
+ } else {
+ createFaderOutputs(0, 0);
+ }
+
+ if (submasterOuts) {
+
+ // one fewer than returned here, because the master has a buss object too
+ if (!createSubmasterOutputs
+ (m_alsaDriver->getMappedStudio()->getObjectCount
+ (MappedObject::AudioBuss) - 1)) {
+ m_ok = false;
+ audit << "Failed to create submaster outs!" << std::endl;
+ return ;
+ }
+
+ } else {
+ createSubmasterOutputs(0);
+ }
+}
+
+RealTime
+JackDriver::getAudioPlayLatency() const
+{
+ if (!m_client)
+ return RealTime::zeroTime;
+
+ jack_nframes_t latency =
+ jack_port_get_total_latency(m_client, m_outputMasters[0]);
+
+ return RealTime::frame2RealTime(latency, m_sampleRate);
+}
+
+RealTime
+JackDriver::getAudioRecordLatency() const
+{
+ if (!m_client)
+ return RealTime::zeroTime;
+
+ jack_nframes_t latency =
+ jack_port_get_total_latency(m_client, m_inputPorts[0]);
+
+ return RealTime::frame2RealTime(latency, m_sampleRate);
+}
+
+RealTime
+JackDriver::getInstrumentPlayLatency(InstrumentId id) const
+{
+ if (m_instrumentLatencies.find(id) == m_instrumentLatencies.end()) {
+ return RealTime::zeroTime;
+ } else {
+ return m_instrumentLatencies.find(id)->second;
+ }
+}
+
+RealTime
+JackDriver::getMaximumPlayLatency() const
+{
+ return m_maxInstrumentLatency;
+}
+
+int
+JackDriver::jackProcessStatic(jack_nframes_t nframes, void *arg)
+{
+ JackDriver *inst = static_cast<JackDriver*>(arg);
+ if (inst)
+ return inst->jackProcess(nframes);
+ else
+ return 0;
+}
+
+int
+JackDriver::jackProcess(jack_nframes_t nframes)
+{
+ if (!m_ok || !m_client) {
+#ifdef DEBUG_JACK_PROCESS
+ std::cerr << "JackDriver::jackProcess: not OK" << std::endl;
+#endif
+
+ return 0;
+ }
+
+ if (!m_bussMixer) {
+#ifdef DEBUG_JACK_PROCESS
+ std::cerr << "JackDriver::jackProcess: no buss mixer" << std::endl;
+#endif
+
+ return jackProcessEmpty(nframes);
+ }
+
+ if (m_alsaDriver->areClocksRunning()) {
+ m_alsaDriver->checkTimerSync(m_framesProcessed);
+ } else {
+ m_alsaDriver->checkTimerSync(0);
+ }
+
+ bool lowLatencyMode = m_alsaDriver->getLowLatencyMode();
+ bool clocksRunning = m_alsaDriver->areClocksRunning();
+ bool playing = m_alsaDriver->isPlaying();
+ bool asyncAudio = m_haveAsyncAudioEvent;
+
+#ifdef DEBUG_JACK_PROCESS
+
+ Profiler profiler("jackProcess", true);
+#else
+#ifdef DEBUG_JACK_XRUN
+
+ Profiler profiler("jackProcess", false);
+#endif
+#endif
+
+ if (lowLatencyMode) {
+ if (clocksRunning) {
+ if (playing || asyncAudio) {
+
+ if (m_instrumentMixer->tryLock() == 0) {
+ m_instrumentMixer->kick(false);
+ m_instrumentMixer->releaseLock();
+ //#ifdef DEBUG_JACK_PROCESS
+ } else {
+ std::cerr << "JackDriver::jackProcess: no instrument mixer lock available" << std::endl;
+ //#endif
+ }
+ if (m_bussMixer->getBussCount() > 0) {
+ if (m_bussMixer->tryLock() == 0) {
+ m_bussMixer->kick(false, false);
+ m_bussMixer->releaseLock();
+ //#ifdef DEBUG_JACK_PROCESS
+ } else {
+ std::cerr << "JackDriver::jackProcess: no buss mixer lock available" << std::endl;
+ //#endif
+ }
+ }
+ }
+ }
+ }
+
+ if (jack_cpu_load(m_client) > 97.0) {
+ reportFailure(MappedEvent::FailureCPUOverload);
+ return jackProcessEmpty(nframes);
+ }
+
+#ifdef DEBUG_JACK_PROCESS
+ Profiler profiler2("jackProcess post mix", true);
+#else
+#ifdef DEBUG_JACK_XRUN
+
+ Profiler profiler2("jackProcess post mix", false);
+#endif
+#endif
+
+ SequencerDataBlock *sdb = m_alsaDriver->getSequencerDataBlock();
+
+ jack_position_t position;
+ jack_transport_state_t state = JackTransportRolling;
+ bool doneRecord = false;
+
+ int ignoreCount = m_ignoreProcessTransportCount;
+ if (ignoreCount > 0)
+ --m_ignoreProcessTransportCount;
+
+ InstrumentId audioInstrumentBase;
+ int audioInstruments;
+ m_alsaDriver->getAudioInstrumentNumbers(audioInstrumentBase, audioInstruments);
+
+ if (m_jackTransportEnabled) {
+
+ state = jack_transport_query(m_client, &position);
+
+#ifdef DEBUG_JACK_PROCESS
+
+ std::cerr << "JackDriver::jackProcess: JACK transport state is " << state << std::endl;
+#endif
+
+ if (state == JackTransportStopped) {
+ if (playing && clocksRunning && !m_waiting) {
+ ExternalTransport *transport =
+ m_alsaDriver->getExternalTransportControl();
+ if (transport) {
+#ifdef DEBUG_JACK_TRANSPORT
+ std::cerr << "JackDriver::jackProcess: JACK transport stopped externally at " << position.frame << std::endl;
+#endif
+
+ m_waitingToken =
+ transport->transportJump
+ (ExternalTransport::TransportStopAtTime,
+ RealTime::frame2RealTime(position.frame,
+ position.frame_rate));
+ }
+ } else if (clocksRunning) {
+ if (!asyncAudio) {
+#ifdef DEBUG_JACK_PROCESS
+ std::cerr << "JackDriver::jackProcess: no interesting async events" << std::endl;
+#endif
+ // do this before record monitor, otherwise we lose monitor out
+ jackProcessEmpty(nframes);
+ }
+
+ // for monitoring:
+ int rv = 0;
+ for (InstrumentId id = audioInstrumentBase;
+ id < audioInstrumentBase + audioInstruments; ++id) {
+ int irv = jackProcessRecord(id, nframes, 0, 0, clocksRunning);
+ if (irv != 0)
+ rv = irv;
+ }
+ doneRecord = true;
+
+ if (!asyncAudio) {
+ return rv;
+ }
+
+ } else {
+ return jackProcessEmpty(nframes);
+ }
+ } else if (state == JackTransportStarting) {
+ return jackProcessEmpty(nframes);
+ } else if (state != JackTransportRolling) {
+ std::cerr << "JackDriver::jackProcess: unexpected JACK transport state " << state << std::endl;
+ }
+ }
+
+ if (state == JackTransportRolling) { // also covers not-on-transport case
+ if (m_waiting) {
+ if (ignoreCount > 0) {
+#ifdef DEBUG_JACK_TRANSPORT
+ std::cerr << "JackDriver::jackProcess: transport rolling, but we're ignoring it (count = " << ignoreCount << ")" << std::endl;
+#endif
+
+ } else {
+#ifdef DEBUG_JACK_TRANSPORT
+ std::cerr << "JackDriver::jackProcess: transport rolling, telling ALSA driver to go!" << std::endl;
+#endif
+
+ m_alsaDriver->startClocksApproved();
+ m_waiting = false;
+ }
+ }
+
+#ifdef DEBUG_JACK_PROCESS
+ std::cerr << "JackDriver::jackProcess (rolling or not on JACK transport)" << std::endl;
+#endif
+
+ if (!clocksRunning) {
+#ifdef DEBUG_JACK_PROCESS
+ std::cerr << "JackDriver::jackProcess: clocks stopped" << std::endl;
+#endif
+
+ return jackProcessEmpty(nframes);
+
+ } else if (!playing) {
+#ifdef DEBUG_JACK_PROCESS
+ std::cerr << "JackDriver::jackProcess: not playing" << std::endl;
+#endif
+
+ if (!asyncAudio) {
+#ifdef DEBUG_JACK_PROCESS
+ std::cerr << "JackDriver::jackProcess: no interesting async events" << std::endl;
+#endif
+ // do this before record monitor, otherwise we lose monitor out
+ jackProcessEmpty(nframes);
+ }
+
+ // for monitoring:
+ int rv = 0;
+ for (InstrumentId id = audioInstrumentBase;
+ id < audioInstrumentBase + audioInstruments; ++id) {
+ int irv = jackProcessRecord(id, nframes, 0, 0, clocksRunning);
+ if (irv != 0)
+ rv = irv;
+ }
+ doneRecord = true;
+
+ if (!asyncAudio) {
+ return rv;
+ }
+ }
+ }
+
+#ifdef DEBUG_JACK_PROCESS
+ Profiler profiler3("jackProcess post transport", true);
+#else
+#ifdef DEBUG_JACK_XRUN
+
+ Profiler profiler3("jackProcess post transport", false);
+#endif
+#endif
+
+ InstrumentId synthInstrumentBase;
+ int synthInstruments;
+ m_alsaDriver->getSoftSynthInstrumentNumbers(synthInstrumentBase, synthInstruments);
+
+ // We always have the master out
+
+ sample_t *master[2] = {
+ static_cast<sample_t *>
+ (jack_port_get_buffer(m_outputMasters[0], nframes)),
+ static_cast<sample_t *>
+ (jack_port_get_buffer(m_outputMasters[1], nframes))
+ };
+
+ memset(master[0], 0, nframes * sizeof(sample_t));
+ memset(master[1], 0, nframes * sizeof(sample_t));
+
+ // Reset monitor outs (if present) here prior to mixing
+
+ if (m_outputMonitors.size() > 0) {
+ sample_t *buffer = static_cast<sample_t *>
+ (jack_port_get_buffer(m_outputMonitors[0], nframes));
+ if (buffer)
+ memset(buffer, 0, nframes * sizeof(sample_t));
+ }
+
+ if (m_outputMonitors.size() > 1) {
+ sample_t *buffer = static_cast<sample_t *>
+ (jack_port_get_buffer(m_outputMonitors[1], nframes));
+ if (buffer)
+ memset(buffer, 0, nframes * sizeof(sample_t));
+ }
+
+ int bussCount = m_bussMixer->getBussCount();
+
+ // If we have any busses, then we just mix from them (but we still
+ // need to keep ourselves up to date by reading and monitoring the
+ // instruments). If we have no busses, mix direct from instruments.
+
+ for (int buss = 0; buss < bussCount; ++buss) {
+
+ sample_t *submaster[2] = { 0, 0 };
+ sample_t peak[2] = { 0.0, 0.0 };
+
+ if ((int)m_outputSubmasters.size() > buss * 2 + 1) {
+ submaster[0] = static_cast<sample_t *>
+ (jack_port_get_buffer(m_outputSubmasters[buss * 2], nframes));
+ submaster[1] = static_cast<sample_t *>
+ (jack_port_get_buffer(m_outputSubmasters[buss * 2 + 1], nframes));
+ }
+
+ if (!submaster[0])
+ submaster[0] = m_tempOutBuffer;
+ if (!submaster[1])
+ submaster[1] = m_tempOutBuffer;
+
+ for (int ch = 0; ch < 2; ++ch) {
+
+ RingBuffer<AudioBussMixer::sample_t> *rb =
+ m_bussMixer->getRingBuffer(buss, ch);
+
+ if (!rb || m_bussMixer->isBussDormant(buss)) {
+ if (rb)
+ rb->skip(nframes);
+ if (submaster[ch])
+ memset(submaster[ch], 0, nframes * sizeof(sample_t));
+ } else {
+ size_t actual = rb->read(submaster[ch], nframes);
+ if (actual < nframes) {
+ reportFailure(MappedEvent::FailureBussMixUnderrun);
+ }
+ for (size_t i = 0; i < nframes; ++i) {
+ sample_t sample = submaster[ch][i];
+ if (sample > peak[ch])
+ peak[ch] = sample;
+ master[ch][i] += sample;
+ }
+ }
+ }
+
+ if (sdb) {
+ LevelInfo info;
+ info.level = AudioLevel::multiplier_to_fader
+ (peak[0], 127, AudioLevel::LongFader);
+ info.levelRight = AudioLevel::multiplier_to_fader
+ (peak[1], 127, AudioLevel::LongFader);
+
+ sdb->setSubmasterLevel(buss, info);
+ }
+
+ for (InstrumentId id = audioInstrumentBase;
+ id < audioInstrumentBase + audioInstruments; ++id) {
+ if (buss + 1 == m_recordInputs[id].input) {
+ jackProcessRecord(id, nframes, submaster[0], submaster[1], clocksRunning);
+ }
+ }
+ }
+
+#ifdef DEBUG_JACK_PROCESS
+ std::cerr << "JackDriver::jackProcess: have " << audioInstruments << " audio and " << synthInstruments << " synth instruments and " << bussCount << " busses" << std::endl;
+#endif
+
+ bool allInstrumentsDormant = true;
+ static RealTime dormantTime = RealTime::zeroTime;
+
+ for (int i = 0; i < audioInstruments + synthInstruments; ++i) {
+
+ InstrumentId id;
+ if (i < audioInstruments)
+ id = audioInstrumentBase + i;
+ else
+ id = synthInstrumentBase + (i - audioInstruments);
+
+ if (m_instrumentMixer->isInstrumentEmpty(id))
+ continue;
+
+ sample_t *instrument[2] = { 0, 0 };
+ sample_t peak[2] = { 0.0, 0.0 };
+
+ if (int(m_outputInstruments.size()) > i * 2 + 1) {
+ instrument[0] = static_cast<sample_t *>
+ (jack_port_get_buffer(m_outputInstruments[i * 2], nframes));
+ instrument[1] = static_cast<sample_t *>
+ (jack_port_get_buffer(m_outputInstruments[i * 2 + 1], nframes));
+ }
+
+ if (!instrument[0])
+ instrument[0] = m_tempOutBuffer;
+ if (!instrument[1])
+ instrument[1] = m_tempOutBuffer;
+
+ for (int ch = 0; ch < 2; ++ch) {
+
+ // We always need to read from an instrument's ring buffer
+ // to keep the instrument moving along, as well as for
+ // monitoring. If the instrument is connected straight to
+ // the master, then we also need to mix from it. (We have
+ // that information cached courtesy of updateAudioData.)
+
+ bool directToMaster = false;
+ if (i < audioInstruments) {
+ directToMaster = (m_directMasterAudioInstruments & (1 << i));
+ } else {
+ directToMaster = (m_directMasterSynthInstruments &
+ (1 << (i - audioInstruments)));
+ }
+
+#ifdef DEBUG_JACK_PROCESS
+ if (id == 1000 || id == 10000) {
+ std::cerr << "JackDriver::jackProcess: instrument id " << id << ", base " << audioInstrumentBase << ", direct masters " << m_directMasterAudioInstruments << ": " << directToMaster << std::endl;
+ }
+#endif
+
+ RingBuffer<AudioInstrumentMixer::sample_t, 2> *rb =
+ m_instrumentMixer->getRingBuffer(id, ch);
+
+ if (!rb || m_instrumentMixer->isInstrumentDormant(id)) {
+#ifdef DEBUG_JACK_PROCESS
+ if (id == 1000 || id == 10000) {
+ if (rb) {
+ std::cerr << "JackDriver::jackProcess: instrument " << id << " dormant" << std::endl;
+ } else {
+ std::cerr << "JackDriver::jackProcess: instrument " << id << " has no ring buffer for channel " << ch << std::endl;
+ }
+ }
+#endif
+ if (rb)
+ rb->skip(nframes);
+ if (instrument[ch])
+ memset(instrument[ch], 0, nframes * sizeof(sample_t));
+
+ } else {
+
+ allInstrumentsDormant = false;
+
+ size_t actual = rb->read(instrument[ch], nframes);
+
+#ifdef DEBUG_JACK_PROCESS
+
+ if (id == 1000) {
+ std::cerr << "JackDriver::jackProcess: read " << actual << " of " << nframes << " frames for instrument " << id << " channel " << ch << std::endl;
+ }
+#endif
+
+ if (actual < nframes) {
+
+ std::cerr << "JackDriver::jackProcess: read " << actual << " of " << nframes << " frames for " << id << " ch " << ch << " (pl " << playing << ", cl " << clocksRunning << ", aa " << asyncAudio << ")" << std::endl;
+
+ reportFailure(MappedEvent::FailureMixUnderrun);
+ }
+ for (size_t f = 0; f < nframes; ++f) {
+ sample_t sample = instrument[ch][f];
+ if (sample > peak[ch])
+ peak[ch] = sample;
+ if (directToMaster)
+ master[ch][f] += sample;
+ }
+ }
+
+ // If the instrument is connected straight to master we
+ // also need to skip() on the buss mixer's reader for it,
+ // otherwise it'll block because the buss mixer isn't
+ // needing to read it.
+
+ if (rb && directToMaster) {
+ rb->skip(nframes, 1); // 1 is the buss mixer's reader (magic)
+ }
+ }
+
+ if (sdb) {
+ LevelInfo info;
+ info.level = AudioLevel::multiplier_to_fader
+ (peak[0], 127, AudioLevel::LongFader);
+ info.levelRight = AudioLevel::multiplier_to_fader
+ (peak[1], 127, AudioLevel::LongFader);
+
+ sdb->setInstrumentLevel(id, info);
+ }
+ }
+
+ if (asyncAudio) {
+ if (!allInstrumentsDormant) {
+ dormantTime = RealTime::zeroTime;
+ } else {
+ dormantTime = dormantTime +
+ RealTime::frame2RealTime(m_bufferSize, m_sampleRate);
+ if (dormantTime > RealTime(10, 0)) {
+ std::cerr << "JackDriver: dormantTime = " << dormantTime << ", resetting m_haveAsyncAudioEvent" << std::endl;
+ m_haveAsyncAudioEvent = false;
+ }
+ }
+ }
+
+ // Get master fader levels. There's no pan on the master.
+ float gain = AudioLevel::dB_to_multiplier(m_masterLevel);
+ float masterPeak[2] = { 0.0, 0.0 };
+
+ for (int ch = 0; ch < 2; ++ch) {
+ for (size_t i = 0; i < nframes; ++i) {
+ sample_t sample = master[ch][i] * gain;
+ if (sample > masterPeak[ch])
+ masterPeak[ch] = sample;
+ master[ch][i] = sample;
+ }
+ }
+
+ if (sdb) {
+ LevelInfo info;
+ info.level = AudioLevel::multiplier_to_fader
+ (masterPeak[0], 127, AudioLevel::LongFader);
+ info.levelRight = AudioLevel::multiplier_to_fader
+ (masterPeak[1], 127, AudioLevel::LongFader);
+
+ sdb->setMasterLevel(info);
+ }
+
+ for (InstrumentId id = audioInstrumentBase;
+ id < audioInstrumentBase + audioInstruments; ++id) {
+ if (m_recordInputs[id].input == 0) {
+ jackProcessRecord(id, nframes, master[0], master[1], clocksRunning);
+ } else if (m_recordInputs[id].input < 1000) { // buss, already done
+ // nothing
+ } else if (!doneRecord) {
+ jackProcessRecord(id, nframes, 0, 0, clocksRunning);
+ }
+ }
+
+ if (playing) {
+ if (!lowLatencyMode) {
+ if (m_bussMixer->getBussCount() == 0) {
+ m_instrumentMixer->signal();
+ } else {
+ m_bussMixer->signal();
+ }
+ }
+ }
+
+ m_framesProcessed += nframes;
+
+#if (defined(DEBUG_JACK_DRIVER) || defined(DEBUG_JACK_PROCESS) || defined(DEBUG_JACK_TRANSPORT))
+
+ framesThisPlay += nframes; //!!!
+#endif
+#ifdef DEBUG_JACK_PROCESS
+
+ std::cerr << "JackDriver::jackProcess: " << nframes << " frames, " << framesThisPlay << " this play, " << m_framesProcessed << " total" << std::endl;
+#endif
+
+ return 0;
+}
+
+int
+JackDriver::jackProcessEmpty(jack_nframes_t nframes)
+{
+ sample_t *buffer;
+
+#ifdef DEBUG_JACK_PROCESS
+
+ std::cerr << "JackDriver::jackProcessEmpty" << std::endl;
+#endif
+
+ buffer = static_cast<sample_t *>
+ (jack_port_get_buffer(m_outputMasters[0], nframes));
+ if (buffer)
+ memset(buffer, 0, nframes * sizeof(sample_t));
+
+ buffer = static_cast<sample_t *>
+ (jack_port_get_buffer(m_outputMasters[1], nframes));
+ if (buffer)
+ memset(buffer, 0, nframes * sizeof(sample_t));
+
+ buffer = static_cast<sample_t *>
+ (jack_port_get_buffer(m_outputMonitors[0], nframes));
+ if (buffer)
+ memset(buffer, 0, nframes * sizeof(sample_t));
+
+ buffer = static_cast<sample_t *>
+ (jack_port_get_buffer(m_outputMonitors[1], nframes));
+ if (buffer)
+ memset(buffer, 0, nframes * sizeof(sample_t));
+
+ for (unsigned int i = 0; i < m_outputSubmasters.size(); ++i) {
+ buffer = static_cast<sample_t *>
+ (jack_port_get_buffer(m_outputSubmasters[i], nframes));
+ if (buffer)
+ memset(buffer, 0, nframes * sizeof(sample_t));
+ }
+
+ for (unsigned int i = 0; i < m_outputInstruments.size(); ++i) {
+ buffer = static_cast<sample_t *>
+ (jack_port_get_buffer(m_outputInstruments[i], nframes));
+ if (buffer)
+ memset(buffer, 0, nframes * sizeof(sample_t));
+ }
+
+ m_framesProcessed += nframes;
+
+#if (defined(DEBUG_JACK_DRIVER) || defined(DEBUG_JACK_PROCESS) || defined(DEBUG_JACK_TRANSPORT))
+
+ framesThisPlay += nframes;
+#endif
+#ifdef DEBUG_JACK_PROCESS
+
+ std::cerr << "JackDriver::jackProcess: " << nframes << " frames, " << framesThisPlay << " this play, " << m_framesProcessed << " total" << std::endl;
+#endif
+
+ return 0;
+}
+
+int
+JackDriver::jackProcessRecord(InstrumentId id,
+ jack_nframes_t nframes,
+ sample_t *sourceBufferLeft,
+ sample_t *sourceBufferRight,
+ bool clocksRunning)
+{
+#ifdef DEBUG_JACK_PROCESS
+ Profiler profiler("jackProcessRecord", true);
+#else
+#ifdef DEBUG_JACK_XRUN
+
+ Profiler profiler("jackProcessRecord", false);
+#endif
+#endif
+
+ SequencerDataBlock *sdb = m_alsaDriver->getSequencerDataBlock();
+ bool wroteSomething = false;
+ sample_t peakLeft = 0.0, peakRight = 0.0;
+
+#ifdef DEBUG_JACK_PROCESS
+
+ std::cerr << "JackDriver::jackProcessRecord(" << id << "): clocksRunning " << clocksRunning << std::endl;
+#endif
+
+ // Get input buffers
+ //
+ sample_t *inputBufferLeft = 0, *inputBufferRight = 0;
+
+ int recInput = m_recordInputs[id].input;
+
+ int channel = m_recordInputs[id].channel;
+ int channels = (channel == -1 ? 2 : 1);
+ if (channels == 2)
+ channel = 0;
+
+ float level = m_recordInputs[id].level;
+
+ if (sourceBufferLeft) {
+
+#ifdef DEBUG_JACK_PROCESS
+ std::cerr << "JackDriver::jackProcessRecord(" << id << "): buss input provided" << std::endl;
+#endif
+
+ inputBufferLeft = sourceBufferLeft;
+ if (sourceBufferRight)
+ inputBufferRight = sourceBufferRight;
+
+ } else if (recInput < 1000) {
+
+#ifdef DEBUG_JACK_PROCESS
+ std::cerr << "JackDriver::jackProcessRecord(" << id << "): no known input" << std::endl;
+#endif
+
+ return 0;
+
+ } else {
+
+#ifdef DEBUG_JACK_PROCESS
+ std::cerr << "JackDriver::jackProcessRecord(" << id << "): record input " << recInput << std::endl;
+#endif
+
+ int input = recInput - 1000;
+
+ int port = input * channels + channel;
+ int portRight = input * channels + 1;
+
+ if (port < int(m_inputPorts.size())) {
+ inputBufferLeft = static_cast<sample_t*>
+ (jack_port_get_buffer(m_inputPorts[port], nframes));
+ }
+
+ if (channels == 2 && portRight < int(m_inputPorts.size())) {
+ inputBufferRight = static_cast<sample_t*>
+ (jack_port_get_buffer(m_inputPorts[portRight], nframes));
+ }
+ }
+
+ float gain = AudioLevel::dB_to_multiplier(level);
+
+ if (m_alsaDriver->getRecordStatus() == RECORD_ON &&
+ clocksRunning &&
+ m_fileWriter->haveRecordFileOpen(id)) {
+
+#ifdef DEBUG_JACK_PROCESS
+ std::cerr << "JackDriver::jackProcessRecord(" << id << "): recording" << std::endl;
+#endif
+
+ memset(m_tempOutBuffer, 0, nframes * sizeof(sample_t));
+
+ if (inputBufferLeft) {
+ for (size_t i = 0; i < nframes; ++i) {
+ sample_t sample = inputBufferLeft[i] * gain;
+ if (sample > peakLeft)
+ peakLeft = sample;
+ m_tempOutBuffer[i] = sample;
+ }
+
+ if (m_outputMonitors.size() > 0) {
+ sample_t *buf =
+ static_cast<sample_t *>
+ (jack_port_get_buffer(m_outputMonitors[0], nframes));
+ if (buf) {
+ for (size_t i = 0; i < nframes; ++i) {
+ buf[i] += m_tempOutBuffer[i];
+ }
+ }
+ }
+
+ m_fileWriter->write(id, m_tempOutBuffer, 0, nframes);
+ }
+
+ if (channels == 2) {
+
+ if (inputBufferRight) {
+ for (size_t i = 0; i < nframes; ++i) {
+ sample_t sample = inputBufferRight[i] * gain;
+ if (sample > peakRight)
+ peakRight = sample;
+ m_tempOutBuffer[i] = sample;
+ }
+ if (m_outputMonitors.size() > 1) {
+ sample_t *buf =
+ static_cast<sample_t *>
+ (jack_port_get_buffer(m_outputMonitors[1], nframes));
+ if (buf) {
+ for (size_t i = 0; i < nframes; ++i) {
+ buf[i] += m_tempOutBuffer[i];
+ }
+ }
+ }
+ }
+
+ m_fileWriter->write(id, m_tempOutBuffer, 1, nframes);
+ }
+
+ wroteSomething = true;
+
+ } else {
+
+ // want peak levels and monitors anyway, even if not recording
+
+#ifdef DEBUG_JACK_PROCESS
+ std::cerr << "JackDriver::jackProcessRecord(" << id << "): monitoring only" << std::endl;
+#endif
+
+ if (inputBufferLeft) {
+
+ sample_t *buf = 0;
+ if (m_outputMonitors.size() > 0) {
+ buf = static_cast<sample_t *>
+ (jack_port_get_buffer(m_outputMonitors[0], nframes));
+ }
+
+ for (size_t i = 0; i < nframes; ++i) {
+ sample_t sample = inputBufferLeft[i] * gain;
+ if (sample > peakLeft)
+ peakLeft = sample;
+ if (buf)
+ buf[i] = sample;
+ }
+
+ if (channels == 2 && inputBufferRight) {
+
+ buf = 0;
+ if (m_outputMonitors.size() > 1) {
+ buf = static_cast<sample_t *>
+ (jack_port_get_buffer(m_outputMonitors[1], nframes));
+ }
+
+ for (size_t i = 0; i < nframes; ++i) {
+ sample_t sample = inputBufferRight[i] * gain;
+ if (sample > peakRight)
+ peakRight = sample;
+ if (buf)
+ buf[i] = sample;
+ }
+ }
+ }
+ }
+
+ if (channels < 2)
+ peakRight = peakLeft;
+
+ if (sdb) {
+ LevelInfo info;
+ info.level = AudioLevel::multiplier_to_fader
+ (peakLeft, 127, AudioLevel::LongFader);
+ info.levelRight = AudioLevel::multiplier_to_fader
+ (peakRight, 127, AudioLevel::LongFader);
+ sdb->setInstrumentRecordLevel(id, info);
+ }
+
+ if (wroteSomething) {
+ m_fileWriter->signal();
+ }
+
+ return 0;
+}
+
+
+int
+JackDriver::jackSyncCallback(jack_transport_state_t state,
+ jack_position_t *position,
+ void *arg)
+{
+ JackDriver *inst = (JackDriver *)arg;
+ if (!inst)
+ return true; // or rather, return "huh?"
+
+ inst->m_alsaDriver->checkTimerSync(0); // reset, as not processing
+
+ if (!inst->m_jackTransportEnabled)
+ return true; // ignore
+
+ ExternalTransport *transport =
+ inst->m_alsaDriver->getExternalTransportControl();
+ if (!transport)
+ return true;
+
+#ifdef DEBUG_JACK_TRANSPORT
+
+ std::cerr << "JackDriver::jackSyncCallback: state " << state << " [" << (state == 0 ? "stopped" : state == 1 ? "rolling" : state == 2 ? "looping" : state == 3 ? "starting" : "unknown") << "], frame " << position->frame << ", waiting " << inst->m_waiting << ", playing " << inst->m_alsaDriver->isPlaying() << std::endl;
+
+ std::cerr << "JackDriver::jackSyncCallback: m_waitingState " << inst->m_waitingState << ", unique_1 " << position->unique_1 << ", unique_2 " << position->unique_2 << std::endl;
+
+ std::cerr << "JackDriver::jackSyncCallback: rate " << position->frame_rate << ", bar " << position->bar << ", beat " << position->beat << ", tick " << position->tick << ", bpm " << position->beats_per_minute << std::endl;
+
+#endif
+
+ ExternalTransport::TransportRequest request =
+ ExternalTransport::TransportNoChange;
+
+ if (inst->m_alsaDriver->isPlaying()) {
+
+ if (state == JackTransportStarting) {
+ request = ExternalTransport::TransportJumpToTime;
+ } else if (state == JackTransportStopped) {
+ request = ExternalTransport::TransportStop;
+ }
+
+ } else {
+
+ if (state == JackTransportStarting) {
+ request = ExternalTransport::TransportStartAtTime;
+ } else if (state == JackTransportStopped) {
+ request = ExternalTransport::TransportNoChange;
+ }
+ }
+
+ if (!inst->m_waiting || inst->m_waitingState != state) {
+
+ if (request == ExternalTransport::TransportJumpToTime ||
+ request == ExternalTransport::TransportStartAtTime) {
+
+ RealTime rt = RealTime::frame2RealTime(position->frame,
+ position->frame_rate);
+
+#ifdef DEBUG_JACK_TRANSPORT
+
+ std::cerr << "JackDriver::jackSyncCallback: Requesting jump to " << rt << std::endl;
+#endif
+
+ inst->m_waitingToken = transport->transportJump(request, rt);
+
+#ifdef DEBUG_JACK_TRANSPORT
+
+ std::cerr << "JackDriver::jackSyncCallback: My token is " << inst->m_waitingToken << std::endl;
+#endif
+
+ } else if (request == ExternalTransport::TransportStop) {
+
+#ifdef DEBUG_JACK_TRANSPORT
+ std::cerr << "JackDriver::jackSyncCallback: Requesting state change to " << request << std::endl;
+#endif
+
+ inst->m_waitingToken = transport->transportChange(request);
+
+#ifdef DEBUG_JACK_TRANSPORT
+
+ std::cerr << "JackDriver::jackSyncCallback: My token is " << inst->m_waitingToken << std::endl;
+#endif
+
+ } else if (request == ExternalTransport::TransportNoChange) {
+
+#ifdef DEBUG_JACK_TRANSPORT
+ std::cerr << "JackDriver::jackSyncCallback: Requesting no state change!" << std::endl;
+#endif
+
+ inst->m_waitingToken = transport->transportChange(request);
+
+#ifdef DEBUG_JACK_TRANSPORT
+
+ std::cerr << "JackDriver::jackSyncCallback: My token is " << inst->m_waitingToken << std::endl;
+#endif
+
+ }
+
+ inst->m_waiting = true;
+ inst->m_waitingState = state;
+
+#ifdef DEBUG_JACK_TRANSPORT
+
+ std::cerr << "JackDriver::jackSyncCallback: Setting waiting to " << inst->m_waiting << " and waiting state to " << inst->m_waitingState << " (request was " << request << ")" << std::endl;
+#endif
+
+ return 0;
+
+ } else {
+
+ if (transport->isTransportSyncComplete(inst->m_waitingToken)) {
+#ifdef DEBUG_JACK_TRANSPORT
+ std::cerr << "JackDriver::jackSyncCallback: Sync complete" << std::endl;
+#endif
+
+ return 1;
+ } else {
+#ifdef DEBUG_JACK_TRANSPORT
+ std::cerr << "JackDriver::jackSyncCallback: Sync not complete" << std::endl;
+#endif
+
+ return 0;
+ }
+ }
+}
+
+bool
+JackDriver::relocateTransportInternal(bool alsoStart)
+{
+ if (!m_client)
+ return true;
+
+#ifdef DEBUG_JACK_TRANSPORT
+
+ const char *fn = (alsoStart ?
+ "JackDriver::startTransport" :
+ "JackDriver::relocateTransport");
+#endif
+
+#ifdef DEBUG_JACK_TRANSPORT
+
+ std::cerr << fn << std::endl;
+#else
+#ifdef DEBUG_JACK_DRIVER
+
+ std::cerr << "JackDriver::relocateTransportInternal" << std::endl;
+#endif
+#endif
+
+ // m_waiting is true if we are waiting for the JACK transport
+ // to finish a change of state.
+
+ if (m_jackTransportEnabled) {
+
+ // If on the transport, we never return true here -- instead
+ // the JACK process calls startClocksApproved() to signal to
+ // the ALSA driver that it's time to go. But we do use this
+ // to manage our JACK transport state requests.
+
+ // Where did this request come from? Are we just responding
+ // to an external sync?
+
+ ExternalTransport *transport =
+ m_alsaDriver->getExternalTransportControl();
+
+ if (transport) {
+ if (transport->isTransportSyncComplete(m_waitingToken)) {
+
+ // Nope, this came from Rosegarden
+
+#ifdef DEBUG_JACK_TRANSPORT
+ std::cerr << fn << ": asking JACK transport to start, setting wait state" << std::endl;
+#endif
+
+ m_waiting = true;
+ m_waitingState = JackTransportStarting;
+
+ long frame = RealTime::realTime2Frame
+ (m_alsaDriver->getSequencerTime(), m_sampleRate);
+
+ if (frame < 0) {
+ // JACK Transport doesn't support preroll and
+ // can't set transport position to before zero
+ // (frame count is unsigned), so there's no very
+ // satisfactory fix for what to do for count-in
+ // bars. Let's just start at zero instead.
+ jack_transport_locate(m_client, 0);
+ } else {
+ jack_transport_locate(m_client, frame);
+ }
+
+ if (alsoStart) {
+ jack_transport_start(m_client);
+ m_ignoreProcessTransportCount = 1;
+ } else {
+ m_ignoreProcessTransportCount = 2;
+ }
+ } else {
+#ifdef DEBUG_JACK_TRANSPORT
+ std::cerr << fn << ": waiting already" << std::endl;
+#endif
+
+ }
+ }
+ return false;
+ }
+
+#if (defined(DEBUG_JACK_DRIVER) || defined(DEBUG_JACK_PROCESS) || defined(DEBUG_JACK_TRANSPORT))
+ framesThisPlay = 0; //!!!
+ struct timeval tv;
+ (void)gettimeofday(&tv, 0);
+ startTime = RealTime(tv.tv_sec, tv.tv_usec * 1000); //!!!
+#endif
+#ifdef DEBUG_JACK_TRANSPORT
+
+ std::cerr << fn << ": not on JACK transport, accepting right away" << std::endl;
+#endif
+
+ return true;
+}
+
+bool
+JackDriver::startTransport()
+{
+ return relocateTransportInternal(true);
+}
+
+bool
+JackDriver::relocateTransport()
+{
+
+ return relocateTransportInternal(false);
+}
+
+void
+JackDriver::stopTransport()
+{
+ if (!m_client)
+ return ;
+
+ std::cerr << "JackDriver::stopTransport: resetting m_haveAsyncAudioEvent" << std::endl;
+ m_haveAsyncAudioEvent = false;
+
+#ifdef DEBUG_JACK_TRANSPORT
+
+ struct timeval tv;
+ (void)gettimeofday(&tv, 0);
+ RealTime endTime = RealTime(tv.tv_sec, tv.tv_usec * 1000); //!!!
+ std::cerr << "\nJackDriver::stop: frames this play: " << framesThisPlay << ", elapsed " << (endTime - startTime) << std::endl;
+#endif
+
+ if (m_jackTransportEnabled) {
+
+ // Where did this request come from? Is this a result of our
+ // sync to a transport that has in fact already stopped?
+
+ ExternalTransport *transport =
+ m_alsaDriver->getExternalTransportControl();
+
+ if (transport) {
+ if (transport->isTransportSyncComplete(m_waitingToken)) {
+
+ // No, we have no outstanding external requests; this
+ // must have genuinely been requested from within
+ // Rosegarden, so:
+
+#ifdef DEBUG_JACK_TRANSPORT
+ std::cerr << "JackDriver::stop: internal request, asking JACK transport to stop" << std::endl;
+#endif
+
+ jack_transport_stop(m_client);
+
+ } else {
+ // Nothing to do
+
+#ifdef DEBUG_JACK_TRANSPORT
+ std::cerr << "JackDriver::stop: external request, JACK transport is already stopped" << std::endl;
+#endif
+
+ }
+ }
+ }
+
+ if (m_instrumentMixer)
+ m_instrumentMixer->resetAllPlugins(true); // discard events too
+}
+
+
+// Pick up any change of buffer size
+//
+int
+JackDriver::jackBufferSize(jack_nframes_t nframes, void *arg)
+{
+ JackDriver *inst = static_cast<JackDriver*>(arg);
+
+#ifdef DEBUG_JACK_DRIVER
+
+ std::cerr << "JackDriver::jackBufferSize - buffer size changed to "
+ << nframes << std::endl;
+#endif
+
+ inst->m_bufferSize = nframes;
+
+ // Recreate our temporary mix buffers to the new size
+ //
+ //!!! need buffer size change callbacks on plugins (so long as they
+ // have internal buffers) and the mix manager, with locks acquired
+ // appropriately
+
+ delete [] inst->m_tempOutBuffer;
+ inst->m_tempOutBuffer = new sample_t[inst->m_bufferSize];
+
+ return 0;
+}
+
+// Sample rate change
+//
+int
+JackDriver::jackSampleRate(jack_nframes_t nframes, void *arg)
+{
+ JackDriver *inst = static_cast<JackDriver*>(arg);
+
+#ifdef DEBUG_JACK_DRIVER
+
+ std::cerr << "JackDriver::jackSampleRate - sample rate changed to "
+ << nframes << std::endl;
+#endif
+
+ inst->m_sampleRate = nframes;
+
+ return 0;
+}
+
+void
+JackDriver::jackShutdown(void *arg)
+{
+#ifdef DEBUG_JACK_DRIVER
+ std::cerr << "JackDriver::jackShutdown() - callback received - "
+ << "informing GUI" << std::endl;
+#endif
+
+#ifdef DEBUG_JACK_XRUN
+
+ std::cerr << "JackDriver::jackShutdown" << std::endl;
+ Profiles::getInstance()->dump();
+#endif
+
+ JackDriver *inst = static_cast<JackDriver*>(arg);
+ inst->m_ok = false;
+ inst->m_kickedOutAt = time(0);
+ inst->reportFailure(MappedEvent::FailureJackDied);
+}
+
+int
+JackDriver::jackXRun(void *arg)
+{
+#ifdef DEBUG_JACK_DRIVER
+ std::cerr << "JackDriver::jackXRun" << std::endl;
+#endif
+
+#ifdef DEBUG_JACK_XRUN
+
+ std::cerr << "JackDriver::jackXRun" << std::endl;
+ Profiles::getInstance()->dump();
+#endif
+
+ // Report to GUI
+ //
+ JackDriver *inst = static_cast<JackDriver*>(arg);
+ inst->reportFailure(MappedEvent::FailureXRuns);
+
+ return 0;
+}
+
+
+void
+
+JackDriver::restoreIfRestorable()
+{
+ if (m_kickedOutAt == 0)
+ return ;
+
+ if (m_client) {
+ jack_client_close(m_client);
+ std::cerr << "closed client" << std::endl;
+ m_client = 0;
+ }
+
+ time_t now = time(0);
+
+ if (now < m_kickedOutAt || now >= m_kickedOutAt + 3) {
+
+ if (m_instrumentMixer)
+ m_instrumentMixer->resetAllPlugins(true);
+ std::cerr << "reset plugins" << std::endl;
+
+ initialise(true);
+
+ if (m_ok) {
+ reportFailure(MappedEvent::FailureJackRestart);
+ } else {
+ reportFailure(MappedEvent::FailureJackRestartFailed);
+ }
+
+ m_kickedOutAt = 0;
+ }
+}
+
+void
+JackDriver::prepareAudio()
+{
+ if (!m_instrumentMixer)
+ return ;
+
+ // This is used when restarting clocks after repositioning, but
+ // when not actually playing (yet). We need to do things like
+ // regenerating the processing buffers here. prebufferAudio()
+ // also does all of this, but rather more besides.
+
+ m_instrumentMixer->allocateBuffers();
+ m_instrumentMixer->resetAllPlugins(false);
+}
+
+void
+JackDriver::prebufferAudio()
+{
+ if (!m_instrumentMixer)
+ return ;
+
+ // We want this to happen when repositioning during playback, and
+ // stopTransport no longer happens then, so we call it from here.
+ // NB. Don't want to discard events here as this is called after
+ // pushing events to the soft synth queues at startup
+ m_instrumentMixer->resetAllPlugins(false);
+
+#ifdef DEBUG_JACK_DRIVER
+
+ std::cerr << "JackDriver::prebufferAudio: sequencer time is "
+ << m_alsaDriver->getSequencerTime() << std::endl;
+#endif
+
+ RealTime sliceStart = getNextSliceStart(m_alsaDriver->getSequencerTime());
+
+ m_fileReader->fillBuffers(sliceStart);
+
+ if (m_bussMixer->getBussCount() > 0) {
+ m_bussMixer->fillBuffers(sliceStart); // also calls on m_instrumentMixer
+ } else {
+ m_instrumentMixer->fillBuffers(sliceStart);
+ }
+}
+
+void
+JackDriver::kickAudio()
+{
+#ifdef DEBUG_JACK_PROCESS
+ std::cerr << "JackDriver::kickAudio" << std::endl;
+#endif
+
+ if (m_fileReader)
+ m_fileReader->kick();
+ if (m_instrumentMixer)
+ m_instrumentMixer->kick();
+ if (m_bussMixer)
+ m_bussMixer->kick();
+ if (m_fileWriter)
+ m_fileWriter->kick();
+}
+
+void
+JackDriver::updateAudioData()
+{
+ if (!m_ok || !m_client)
+ return ;
+
+#ifdef DEBUG_JACK_DRIVER
+ // std::cerr << "JackDriver::updateAudioData starting" << std::endl;
+#endif
+
+ MappedAudioBuss *mbuss =
+ m_alsaDriver->getMappedStudio()->getAudioBuss(0);
+
+ if (mbuss) {
+ float level = 0.0;
+ (void)mbuss->getProperty(MappedAudioBuss::Level, level);
+ m_masterLevel = level;
+ }
+
+ unsigned long directMasterAudioInstruments = 0L;
+ unsigned long directMasterSynthInstruments = 0L;
+
+ InstrumentId audioInstrumentBase;
+ int audioInstruments;
+ m_alsaDriver->getAudioInstrumentNumbers(audioInstrumentBase, audioInstruments);
+
+ InstrumentId synthInstrumentBase;
+ int synthInstruments;
+ m_alsaDriver->getSoftSynthInstrumentNumbers(synthInstrumentBase, synthInstruments);
+
+ RealTime jackLatency = getAudioPlayLatency();
+ RealTime maxLatency = RealTime::zeroTime;
+
+ for (int i = 0; i < audioInstruments + synthInstruments; ++i) {
+
+ InstrumentId id;
+ if (i < audioInstruments)
+ id = audioInstrumentBase + i;
+ else
+ id = synthInstrumentBase + (i - audioInstruments);
+
+ MappedAudioFader *fader = m_alsaDriver->getMappedStudio()->getAudioFader(id);
+ if (!fader)
+ continue;
+
+ float f = 2;
+ (void)fader->getProperty(MappedAudioFader::Channels, f);
+ int channels = (int)f;
+
+ int inputChannel = -1;
+ if (channels == 1) {
+ float f = 0;
+ (void)fader->getProperty(MappedAudioFader::InputChannel, f);
+ inputChannel = (int)f;
+ }
+
+ float level = 0.0;
+ (void)fader->getProperty(MappedAudioFader::FaderRecordLevel, level);
+
+ // Like in base/Instrument.h, we use numbers < 1000 to
+ // mean buss numbers and >= 1000 to mean record ins
+ // when recording the record input number.
+
+ MappedObjectValueList connections = fader->getConnections
+ (MappedConnectableObject::In);
+ int input = 1000;
+
+ if (connections.empty()) {
+
+ std::cerr << "No connections in for record instrument "
+ << (id) << " (mapped id " << fader->getId() << ")" << std::endl;
+
+ // oh dear.
+ input = 1000;
+
+ } else if (*connections.begin() == mbuss->getId()) {
+
+ input = 0;
+
+ } else {
+
+ MappedObject *obj = m_alsaDriver->getMappedStudio()->
+ getObjectById(MappedObjectId(*connections.begin()));
+
+ if (!obj) {
+
+ std::cerr << "No such object as " << *connections.begin() << std::endl;
+ input = 1000;
+ } else if (obj->getType() == MappedObject::AudioBuss) {
+ input = (int)((MappedAudioBuss *)obj)->getBussId();
+ } else if (obj->getType() == MappedObject::AudioInput) {
+ input = (int)((MappedAudioInput *)obj)->getInputNumber()
+ + 1000;
+ } else {
+ std::cerr << "Object " << *connections.begin() << " is not buss or input" << std::endl;
+ input = 1000;
+ }
+ }
+
+ if (m_recordInputs[id].input != input) {
+ std::cerr << "Changing record input for instrument "
+ << id << " to " << input << std::endl;
+ }
+ m_recordInputs[id] = RecordInputDesc(input, inputChannel, level);
+
+ size_t pluginLatency = 0;
+ bool empty = m_instrumentMixer->isInstrumentEmpty(id);
+
+ if (!empty) {
+ pluginLatency = m_instrumentMixer->getPluginLatency(id);
+ }
+
+ // If we find the object is connected to no output, or to buss
+ // number 0 (the master), then we set the bit appropriately.
+
+ connections = fader->getConnections(MappedConnectableObject::Out);
+
+ if (connections.empty() || (*connections.begin() == mbuss->getId())) {
+ if (i < audioInstruments) {
+ directMasterAudioInstruments |= (1 << i);
+ } else {
+ directMasterSynthInstruments |= (1 << (i - audioInstruments));
+ }
+ } else if (!empty) {
+ pluginLatency +=
+ m_instrumentMixer->getPluginLatency((unsigned int) * connections.begin());
+ }
+
+ if (empty) {
+ m_instrumentLatencies[id] = RealTime::zeroTime;
+ } else {
+ m_instrumentLatencies[id] = jackLatency +
+ RealTime::frame2RealTime(pluginLatency, m_sampleRate);
+ if (m_instrumentLatencies[id] > maxLatency) {
+ maxLatency = m_instrumentLatencies[id];
+ }
+ }
+ }
+
+ m_maxInstrumentLatency = maxLatency;
+ m_directMasterAudioInstruments = directMasterAudioInstruments;
+ m_directMasterSynthInstruments = directMasterSynthInstruments;
+ m_maxInstrumentLatency = maxLatency;
+
+ int inputs = m_alsaDriver->getMappedStudio()->
+ getObjectCount(MappedObject::AudioInput);
+
+ if (m_client) {
+ // this will return with no work if the inputs are already correct:
+ createRecordInputs(inputs);
+ }
+
+ m_bussMixer->updateInstrumentConnections();
+ m_instrumentMixer->updateInstrumentMuteStates();
+
+ if (m_bussMixer->getBussCount() == 0 || m_alsaDriver->getLowLatencyMode()) {
+ if (m_bussMixer->running()) {
+ m_bussMixer->terminate();
+ }
+ } else {
+ if (!m_bussMixer->running()) {
+ m_bussMixer->run();
+ }
+ }
+
+ if (m_alsaDriver->getLowLatencyMode()) {
+ if (m_instrumentMixer->running()) {
+ m_instrumentMixer->terminate();
+ }
+ } else {
+ if (!m_instrumentMixer->running()) {
+ m_instrumentMixer->run();
+ }
+ }
+
+#ifdef DEBUG_JACK_DRIVER
+ // std::cerr << "JackDriver::updateAudioData exiting" << std::endl;
+#endif
+}
+
+void
+JackDriver::setAudioBussLevels(int bussNo, float dB, float pan)
+{
+ if (m_bussMixer) {
+ m_bussMixer->setBussLevels(bussNo, dB, pan);
+ }
+}
+
+void
+JackDriver::setAudioInstrumentLevels(InstrumentId instrument, float dB, float pan)
+{
+ if (m_instrumentMixer) {
+ m_instrumentMixer->setInstrumentLevels(instrument, dB, pan);
+ }
+}
+
+RealTime
+JackDriver::getNextSliceStart(const RealTime &now) const
+{
+ jack_nframes_t frame;
+ bool neg = false;
+
+ if (now < RealTime::zeroTime) {
+ neg = true;
+ frame = RealTime::realTime2Frame(RealTime::zeroTime - now, m_sampleRate);
+ } else {
+ frame = RealTime::realTime2Frame(now, m_sampleRate);
+ }
+
+ jack_nframes_t rounded = frame;
+ rounded /= m_bufferSize;
+ rounded *= m_bufferSize;
+
+ RealTime roundrt;
+
+ if (rounded == frame)
+ roundrt = RealTime::frame2RealTime(rounded, m_sampleRate);
+ else if (neg)
+ roundrt = RealTime::frame2RealTime(rounded - m_bufferSize, m_sampleRate);
+ else
+ roundrt = RealTime::frame2RealTime(rounded + m_bufferSize, m_sampleRate);
+
+ if (neg)
+ roundrt = RealTime::zeroTime - roundrt;
+
+ return roundrt;
+}
+
+
+int
+JackDriver::getAudioQueueLocks()
+{
+ // We have to lock the mixers first, because the mixers can try to
+ // lock the disk manager from within a locked section -- so if we
+ // locked the disk manager first we would risk deadlock when
+ // trying to acquire the instrument mixer lock
+
+ int rv = 0;
+ if (m_bussMixer) {
+#ifdef DEBUG_JACK_DRIVER
+ std::cerr << "JackDriver::getAudioQueueLocks: trying to lock buss mixer" << std::endl;
+#endif
+
+ rv = m_bussMixer->getLock();
+ if (rv)
+ return rv;
+ }
+ if (m_instrumentMixer) {
+#ifdef DEBUG_JACK_DRIVER
+ std::cerr << "JackDriver::getAudioQueueLocks: ok, now trying for instrument mixer" << std::endl;
+#endif
+
+ rv = m_instrumentMixer->getLock();
+ if (rv)
+ return rv;
+ }
+ if (m_fileReader) {
+#ifdef DEBUG_JACK_DRIVER
+ std::cerr << "JackDriver::getAudioQueueLocks: ok, now trying for disk reader" << std::endl;
+#endif
+
+ rv = m_fileReader->getLock();
+ if (rv)
+ return rv;
+ }
+ if (m_fileWriter) {
+#ifdef DEBUG_JACK_DRIVER
+ std::cerr << "JackDriver::getAudioQueueLocks: ok, now trying for disk writer" << std::endl;
+#endif
+
+ rv = m_fileWriter->getLock();
+ }
+#ifdef DEBUG_JACK_DRIVER
+ std::cerr << "JackDriver::getAudioQueueLocks: ok" << std::endl;
+#endif
+
+ return rv;
+}
+
+int
+JackDriver::tryAudioQueueLocks()
+{
+ int rv = 0;
+ if (m_bussMixer) {
+ rv = m_bussMixer->tryLock();
+ if (rv)
+ return rv;
+ }
+ if (m_instrumentMixer) {
+ rv = m_instrumentMixer->tryLock();
+ if (rv) {
+ if (m_bussMixer) {
+ m_bussMixer->releaseLock();
+ }
+ }
+ }
+ if (m_fileReader) {
+ rv = m_fileReader->tryLock();
+ if (rv) {
+ if (m_instrumentMixer) {
+ m_instrumentMixer->releaseLock();
+ }
+ if (m_bussMixer) {
+ m_bussMixer->releaseLock();
+ }
+ }
+ }
+ if (m_fileWriter) {
+ rv = m_fileWriter->tryLock();
+ if (rv) {
+ if (m_fileReader) {
+ m_fileReader->releaseLock();
+ }
+ if (m_instrumentMixer) {
+ m_instrumentMixer->releaseLock();
+ }
+ if (m_bussMixer) {
+ m_bussMixer->releaseLock();
+ }
+ }
+ }
+ return rv;
+}
+
+int
+JackDriver::releaseAudioQueueLocks()
+{
+ int rv = 0;
+#ifdef DEBUG_JACK_DRIVER
+
+ std::cerr << "JackDriver::releaseAudioQueueLocks" << std::endl;
+#endif
+
+ if (m_fileWriter)
+ rv = m_fileWriter->releaseLock();
+ if (m_fileReader)
+ rv = m_fileReader->releaseLock();
+ if (m_instrumentMixer)
+ rv = m_instrumentMixer->releaseLock();
+ if (m_bussMixer)
+ rv = m_bussMixer->releaseLock();
+ return rv;
+}
+
+
+void
+JackDriver::setPluginInstance(InstrumentId id, QString identifier,
+ int position)
+{
+ if (m_instrumentMixer) {
+ m_instrumentMixer->setPlugin(id, position, identifier);
+ }
+ if (!m_alsaDriver->isPlaying()) {
+ prebufferAudio(); // to ensure the plugin's ringbuffers are generated
+ }
+}
+
+void
+JackDriver::removePluginInstance(InstrumentId id, int position)
+{
+ if (m_instrumentMixer)
+ m_instrumentMixer->removePlugin(id, position);
+}
+
+void
+JackDriver::removePluginInstances()
+{
+ if (m_instrumentMixer)
+ m_instrumentMixer->removeAllPlugins();
+}
+
+void
+JackDriver::setPluginInstancePortValue(InstrumentId id, int position,
+ unsigned long portNumber,
+ float value)
+{
+ if (m_instrumentMixer)
+ m_instrumentMixer->setPluginPortValue(id, position, portNumber, value);
+}
+
+float
+JackDriver::getPluginInstancePortValue(InstrumentId id, int position,
+ unsigned long portNumber)
+{
+ if (m_instrumentMixer)
+ return m_instrumentMixer->getPluginPortValue(id, position, portNumber);
+ return 0;
+}
+
+void
+JackDriver::setPluginInstanceBypass(InstrumentId id, int position, bool value)
+{
+ if (m_instrumentMixer)
+ m_instrumentMixer->setPluginBypass(id, position, value);
+}
+
+QStringList
+JackDriver::getPluginInstancePrograms(InstrumentId id, int position)
+{
+ if (m_instrumentMixer)
+ return m_instrumentMixer->getPluginPrograms(id, position);
+ return QStringList();
+}
+
+QString
+JackDriver::getPluginInstanceProgram(InstrumentId id, int position)
+{
+ if (m_instrumentMixer)
+ return m_instrumentMixer->getPluginProgram(id, position);
+ return QString();
+}
+
+QString
+JackDriver::getPluginInstanceProgram(InstrumentId id, int position,
+ int bank, int program)
+{
+ if (m_instrumentMixer)
+ return m_instrumentMixer->getPluginProgram(id, position, bank, program);
+ return QString();
+}
+
+unsigned long
+JackDriver::getPluginInstanceProgram(InstrumentId id, int position, QString name)
+{
+ if (m_instrumentMixer)
+ return m_instrumentMixer->getPluginProgram(id, position, name);
+ return 0;
+}
+
+void
+JackDriver::setPluginInstanceProgram(InstrumentId id, int position, QString program)
+{
+ if (m_instrumentMixer)
+ m_instrumentMixer->setPluginProgram(id, position, program);
+}
+
+QString
+JackDriver::configurePlugin(InstrumentId id, int position, QString key, QString value)
+{
+ if (m_instrumentMixer)
+ return m_instrumentMixer->configurePlugin(id, position, key, value);
+ return QString();
+}
+
+RunnablePluginInstance *
+JackDriver::getSynthPlugin(InstrumentId id)
+{
+ if (m_instrumentMixer)
+ return m_instrumentMixer->getSynthPlugin(id);
+ else
+ return 0;
+}
+
+void
+JackDriver::clearSynthPluginEvents()
+{
+ if (!m_instrumentMixer) return;
+
+#ifdef DEBUG_JACK_DRIVER
+ std::cerr << "JackDriver::clearSynthPluginEvents" << std::endl;
+#endif
+
+ m_instrumentMixer->discardPluginEvents();
+}
+
+bool
+JackDriver::openRecordFile(InstrumentId id,
+ const std::string &filename)
+{
+ if (m_fileWriter) {
+ if (!m_fileWriter->running()) {
+ m_fileWriter->run();
+ }
+ return m_fileWriter->openRecordFile(id, filename);
+ } else {
+ std::cerr << "JackDriver::openRecordFile: No file writer available!" << std::endl;
+ return false;
+ }
+}
+
+bool
+JackDriver::closeRecordFile(InstrumentId id,
+ AudioFileId &returnedId)
+{
+ if (m_fileWriter) {
+ return m_fileWriter->closeRecordFile(id, returnedId);
+ if (m_fileWriter->running() && !m_fileWriter->haveRecordFilesOpen()) {
+ m_fileWriter->terminate();
+ }
+ } else
+ return false;
+}
+
+
+void
+JackDriver::reportFailure(MappedEvent::FailureCode code)
+{
+ if (m_alsaDriver)
+ m_alsaDriver->reportFailure(code);
+}
+
+
+}
+
+#endif // HAVE_LIBJACK
+#endif // HAVE_ALSA
diff --git a/src/sound/JackDriver.h b/src/sound/JackDriver.h
new file mode 100644
index 0000000..b46080d
--- /dev/null
+++ b/src/sound/JackDriver.h
@@ -0,0 +1,297 @@
+// -*- c-basic-offset: 4 -*-
+
+/*
+ Rosegarden
+ A sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <bownie@bownie.com>
+
+ 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 _JACKDRIVER_H_
+#define _JACKDRIVER_H_
+
+#ifdef HAVE_ALSA
+#ifdef HAVE_LIBJACK
+
+#include "RunnablePluginInstance.h"
+#include <jack/jack.h>
+#include "SoundDriver.h"
+#include "Instrument.h"
+#include "RealTime.h"
+#include "ExternalTransport.h"
+#include <qstringlist.h>
+
+namespace Rosegarden
+{
+
+class AlsaDriver;
+class AudioBussMixer;
+class AudioInstrumentMixer;
+class AudioFileReader;
+class AudioFileWriter;
+
+class JackDriver
+{
+public:
+ // convenience
+ typedef jack_default_audio_sample_t sample_t;
+
+ JackDriver(AlsaDriver *alsaDriver);
+ virtual ~JackDriver();
+
+ bool isOK() const { return m_ok; }
+
+ bool isTransportEnabled() { return m_jackTransportEnabled; }
+ bool isTransportMaster () { return m_jackTransportMaster; }
+
+ void setTransportEnabled(bool e) { m_jackTransportEnabled = e; }
+ void setTransportMaster (bool m) { m_jackTransportMaster = m; }
+
+ // These methods call back on the sound driver if necessary to
+ // establish the current transport location to start at or
+ // relocate to. startTransport and relocateTransport return true
+ // if they have completed and the sound driver can safely call
+ // startClocks; false if the sound driver should wait for the JACK
+ // driver to call back on startClocksApproved before starting.
+ bool startTransport();
+ bool relocateTransport();
+ void stopTransport();
+
+ RealTime getAudioPlayLatency() const;
+ RealTime getAudioRecordLatency() const;
+ RealTime getInstrumentPlayLatency(InstrumentId) const;
+ RealTime getMaximumPlayLatency() const;
+
+ // Plugin instance management
+ //
+ virtual void setPluginInstance(InstrumentId id,
+ QString identifier,
+ int position);
+
+ virtual void removePluginInstance(InstrumentId id, int position);
+
+ // Remove all plugin instances
+ //
+ virtual void removePluginInstances();
+
+ virtual void setPluginInstancePortValue(InstrumentId id,
+ int position,
+ unsigned long portNumber,
+ float value);
+
+ virtual float getPluginInstancePortValue(InstrumentId id,
+ int position,
+ unsigned long portNumber);
+
+ virtual void setPluginInstanceBypass(InstrumentId id,
+ int position,
+ bool value);
+
+ virtual QStringList getPluginInstancePrograms(InstrumentId id,
+ int position);
+
+ virtual QString getPluginInstanceProgram(InstrumentId id,
+ int position);
+
+ virtual QString getPluginInstanceProgram(InstrumentId id,
+ int position,
+ int bank,
+ int program);
+
+ virtual unsigned long getPluginInstanceProgram(InstrumentId id,
+ int position,
+ QString name);
+
+ virtual void setPluginInstanceProgram(InstrumentId id,
+ int position,
+ QString program);
+
+ virtual QString configurePlugin(InstrumentId id,
+ int position,
+ QString key, QString value);
+
+ virtual RunnablePluginInstance *getSynthPlugin(InstrumentId id);
+
+ virtual void clearSynthPluginEvents(); // when stopping
+
+ virtual unsigned int getSampleRate() const { return m_sampleRate; }
+ virtual unsigned int getBufferSize() const { return m_bufferSize; }
+
+ // A new audio file for storage of our recorded samples - the
+ // file stays open so we can append samples at will. We must
+ // explicitly close the file eventually though to make sure
+ // the integrity is correct (sample sizes must be written).
+ //
+ bool openRecordFile(InstrumentId id,
+ const std::string &fileName);
+ bool closeRecordFile(InstrumentId id,
+ AudioFileId &returnedId);
+
+ // Set or change the number of audio inputs and outputs.
+ // The first of these is slightly misnamed -- the submasters
+ // argument controls the number of busses, not ports (which
+ // may or may not exist depending on the setAudioPorts call).
+ //
+ void setAudioPorts(bool faderOuts, bool submasterOuts);
+
+ // Locks used by the disk thread and mix thread. The AlsaDriver
+ // should hold these locks whenever it wants to modify its audio
+ // play queue -- at least when adding or removing files or
+ // resetting status; it doesn't need to hold the locks when
+ // incrementing their statuses or simply reading them.
+ //
+ int getAudioQueueLocks();
+ int tryAudioQueueLocks();
+ int releaseAudioQueueLocks();
+
+ void prepareAudio(); // when repositioning etc
+ void prebufferAudio(); // when starting playback (incorporates prepareAudio)
+ void kickAudio(); // for paranoia only
+
+ // Because we don't want to do any lookups that might involve
+ // locking etc from within the JACK process thread, we instead
+ // call this regularly from the ALSA driver thread -- it looks up
+ // various bits of data such as the master fader and monitoring
+ // levels, number of inputs etc and either processes them or
+ // writes them into simple records in the JACK driver for process
+ // to read. Actually quite a lot of work.
+ //
+ void updateAudioData();
+
+ // Similarly, set data on the buss mixer to avoid the buss mixer
+ // having to call back on the mapped studio to discover it
+ //
+ void setAudioBussLevels(int bussNo, float dB, float pan);
+
+ // Likewise for instrument mixer
+ //
+ void setAudioInstrumentLevels(InstrumentId instrument, float dB, float pan);
+
+ // Called from AlsaDriver to indicate that an async MIDI event is
+ // being sent to a soft synth. JackDriver uses this to suggest
+ // that it needs to start processing soft synths, if it wasn't
+ // already. It will switch this off again itself when things
+ // fall silent.
+ //
+ void setHaveAsyncAudioEvent() { m_haveAsyncAudioEvent = true; }
+
+ RealTime getNextSliceStart(const RealTime &now) const;
+
+ // For audit purposes only.
+ size_t getFramesProcessed() const { return m_framesProcessed; }
+
+ // Reinitialise if we've been kicked off JACK -- if we can
+ //
+ void restoreIfRestorable();
+
+ // Report back to GUI via the AlsaDriver
+ //
+ void reportFailure(MappedEvent::FailureCode code);
+
+protected:
+
+ // static methods for JACK process thread:
+ static int jackProcessStatic(jack_nframes_t nframes, void *arg);
+ static int jackBufferSize(jack_nframes_t nframes, void *arg);
+ static int jackSampleRate(jack_nframes_t nframes, void *arg);
+ static void jackShutdown(void *arg);
+ static int jackXRun(void *);
+
+ // static JACK transport callbacks
+ static int jackSyncCallback(jack_transport_state_t,
+ jack_position_t *, void *);
+ static int jackTimebaseCallback(jack_transport_state_t,
+ jack_nframes_t,
+ jack_position_t *,
+ int,
+ void *);
+
+ // jackProcessStatic delegates to this
+ int jackProcess(jack_nframes_t nframes);
+ int jackProcessRecord(InstrumentId id,
+ jack_nframes_t nframes,
+ sample_t *, sample_t *, bool);
+ int jackProcessEmpty(jack_nframes_t nframes);
+
+ // other helper methods:
+
+ void initialise(bool reinitialise = false);
+
+ bool createMainOutputs();
+ bool createFaderOutputs(int audioPairs, int synthPairs);
+ bool createSubmasterOutputs(int pairs);
+ bool createRecordInputs(int pairs);
+
+ bool relocateTransportInternal(bool alsoStart);
+
+ // data members:
+
+ jack_client_t *m_client;
+
+ std::vector<jack_port_t *> m_inputPorts;
+ std::vector<jack_port_t *> m_outputInstruments;
+ std::vector<jack_port_t *> m_outputSubmasters;
+ std::vector<jack_port_t *> m_outputMonitors;
+ std::vector<jack_port_t *> m_outputMasters;
+
+ jack_nframes_t m_bufferSize;
+ jack_nframes_t m_sampleRate;
+
+ sample_t *m_tempOutBuffer;
+
+ bool m_jackTransportEnabled;
+ bool m_jackTransportMaster;
+
+ bool m_waiting;
+ jack_transport_state_t m_waitingState;
+ ExternalTransport::TransportToken m_waitingToken;
+ int m_ignoreProcessTransportCount;
+
+ AudioBussMixer *m_bussMixer;
+ AudioInstrumentMixer *m_instrumentMixer;
+ AudioFileReader *m_fileReader;
+ AudioFileWriter *m_fileWriter;
+ AlsaDriver *m_alsaDriver;
+
+ float m_masterLevel;
+ unsigned long m_directMasterAudioInstruments; // bitmap
+ unsigned long m_directMasterSynthInstruments;
+ std::map<InstrumentId, RealTime> m_instrumentLatencies;
+ RealTime m_maxInstrumentLatency;
+ bool m_haveAsyncAudioEvent;
+
+ struct RecordInputDesc {
+ int input;
+ int channel;
+ float level;
+ RecordInputDesc(int i = 1000, int c = -1, float l = 0.0f) :
+ input(i), channel(c), level(l) { }
+ };
+ typedef std::map<InstrumentId, RecordInputDesc> RecordInputMap;
+ RecordInputMap m_recordInputs;
+
+ time_t m_kickedOutAt;
+ size_t m_framesProcessed;
+ bool m_ok;
+};
+
+
+}
+
+#endif
+#endif
+
+#endif
+
diff --git a/src/sound/LADSPAPluginFactory.cpp b/src/sound/LADSPAPluginFactory.cpp
new file mode 100644
index 0000000..2a4a4ea
--- /dev/null
+++ b/src/sound/LADSPAPluginFactory.cpp
@@ -0,0 +1,841 @@
+// -*- c-basic-offset: 4 -*-
+
+/*
+ Rosegarden
+ A sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <bownie@bownie.com>
+
+ 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 "LADSPAPluginFactory.h"
+#include <iostream>
+#include <cstdlib>
+
+#ifdef HAVE_LADSPA
+
+#include <dlfcn.h>
+#include <qdir.h>
+#include <cmath>
+
+#include "AudioPluginInstance.h"
+#include "LADSPAPluginInstance.h"
+#include "MappedStudio.h"
+#include "PluginIdentifier.h"
+
+#ifdef HAVE_LIBLRDF
+#include "lrdf.h"
+#endif // HAVE_LIBLRDF
+
+#include <kdebug.h>
+
+namespace Rosegarden
+{
+
+LADSPAPluginFactory::LADSPAPluginFactory()
+{}
+
+LADSPAPluginFactory::~LADSPAPluginFactory()
+{
+ for (std::set
+ <RunnablePluginInstance *>::iterator i = m_instances.begin();
+ i != m_instances.end(); ++i) {
+ (*i)->setFactory(0);
+ delete *i;
+ }
+ m_instances.clear();
+ unloadUnusedLibraries();
+}
+
+const std::vector<QString> &
+LADSPAPluginFactory::getPluginIdentifiers() const
+{
+ return m_identifiers;
+}
+
+void
+LADSPAPluginFactory::enumeratePlugins(MappedObjectPropertyList &list)
+{
+ for (std::vector<QString>::iterator i = m_identifiers.begin();
+ i != m_identifiers.end(); ++i) {
+
+ const LADSPA_Descriptor *descriptor = getLADSPADescriptor(*i);
+
+ if (!descriptor) {
+ std::cerr << "WARNING: LADSPAPluginFactory::enumeratePlugins: couldn't get descriptor for identifier " << *i << std::endl;
+ continue;
+ }
+
+// std::cerr << "Enumerating plugin identifier " << *i << std::endl;
+
+ list.push_back(*i);
+ list.push_back(descriptor->Name);
+ list.push_back(QString("%1").arg(descriptor->UniqueID));
+ list.push_back(descriptor->Label);
+ list.push_back(descriptor->Maker);
+ list.push_back(descriptor->Copyright);
+ list.push_back("false"); // is synth
+ list.push_back("false"); // is grouped
+
+ if (m_taxonomy.find(descriptor->UniqueID) != m_taxonomy.end() &&
+ m_taxonomy[descriptor->UniqueID] != "") {
+// std::cerr << "LADSPAPluginFactory: cat for " << *i<< " found in taxonomy as " << m_taxonomy[descriptor->UniqueID] << std::endl;
+ list.push_back(m_taxonomy[descriptor->UniqueID]);
+
+ } else if (m_fallbackCategories.find(*i) !=
+ m_fallbackCategories.end()) {
+ list.push_back(m_fallbackCategories[*i]);
+// std::cerr << "LADSPAPluginFactory: cat for " << *i <<" found in fallbacks as " << m_fallbackCategories[*i] << std::endl;
+
+ } else {
+ list.push_back("");
+// std::cerr << "LADSPAPluginFactory: cat for " << *i << " not found (despite having " << m_fallbackCategories.size() << " fallbacks)" << std::endl;
+
+ }
+
+ list.push_back(QString("%1").arg(descriptor->PortCount));
+
+ for (unsigned long p = 0; p < descriptor->PortCount; ++p) {
+
+ int type = 0;
+ if (LADSPA_IS_PORT_CONTROL(descriptor->PortDescriptors[p])) {
+ type |= PluginPort::Control;
+ } else {
+ type |= PluginPort::Audio;
+ }
+ if (LADSPA_IS_PORT_INPUT(descriptor->PortDescriptors[p])) {
+ type |= PluginPort::Input;
+ } else {
+ type |= PluginPort::Output;
+ }
+
+ list.push_back(QString("%1").arg(p));
+ list.push_back(descriptor->PortNames[p]);
+ list.push_back(QString("%1").arg(type));
+ list.push_back(QString("%1").arg(getPortDisplayHint(descriptor, p)));
+ list.push_back(QString("%1").arg(getPortMinimum(descriptor, p)));
+ list.push_back(QString("%1").arg(getPortMaximum(descriptor, p)));
+ list.push_back(QString("%1").arg(getPortDefault(descriptor, p)));
+ }
+ }
+
+ unloadUnusedLibraries();
+}
+
+
+void
+LADSPAPluginFactory::populatePluginSlot(QString identifier, MappedPluginSlot &slot)
+{
+ const LADSPA_Descriptor *descriptor = getLADSPADescriptor(identifier);
+
+ if (descriptor) {
+
+ slot.setProperty(MappedPluginSlot::Label, descriptor->Label);
+ slot.setProperty(MappedPluginSlot::PluginName, descriptor->Name);
+ slot.setProperty(MappedPluginSlot::Author, descriptor->Maker);
+ slot.setProperty(MappedPluginSlot::Copyright, descriptor->Copyright);
+ slot.setProperty(MappedPluginSlot::PortCount, descriptor->PortCount);
+
+ if (m_taxonomy.find(descriptor->UniqueID) != m_taxonomy.end() &&
+ m_taxonomy[descriptor->UniqueID] != "") {
+ // std::cerr << "LADSPAPluginFactory: cat for " << identifier<< " found in taxonomy as " << m_taxonomy[descriptor->UniqueID] << std::endl;
+ slot.setProperty(MappedPluginSlot::Category,
+ m_taxonomy[descriptor->UniqueID]);
+
+ } else if (m_fallbackCategories.find(identifier) !=
+ m_fallbackCategories.end()) {
+ // std::cerr << "LADSPAPluginFactory: cat for " << identifier <<" found in fallbacks as " << m_fallbackCategories[identifier] << std::endl;
+ slot.setProperty(MappedPluginSlot::Category,
+ m_fallbackCategories[identifier]);
+
+ } else {
+ // std::cerr << "LADSPAPluginFactory: cat for " << identifier << " not found (despite having " << m_fallbackCategories.size() << " fallbacks)" << std::endl;
+ slot.setProperty(MappedPluginSlot::Category, "");
+ }
+
+ slot.destroyChildren();
+
+ for (unsigned long i = 0; i < descriptor->PortCount; i++) {
+
+ if (LADSPA_IS_PORT_CONTROL(descriptor->PortDescriptors[i]) &&
+ LADSPA_IS_PORT_INPUT(descriptor->PortDescriptors[i])) {
+
+ MappedStudio *studio = dynamic_cast<MappedStudio *>(slot.getParent());
+ if (!studio) {
+ std::cerr << "WARNING: LADSPAPluginFactory::populatePluginSlot: can't find studio" << std::endl;
+ return ;
+ }
+
+ MappedPluginPort *port =
+ dynamic_cast<MappedPluginPort *>
+ (studio->createObject(MappedObject::PluginPort));
+
+ slot.addChild(port);
+ port->setParent(&slot);
+
+ port->setProperty(MappedPluginPort::PortNumber, i);
+ port->setProperty(MappedPluginPort::Name,
+ descriptor->PortNames[i]);
+ port->setProperty(MappedPluginPort::Maximum,
+ getPortMaximum(descriptor, i));
+ port->setProperty(MappedPluginPort::Minimum,
+ getPortMinimum(descriptor, i));
+ port->setProperty(MappedPluginPort::Default,
+ getPortDefault(descriptor, i));
+ port->setProperty(MappedPluginPort::DisplayHint,
+ getPortDisplayHint(descriptor, i));
+ }
+ }
+ }
+
+ //!!! leak here if the plugin is not instantiated too...?
+}
+
+MappedObjectValue
+LADSPAPluginFactory::getPortMinimum(const LADSPA_Descriptor *descriptor, int port)
+{
+ LADSPA_PortRangeHintDescriptor d =
+ descriptor->PortRangeHints[port].HintDescriptor;
+
+ MappedObjectValue minimum = 0.0;
+
+ if (LADSPA_IS_HINT_BOUNDED_BELOW(d)) {
+ MappedObjectValue lb = descriptor->PortRangeHints[port].LowerBound;
+ minimum = lb;
+ } else if (LADSPA_IS_HINT_BOUNDED_ABOVE(d)) {
+ MappedObjectValue ub = descriptor->PortRangeHints[port].UpperBound;
+ minimum = std::min(0.f, ub - 1.f);
+ }
+
+ if (LADSPA_IS_HINT_SAMPLE_RATE(d)) {
+ minimum *= m_sampleRate;
+ }
+
+ if (LADSPA_IS_HINT_LOGARITHMIC(d)) {
+ if (minimum == 0.f) minimum = 1.f;
+ }
+
+ return minimum;
+}
+
+MappedObjectValue
+LADSPAPluginFactory::getPortMaximum(const LADSPA_Descriptor *descriptor, int port)
+{
+ LADSPA_PortRangeHintDescriptor d =
+ descriptor->PortRangeHints[port].HintDescriptor;
+
+ MappedObjectValue maximum = 1.0;
+
+// std::cerr << "LADSPAPluginFactory::getPortMaximum(" << port << ")" << std::endl;
+// std::cerr << "bounded above: " << LADSPA_IS_HINT_BOUNDED_ABOVE(d) << std::endl;
+
+ if (LADSPA_IS_HINT_BOUNDED_ABOVE(d)) {
+ MappedObjectValue ub = descriptor->PortRangeHints[port].UpperBound;
+ maximum = ub;
+ } else {
+ MappedObjectValue lb = descriptor->PortRangeHints[port].LowerBound;
+ if (LADSPA_IS_HINT_LOGARITHMIC(d)) {
+ if (lb == 0.f) lb = 1.f;
+ maximum = lb * 100.f;
+ } else {
+ if (lb == 1.f) maximum = 10.f;
+ else maximum = lb + 10;
+ }
+ }
+
+ if (LADSPA_IS_HINT_SAMPLE_RATE(d)) {
+// std::cerr << "note: port has sample rate hint" << std::endl;
+ maximum *= m_sampleRate;
+ }
+
+// std::cerr << "maximum: " << maximum << std::endl;
+// if (LADSPA_IS_HINT_LOGARITHMIC(d)) {
+// std::cerr << "note: port is logarithmic" << std::endl;
+// }
+// std::cerr << "note: minimum is reported as " << getPortMinimum(descriptor, port) << " (from bounded = " << LADSPA_IS_HINT_BOUNDED_BELOW(d) << ", bound = " << descriptor->PortRangeHints[port].LowerBound << ")" << std::endl;
+
+ return maximum;
+}
+
+MappedObjectValue
+LADSPAPluginFactory::getPortDefault(const LADSPA_Descriptor *descriptor, int port)
+{
+ MappedObjectValue minimum = getPortMinimum(descriptor, port);
+ MappedObjectValue maximum = getPortMaximum(descriptor, port);
+ MappedObjectValue deft;
+
+ if (m_portDefaults.find(descriptor->UniqueID) !=
+ m_portDefaults.end()) {
+ if (m_portDefaults[descriptor->UniqueID].find(port) !=
+ m_portDefaults[descriptor->UniqueID].end()) {
+
+ deft = m_portDefaults[descriptor->UniqueID][port];
+ if (deft < minimum) deft = minimum;
+ if (deft > maximum) deft = maximum;
+// std::cerr << "port " << port << ": default " << deft << " from defaults" << std::endl;
+ return deft;
+ }
+ }
+
+ LADSPA_PortRangeHintDescriptor d =
+ descriptor->PortRangeHints[port].HintDescriptor;
+
+ bool logarithmic = LADSPA_IS_HINT_LOGARITHMIC(d);
+
+ float logmin = 0, logmax = 0;
+ if (logarithmic) {
+ float thresh = powf(10, -10);
+ if (minimum < thresh) logmin = -10;
+ else logmin = log10f(minimum);
+ if (maximum < thresh) logmax = -10;
+ else logmax = log10f(maximum);
+ }
+
+ if (!LADSPA_IS_HINT_HAS_DEFAULT(d)) {
+
+ deft = minimum;
+
+ } else if (LADSPA_IS_HINT_DEFAULT_MINIMUM(d)) {
+
+ // See comment for DEFAULT_MAXIMUM below
+ if (!LADSPA_IS_HINT_BOUNDED_BELOW(d)) {
+ deft = descriptor->PortRangeHints[port].LowerBound;
+ if (LADSPA_IS_HINT_SAMPLE_RATE(d)) {
+ deft *= m_sampleRate;
+ }
+// std::cerr << "default-minimum: " << deft << std::endl;
+ if (deft < minimum || deft > maximum) deft = minimum;
+// std::cerr << "default-minimum: " << deft << std::endl;
+ } else {
+ deft = minimum;
+ }
+
+ } else if (LADSPA_IS_HINT_DEFAULT_LOW(d)) {
+
+ if (logarithmic) {
+ deft = powf(10, logmin * 0.75 + logmax * 0.25);
+ } else {
+ deft = minimum * 0.75 + maximum * 0.25;
+ }
+
+ } else if (LADSPA_IS_HINT_DEFAULT_MIDDLE(d)) {
+
+ if (logarithmic) {
+ deft = powf(10, logmin * 0.5 + logmax * 0.5);
+ } else {
+ deft = minimum * 0.5 + maximum * 0.5;
+ }
+
+ } else if (LADSPA_IS_HINT_DEFAULT_HIGH(d)) {
+
+ if (logarithmic) {
+ deft = powf(10, logmin * 0.25 + logmax * 0.75);
+ } else {
+ deft = minimum * 0.25 + maximum * 0.75;
+ }
+
+ } else if (LADSPA_IS_HINT_DEFAULT_MAXIMUM(d)) {
+
+ // CMT plugins employ this grossness (setting DEFAULT_MAXIMUM
+ // without BOUNDED_ABOVE and then using the UPPER_BOUND as the
+ // port default)
+ if (!LADSPA_IS_HINT_BOUNDED_ABOVE(d)) {
+ deft = descriptor->PortRangeHints[port].UpperBound;
+ if (LADSPA_IS_HINT_SAMPLE_RATE(d)) {
+ deft *= m_sampleRate;
+ }
+// std::cerr << "default-maximum: " << deft << std::endl;
+ if (deft < minimum || deft > maximum) deft = maximum;
+// std::cerr << "default-maximum: " << deft << std::endl;
+ } else {
+ deft = maximum;
+ }
+
+ } else if (LADSPA_IS_HINT_DEFAULT_0(d)) {
+
+ deft = 0.0;
+
+ } else if (LADSPA_IS_HINT_DEFAULT_1(d)) {
+
+ deft = 1.0;
+
+ } else if (LADSPA_IS_HINT_DEFAULT_100(d)) {
+
+ deft = 100.0;
+
+ } else if (LADSPA_IS_HINT_DEFAULT_440(d)) {
+
+ deft = 440.0;
+
+ } else {
+
+ deft = minimum;
+ }
+
+// std::cerr << "port " << port << " default = "<< deft << std::endl;
+
+ return deft;
+}
+
+int
+LADSPAPluginFactory::getPortDisplayHint(const LADSPA_Descriptor *descriptor, int port)
+{
+ LADSPA_PortRangeHintDescriptor d =
+ descriptor->PortRangeHints[port].HintDescriptor;
+ int hint = PluginPort::NoHint;
+
+ if (LADSPA_IS_HINT_TOGGLED(d))
+ hint |= PluginPort::Toggled;
+ if (LADSPA_IS_HINT_INTEGER(d))
+ hint |= PluginPort::Integer;
+ if (LADSPA_IS_HINT_LOGARITHMIC(d))
+ hint |= PluginPort::Logarithmic;
+
+ return hint;
+}
+
+
+RunnablePluginInstance *
+LADSPAPluginFactory::instantiatePlugin(QString identifier,
+ int instrument,
+ int position,
+ unsigned int sampleRate,
+ unsigned int blockSize,
+ unsigned int channels)
+{
+ const LADSPA_Descriptor *descriptor = getLADSPADescriptor(identifier);
+
+ if (descriptor) {
+
+ LADSPAPluginInstance *instance =
+ new LADSPAPluginInstance
+ (this, instrument, identifier, position, sampleRate, blockSize, channels,
+ descriptor);
+
+ m_instances.insert(instance);
+
+ return instance;
+ }
+
+ return 0;
+}
+
+void
+LADSPAPluginFactory::releasePlugin(RunnablePluginInstance *instance,
+ QString identifier)
+{
+ if (m_instances.find(instance) == m_instances.end()) {
+ std::cerr << "WARNING: LADSPAPluginFactory::releasePlugin: Not one of mine!"
+ << std::endl;
+ return ;
+ }
+
+ QString type, soname, label;
+ PluginIdentifier::parseIdentifier(identifier, type, soname, label);
+
+ m_instances.erase(m_instances.find(instance));
+
+ bool stillInUse = false;
+
+ for (std::set
+ <RunnablePluginInstance *>::iterator ii = m_instances.begin();
+ ii != m_instances.end(); ++ii) {
+ QString itype, isoname, ilabel;
+ PluginIdentifier::parseIdentifier((*ii)->getIdentifier(), itype, isoname, ilabel);
+ if (isoname == soname) {
+ // std::cerr << "LADSPAPluginFactory::releasePlugin: dll " << soname << " is still in use for plugin " << ilabel << std::endl;
+ stillInUse = true;
+ break;
+ }
+ }
+
+ if (!stillInUse) {
+ // std::cerr << "LADSPAPluginFactory::releasePlugin: dll " << soname << " no longer in use, unloading" << std::endl;
+ unloadLibrary(soname);
+ }
+}
+
+const LADSPA_Descriptor *
+LADSPAPluginFactory::getLADSPADescriptor(QString identifier)
+{
+ QString type, soname, label;
+ PluginIdentifier::parseIdentifier(identifier, type, soname, label);
+
+ if (m_libraryHandles.find(soname) == m_libraryHandles.end()) {
+ loadLibrary(soname);
+ if (m_libraryHandles.find(soname) == m_libraryHandles.end()) {
+ std::cerr << "WARNING: LADSPAPluginFactory::getLADSPADescriptor: loadLibrary failed for " << soname << std::endl;
+ return 0;
+ }
+ }
+
+ void *libraryHandle = m_libraryHandles[soname];
+
+ LADSPA_Descriptor_Function fn = (LADSPA_Descriptor_Function)
+ dlsym(libraryHandle, "ladspa_descriptor");
+
+ if (!fn) {
+ std::cerr << "WARNING: LADSPAPluginFactory::getLADSPADescriptor: No descriptor function in library " << soname << std::endl;
+ return 0;
+ }
+
+ const LADSPA_Descriptor *descriptor = 0;
+
+ int index = 0;
+ while ((descriptor = fn(index))) {
+ if (descriptor->Label == label)
+ return descriptor;
+ ++index;
+ }
+
+ std::cerr << "WARNING: LADSPAPluginFactory::getLADSPADescriptor: No such plugin as " << label << " in library " << soname << std::endl;
+
+ return 0;
+}
+
+void
+LADSPAPluginFactory::loadLibrary(QString soName)
+{
+ void *libraryHandle = dlopen(soName.data(), RTLD_NOW);
+ if (libraryHandle)
+ m_libraryHandles[soName] = libraryHandle;
+}
+
+void
+LADSPAPluginFactory::unloadLibrary(QString soName)
+{
+ LibraryHandleMap::iterator li = m_libraryHandles.find(soName);
+ if (li != m_libraryHandles.end()) {
+ // std::cerr << "unloading " << soName << std::endl;
+ dlclose(m_libraryHandles[soName]);
+ m_libraryHandles.erase(li);
+ }
+}
+
+void
+LADSPAPluginFactory::unloadUnusedLibraries()
+{
+ std::vector<QString> toUnload;
+
+ for (LibraryHandleMap::iterator i = m_libraryHandles.begin();
+ i != m_libraryHandles.end(); ++i) {
+
+ bool stillInUse = false;
+
+ for (std::set
+ <RunnablePluginInstance *>::iterator ii = m_instances.begin();
+ ii != m_instances.end(); ++ii) {
+
+ QString itype, isoname, ilabel;
+ PluginIdentifier::parseIdentifier((*ii)->getIdentifier(), itype, isoname, ilabel);
+ if (isoname == i->first) {
+ stillInUse = true;
+ break;
+ }
+ }
+
+ if (!stillInUse)
+ toUnload.push_back(i->first);
+ }
+
+ for (std::vector<QString>::iterator i = toUnload.begin();
+ i != toUnload.end(); ++i) {
+ unloadLibrary(*i);
+ }
+}
+
+
+// It is only later, after they've gone,
+// I realize they have delivered a letter.
+// It's a letter from my wife. "What are you doing
+// there?" my wife asks. "Are you drinking?"
+// I study the postmark for hours. Then it, too, begins to fade.
+// I hope someday to forget all this.
+
+
+std::vector<QString>
+LADSPAPluginFactory::getPluginPath()
+{
+ std::vector<QString> pathList;
+ std::string path;
+
+ char *cpath = getenv("LADSPA_PATH");
+ if (cpath)
+ path = cpath;
+
+ if (path == "") {
+ path = "/usr/local/lib/ladspa:/usr/lib/ladspa";
+ char *home = getenv("HOME");
+ if (home)
+ path = std::string(home) + "/.ladspa:" + path;
+ }
+
+ std::string::size_type index = 0, newindex = 0;
+
+ while ((newindex = path.find(':', index)) < path.size()) {
+ pathList.push_back(path.substr(index, newindex - index).c_str());
+ index = newindex + 1;
+ }
+
+ pathList.push_back(path.substr(index).c_str());
+
+ return pathList;
+}
+
+
+#ifdef HAVE_LIBLRDF
+std::vector<QString>
+LADSPAPluginFactory::getLRDFPath(QString &baseUri)
+{
+ std::vector<QString> pathList = getPluginPath();
+ std::vector<QString> lrdfPaths;
+
+ lrdfPaths.push_back("/usr/local/share/ladspa/rdf");
+ lrdfPaths.push_back("/usr/share/ladspa/rdf");
+
+ for (std::vector<QString>::iterator i = pathList.begin();
+ i != pathList.end(); ++i) {
+ lrdfPaths.push_back(*i + "/rdf");
+ }
+
+ baseUri = LADSPA_BASE;
+ return lrdfPaths;
+}
+#endif
+
+void
+LADSPAPluginFactory::discoverPlugins()
+{
+ std::vector<QString> pathList = getPluginPath();
+
+ std::cerr << "LADSPAPluginFactory::discoverPlugins - "
+ << "discovering plugins; path is ";
+ for (std::vector<QString>::iterator i = pathList.begin();
+ i != pathList.end(); ++i) {
+ std::cerr << "[" << *i << "] ";
+ }
+ std::cerr << std::endl;
+
+// std::cerr << "LADSPAPluginFactory::discoverPlugins - "
+// << "trace is ";
+// std::cerr << kdBacktrace() << std::endl;
+
+#ifdef HAVE_LIBLRDF
+ // Initialise liblrdf and read the description files
+ //
+ lrdf_init();
+
+ QString baseUri;
+ std::vector<QString> lrdfPaths = getLRDFPath(baseUri);
+
+ bool haveSomething = false;
+
+ for (size_t i = 0; i < lrdfPaths.size(); ++i) {
+ QDir dir(lrdfPaths[i], "*.rdf;*.rdfs");
+ for (unsigned int j = 0; j < dir.count(); ++j) {
+ if (!lrdf_read_file(QString("file:" + lrdfPaths[i] + "/" + dir[j]).data())) {
+ // std::cerr << "LADSPAPluginFactory: read RDF file " << (lrdfPaths[i] + "/" + dir[j]) << std::endl;
+ haveSomething = true;
+ }
+ }
+ }
+
+ if (haveSomething) {
+ generateTaxonomy(baseUri + "Plugin", "");
+ }
+#endif // HAVE_LIBLRDF
+
+ generateFallbackCategories();
+
+ for (std::vector<QString>::iterator i = pathList.begin();
+ i != pathList.end(); ++i) {
+
+ QDir pluginDir(*i, "*.so");
+
+ for (unsigned int j = 0; j < pluginDir.count(); ++j) {
+ discoverPlugins(QString("%1/%2").arg(*i).arg(pluginDir[j]));
+ }
+ }
+
+#ifdef HAVE_LIBLRDF
+ // Cleanup after the RDF library
+ //
+ lrdf_cleanup();
+#endif // HAVE_LIBLRDF
+
+ std::cerr << "LADSPAPluginFactory::discoverPlugins - done" << std::endl;
+}
+
+void
+LADSPAPluginFactory::discoverPlugins(QString soName)
+{
+ void *libraryHandle = dlopen(soName.data(), RTLD_LAZY);
+
+ if (!libraryHandle) {
+ std::cerr << "WARNING: LADSPAPluginFactory::discoverPlugins: couldn't dlopen "
+ << soName << " - " << dlerror() << std::endl;
+ return ;
+ }
+
+ LADSPA_Descriptor_Function fn = (LADSPA_Descriptor_Function)
+ dlsym(libraryHandle, "ladspa_descriptor");
+
+ if (!fn) {
+ std::cerr << "WARNING: LADSPAPluginFactory::discoverPlugins: No descriptor function in " << soName << std::endl;
+ return ;
+ }
+
+ const LADSPA_Descriptor *descriptor = 0;
+
+ int index = 0;
+ while ((descriptor = fn(index))) {
+
+#ifdef HAVE_LIBLRDF
+ char * def_uri = 0;
+ lrdf_defaults *defs = 0;
+
+ QString category = m_taxonomy[descriptor->UniqueID];
+
+ if (category == "" && descriptor->Name != 0) {
+ std::string name = descriptor->Name;
+ if (name.length() > 4 &&
+ name.substr(name.length() - 4) == " VST") {
+ category = "VST effects";
+ m_taxonomy[descriptor->UniqueID] = category;
+ }
+ }
+
+// std::cerr << "Plugin id is " << descriptor->UniqueID
+// << ", category is \"" << (category ? category : QString("(none)"))
+// << "\", name is " << descriptor->Name
+// << ", label is " << descriptor->Label
+// << std::endl;
+
+ def_uri = lrdf_get_default_uri(descriptor->UniqueID);
+ if (def_uri) {
+ defs = lrdf_get_setting_values(def_uri);
+ }
+
+ int controlPortNumber = 1;
+
+ for (unsigned long i = 0; i < descriptor->PortCount; i++) {
+
+ if (LADSPA_IS_PORT_CONTROL(descriptor->PortDescriptors[i])) {
+
+ if (def_uri && defs) {
+
+ for (int j = 0; j < defs->count; j++) {
+ if (defs->items[j].pid == controlPortNumber) {
+ // std::cerr << "Default for this port (" << defs->items[j].pid << ", " << defs->items[j].label << ") is " << defs->items[j].value << "; applying this to port number " << i << " with name " << descriptor->PortNames[i] << std::endl;
+ m_portDefaults[descriptor->UniqueID][i] =
+ defs->items[j].value;
+ }
+ }
+ }
+
+ ++controlPortNumber;
+ }
+ }
+#endif // HAVE_LIBLRDF
+
+ QString identifier = PluginIdentifier::createIdentifier
+ ("ladspa", soName, descriptor->Label);
+// std::cerr << "Added plugin identifier " << identifier << std::endl;
+ m_identifiers.push_back(identifier);
+
+ ++index;
+ }
+
+ if (dlclose(libraryHandle) != 0) {
+ std::cerr << "WARNING: LADSPAPluginFactory::discoverPlugins - can't unload " << libraryHandle << std::endl;
+ return ;
+ }
+}
+
+void
+LADSPAPluginFactory::generateFallbackCategories()
+{
+ std::vector<QString> pluginPath = getPluginPath();
+ std::vector<QString> path;
+
+ for (size_t i = 0; i < pluginPath.size(); ++i) {
+ if (pluginPath[i].contains("/lib/")) {
+ QString p(pluginPath[i]);
+ p.replace("/lib/", "/share/");
+ path.push_back(p);
+ // std::cerr << "LADSPAPluginFactory::generateFallbackCategories: path element " << p << std::endl;
+ }
+ path.push_back(pluginPath[i]);
+ // std::cerr << "LADSPAPluginFactory::generateFallbackCategories: path element " << pluginPath[i] << std::endl;
+ }
+
+ for (size_t i = 0; i < path.size(); ++i) {
+
+ QDir dir(path[i], "*.cat");
+
+// std::cerr << "LADSPAPluginFactory::generateFallbackCategories: directory " << path[i] << " has " << dir.count() << " .cat files" << std::endl;
+ for (unsigned int j = 0; j < dir.count(); ++j) {
+
+ QFile file(path[i] + "/" + dir[j]);
+
+ // std::cerr << "LADSPAPluginFactory::generateFallbackCategories: about to open " << (path[i] + "/" + dir[j]) << std::endl;
+
+ if (file.open(IO_ReadOnly)) {
+ // std::cerr << "...opened" << std::endl;
+ QTextStream stream(&file);
+ QString line;
+
+ while (!stream.eof()) {
+ line = stream.readLine();
+ // std::cerr << "line is: \"" << line << "\"" << std::endl;
+ QString id = line.section("::", 0, 0);
+ QString cat = line.section("::", 1, 1);
+ m_fallbackCategories[id] = cat;
+// std::cerr << "set id \"" << id << "\" to cat \"" << cat << "\"" << std::endl;
+ }
+ }
+ }
+ }
+}
+
+void
+LADSPAPluginFactory::generateTaxonomy(QString uri, QString base)
+{
+#ifdef HAVE_LIBLRDF
+ lrdf_uris *uris = lrdf_get_instances(uri.data());
+
+ if (uris != NULL) {
+ for (int i = 0; i < uris->count; ++i) {
+ m_taxonomy[lrdf_get_uid(uris->items[i])] = base;
+ }
+ lrdf_free_uris(uris);
+ }
+
+ uris = lrdf_get_subclasses(uri.data());
+
+ if (uris != NULL) {
+ for (int i = 0; i < uris->count; ++i) {
+ char *label = lrdf_get_label(uris->items[i]);
+ generateTaxonomy(uris->items[i],
+ base + (base.length() > 0 ? " > " : "") + label);
+ }
+ lrdf_free_uris(uris);
+ }
+#endif
+}
+
+}
+
+#endif // HAVE_LADSPA
+
diff --git a/src/sound/LADSPAPluginFactory.h b/src/sound/LADSPAPluginFactory.h
new file mode 100644
index 0000000..a5ec368
--- /dev/null
+++ b/src/sound/LADSPAPluginFactory.h
@@ -0,0 +1,104 @@
+// -*- c-basic-offset: 4 -*-
+
+/*
+ Rosegarden
+ A sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <bownie@bownie.com>
+
+ 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 _LADSPA_PLUGIN_FACTORY_H_
+#define _LADSPA_PLUGIN_FACTORY_H_
+
+#ifdef HAVE_LADSPA
+
+#include "PluginFactory.h"
+#include <ladspa.h>
+
+#include <vector>
+#include <map>
+#include <set>
+#include <qstring.h>
+
+namespace Rosegarden
+{
+
+class LADSPAPluginInstance;
+
+class LADSPAPluginFactory : public PluginFactory
+{
+public:
+ virtual ~LADSPAPluginFactory();
+
+ virtual void discoverPlugins();
+
+ virtual const std::vector<QString> &getPluginIdentifiers() const;
+
+ virtual void enumeratePlugins(MappedObjectPropertyList &list);
+
+ virtual void populatePluginSlot(QString identifier, MappedPluginSlot &slot);
+
+ virtual RunnablePluginInstance *instantiatePlugin(QString identifier,
+ int instrumentId,
+ int position,
+ unsigned int sampleRate,
+ unsigned int blockSize,
+ unsigned int channels);
+
+ MappedObjectValue getPortMinimum(const LADSPA_Descriptor *, int port);
+ MappedObjectValue getPortMaximum(const LADSPA_Descriptor *, int port);
+ MappedObjectValue getPortDefault(const LADSPA_Descriptor *, int port);
+ int getPortDisplayHint(const LADSPA_Descriptor *, int port);
+
+protected:
+ LADSPAPluginFactory();
+ friend class PluginFactory;
+
+ virtual std::vector<QString> getPluginPath();
+
+#ifdef HAVE_LIBLRDF
+ virtual std::vector<QString> getLRDFPath(QString &baseUri);
+#endif
+
+ virtual void discoverPlugins(QString soName);
+ virtual void generateTaxonomy(QString uri, QString base);
+ virtual void generateFallbackCategories();
+
+ virtual void releasePlugin(RunnablePluginInstance *, QString);
+
+ virtual const LADSPA_Descriptor *getLADSPADescriptor(QString identifier);
+
+ void loadLibrary(QString soName);
+ void unloadLibrary(QString soName);
+ void unloadUnusedLibraries();
+
+ std::vector<QString> m_identifiers;
+
+ std::map<unsigned long, QString> m_taxonomy;
+ std::map<QString, QString> m_fallbackCategories;
+ std::map<unsigned long, std::map<int, float> > m_portDefaults;
+
+ std::set<RunnablePluginInstance *> m_instances;
+
+ typedef std::map<QString, void *> LibraryHandleMap;
+ LibraryHandleMap m_libraryHandles;
+};
+
+}
+
+#endif
+
+#endif
+
diff --git a/src/sound/LADSPAPluginInstance.cpp b/src/sound/LADSPAPluginInstance.cpp
new file mode 100644
index 0000000..e2b8890
--- /dev/null
+++ b/src/sound/LADSPAPluginInstance.cpp
@@ -0,0 +1,435 @@
+// -*- c-indentation-style:"stroustrup" c-basic-offset: 4 -*-
+/*
+ Rosegarden
+ A sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <bownie@bownie.com>
+
+ 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 <iostream>
+#include <cassert>
+
+#include "LADSPAPluginInstance.h"
+#include "LADSPAPluginFactory.h"
+
+#ifdef HAVE_LADSPA
+
+//#define DEBUG_LADSPA 1
+
+namespace Rosegarden
+{
+
+
+LADSPAPluginInstance::LADSPAPluginInstance(PluginFactory *factory,
+ InstrumentId instrument,
+ QString identifier,
+ int position,
+ unsigned long sampleRate,
+ size_t blockSize,
+ int idealChannelCount,
+ const LADSPA_Descriptor* descriptor) :
+ RunnablePluginInstance(factory, identifier),
+ m_instrument(instrument),
+ m_position(position),
+ m_instanceCount(0),
+ m_descriptor(descriptor),
+ m_blockSize(blockSize),
+ m_sampleRate(sampleRate),
+ m_latencyPort(0),
+ m_run(false),
+ m_bypassed(false)
+{
+ init(idealChannelCount);
+
+ m_inputBuffers = new sample_t * [m_instanceCount * m_audioPortsIn.size()];
+ m_outputBuffers = new sample_t * [m_instanceCount * m_audioPortsOut.size()];
+
+ for (size_t i = 0; i < m_instanceCount * m_audioPortsIn.size(); ++i) {
+ m_inputBuffers[i] = new sample_t[blockSize];
+ }
+ for (size_t i = 0; i < m_instanceCount * m_audioPortsOut.size(); ++i) {
+ m_outputBuffers[i] = new sample_t[blockSize];
+ }
+
+ m_ownBuffers = true;
+
+ instantiate(sampleRate);
+ if (isOK()) {
+ connectPorts();
+ activate();
+ }
+}
+
+LADSPAPluginInstance::LADSPAPluginInstance(PluginFactory *factory,
+ InstrumentId instrument,
+ QString identifier,
+ int position,
+ unsigned long sampleRate,
+ size_t blockSize,
+ sample_t **inputBuffers,
+ sample_t **outputBuffers,
+ const LADSPA_Descriptor* descriptor) :
+ RunnablePluginInstance(factory, identifier),
+ m_instrument(instrument),
+ m_position(position),
+ m_instanceCount(0),
+ m_descriptor(descriptor),
+ m_blockSize(blockSize),
+ m_inputBuffers(inputBuffers),
+ m_outputBuffers(outputBuffers),
+ m_ownBuffers(false),
+ m_sampleRate(sampleRate),
+ m_latencyPort(0),
+ m_run(false),
+ m_bypassed(false)
+{
+ init();
+
+ instantiate(sampleRate);
+ if (isOK()) {
+ connectPorts();
+ activate();
+ }
+}
+
+
+void
+LADSPAPluginInstance::init(int idealChannelCount)
+{
+#ifdef DEBUG_LADSPA
+ std::cerr << "LADSPAPluginInstance::init(" << idealChannelCount << "): plugin has "
+ << m_descriptor->PortCount << " ports" << std::endl;
+#endif
+
+ // Discover ports numbers and identities
+ //
+ for (unsigned long i = 0; i < m_descriptor->PortCount; ++i) {
+ if (LADSPA_IS_PORT_AUDIO(m_descriptor->PortDescriptors[i])) {
+ if (LADSPA_IS_PORT_INPUT(m_descriptor->PortDescriptors[i])) {
+#ifdef DEBUG_LADSPA
+ std::cerr << "LADSPAPluginInstance::init: port " << i << " is audio in" << std::endl;
+#endif
+
+ m_audioPortsIn.push_back(i);
+ } else {
+#ifdef DEBUG_LADSPA
+ std::cerr << "LADSPAPluginInstance::init: port " << i << " is audio out" << std::endl;
+#endif
+
+ m_audioPortsOut.push_back(i);
+ }
+ } else
+ if (LADSPA_IS_PORT_CONTROL(m_descriptor->PortDescriptors[i])) {
+ if (LADSPA_IS_PORT_INPUT(m_descriptor->PortDescriptors[i])) {
+#ifdef DEBUG_LADSPA
+ std::cerr << "LADSPAPluginInstance::init: port " << i << " is control in" << std::endl;
+#endif
+
+ LADSPA_Data *data = new LADSPA_Data(0.0);
+ m_controlPortsIn.push_back(
+ std::pair<unsigned long, LADSPA_Data*>(i, data));
+ } else {
+#ifdef DEBUG_LADSPA
+ std::cerr << "LADSPAPluginInstance::init: port " << i << " is control out" << std::endl;
+#endif
+
+ LADSPA_Data *data = new LADSPA_Data(0.0);
+ m_controlPortsOut.push_back(
+ std::pair<unsigned long, LADSPA_Data*>(i, data));
+ if (!strcmp(m_descriptor->PortNames[i], "latency") ||
+ !strcmp(m_descriptor->PortNames[i], "_latency")) {
+#ifdef DEBUG_LADSPA
+ std::cerr << "Wooo! We have a latency port!" << std::endl;
+#endif
+
+ m_latencyPort = data;
+ }
+ }
+ }
+#ifdef DEBUG_LADSPA
+ else
+ std::cerr << "LADSPAPluginInstance::init - "
+ << "unrecognised port type" << std::endl;
+#endif
+
+ }
+
+ m_instanceCount = 1;
+
+ if (idealChannelCount > 0) {
+ if (m_audioPortsIn.size() == 1) {
+ // mono plugin: duplicate it if need be
+ m_instanceCount = idealChannelCount;
+ }
+ }
+}
+
+size_t
+LADSPAPluginInstance::getLatency()
+{
+ if (m_latencyPort) {
+ if (!m_run) {
+ for (int i = 0; i < getAudioInputCount(); ++i) {
+ for (int j = 0; j < m_blockSize; ++j) {
+ m_inputBuffers[i][j] = 0.f;
+ }
+ }
+ run(RealTime::zeroTime);
+ }
+ return *m_latencyPort;
+ }
+ return 0;
+}
+
+void
+LADSPAPluginInstance::silence()
+{
+ if (isOK()) {
+ deactivate();
+ activate();
+ }
+}
+
+void
+LADSPAPluginInstance::setIdealChannelCount(size_t channels)
+{
+ if (m_audioPortsIn.size() != 1 || channels == m_instanceCount) {
+ silence();
+ return ;
+ }
+
+ if (isOK()) {
+ deactivate();
+ }
+
+ //!!! don't we need to reallocate inputBuffers and outputBuffers?
+
+ cleanup();
+ m_instanceCount = channels;
+ instantiate(m_sampleRate);
+ if (isOK()) {
+ connectPorts();
+ activate();
+ }
+}
+
+
+LADSPAPluginInstance::~LADSPAPluginInstance()
+{
+#ifdef DEBUG_LADSPA
+ std::cerr << "LADSPAPluginInstance::~LADSPAPluginInstance" << std::endl;
+#endif
+
+ if (m_instanceHandles.size() != 0) { // "isOK()"
+ deactivate();
+ }
+
+ cleanup();
+
+ for (unsigned int i = 0; i < m_controlPortsIn.size(); ++i)
+ delete m_controlPortsIn[i].second;
+
+ for (unsigned int i = 0; i < m_controlPortsOut.size(); ++i)
+ delete m_controlPortsOut[i].second;
+
+ m_controlPortsIn.clear();
+ m_controlPortsOut.clear();
+
+ if (m_ownBuffers) {
+ for (size_t i = 0; i < m_audioPortsIn.size(); ++i) {
+ delete[] m_inputBuffers[i];
+ }
+ for (size_t i = 0; i < m_audioPortsOut.size(); ++i) {
+ delete[] m_outputBuffers[i];
+ }
+
+ delete[] m_inputBuffers;
+ delete[] m_outputBuffers;
+ }
+
+ m_audioPortsIn.clear();
+ m_audioPortsOut.clear();
+}
+
+
+void
+LADSPAPluginInstance::instantiate(unsigned long sampleRate)
+{
+#ifdef DEBUG_LADSPA
+ std::cout << "LADSPAPluginInstance::instantiate - plugin unique id = "
+ << m_descriptor->UniqueID << std::endl;
+#endif
+
+ if (!m_descriptor)
+ return ;
+
+ if (!m_descriptor->instantiate) {
+ std::cerr << "Bad plugin: plugin id " << m_descriptor->UniqueID
+ << ":" << m_descriptor->Label
+ << " has no instantiate method!" << std::endl;
+ return ;
+ }
+
+ for (int i = 0; i < m_instanceCount; ++i) {
+ m_instanceHandles.push_back
+ (m_descriptor->instantiate(m_descriptor, sampleRate));
+ }
+}
+
+void
+LADSPAPluginInstance::activate()
+{
+ if (!m_descriptor || !m_descriptor->activate)
+ return ;
+
+ for (std::vector<LADSPA_Handle>::iterator hi = m_instanceHandles.begin();
+ hi != m_instanceHandles.end(); ++hi) {
+ m_descriptor->activate(*hi);
+ }
+}
+
+void
+LADSPAPluginInstance::connectPorts()
+{
+ if (!m_descriptor || !m_descriptor->connect_port)
+ return ;
+
+ assert(sizeof(LADSPA_Data) == sizeof(float));
+ assert(sizeof(sample_t) == sizeof(float));
+
+ int inbuf = 0, outbuf = 0;
+
+ for (std::vector<LADSPA_Handle>::iterator hi = m_instanceHandles.begin();
+ hi != m_instanceHandles.end(); ++hi) {
+
+ for (unsigned int i = 0; i < m_audioPortsIn.size(); ++i) {
+ m_descriptor->connect_port(*hi,
+ m_audioPortsIn[i],
+ (LADSPA_Data *)m_inputBuffers[inbuf]);
+ ++inbuf;
+ }
+
+ for (unsigned int i = 0; i < m_audioPortsOut.size(); ++i) {
+ m_descriptor->connect_port(*hi,
+ m_audioPortsOut[i],
+ (LADSPA_Data *)m_outputBuffers[outbuf]);
+ ++outbuf;
+ }
+
+ // If there is more than one instance, they all share the same
+ // control port ins (and outs, for the moment, because we
+ // don't actually do anything with the outs anyway -- but they
+ // do have to be connected as the plugin can't know if they're
+ // not and will write to them anyway).
+
+ for (unsigned int i = 0; i < m_controlPortsIn.size(); ++i) {
+ m_descriptor->connect_port(*hi,
+ m_controlPortsIn[i].first,
+ m_controlPortsIn[i].second);
+ }
+
+ for (unsigned int i = 0; i < m_controlPortsOut.size(); ++i) {
+ m_descriptor->connect_port(*hi,
+ m_controlPortsOut[i].first,
+ m_controlPortsOut[i].second);
+ }
+ }
+}
+
+void
+LADSPAPluginInstance::setPortValue(unsigned int portNumber, float value)
+{
+ for (unsigned int i = 0; i < m_controlPortsIn.size(); ++i) {
+ if (m_controlPortsIn[i].first == portNumber) {
+ LADSPAPluginFactory *f = dynamic_cast<LADSPAPluginFactory *>(m_factory);
+ if (f) {
+ if (value < f->getPortMinimum(m_descriptor, portNumber)) {
+ value = f->getPortMinimum(m_descriptor, portNumber);
+ }
+ if (value > f->getPortMaximum(m_descriptor, portNumber)) {
+ value = f->getPortMaximum(m_descriptor, portNumber);
+ }
+ }
+ (*m_controlPortsIn[i].second) = value;
+ }
+ }
+}
+
+float
+LADSPAPluginInstance::getPortValue(unsigned int portNumber)
+{
+ for (unsigned int i = 0; i < m_controlPortsIn.size(); ++i) {
+ if (m_controlPortsIn[i].first == portNumber) {
+ return (*m_controlPortsIn[i].second);
+ }
+ }
+
+ return 0.0;
+}
+
+void
+LADSPAPluginInstance::run(const RealTime &)
+{
+ if (!m_descriptor || !m_descriptor->run)
+ return ;
+
+ for (std::vector<LADSPA_Handle>::iterator hi = m_instanceHandles.begin();
+ hi != m_instanceHandles.end(); ++hi) {
+ m_descriptor->run(*hi, m_blockSize);
+ }
+
+ m_run = true;
+}
+
+void
+LADSPAPluginInstance::deactivate()
+{
+ if (!m_descriptor || !m_descriptor->deactivate)
+ return ;
+
+ for (std::vector<LADSPA_Handle>::iterator hi = m_instanceHandles.begin();
+ hi != m_instanceHandles.end(); ++hi) {
+ m_descriptor->deactivate(*hi);
+ }
+}
+
+void
+LADSPAPluginInstance::cleanup()
+{
+ if (!m_descriptor)
+ return ;
+
+ if (!m_descriptor->cleanup) {
+ std::cerr << "Bad plugin: plugin id " << m_descriptor->UniqueID
+ << ":" << m_descriptor->Label
+ << " has no cleanup method!" << std::endl;
+ return ;
+ }
+
+ for (std::vector<LADSPA_Handle>::iterator hi = m_instanceHandles.begin();
+ hi != m_instanceHandles.end(); ++hi) {
+ m_descriptor->cleanup(*hi);
+ }
+
+ m_instanceHandles.clear();
+}
+
+
+
+}
+
+#endif // HAVE_LADSPA
+
+
diff --git a/src/sound/LADSPAPluginInstance.h b/src/sound/LADSPAPluginInstance.h
new file mode 100644
index 0000000..9654cfb
--- /dev/null
+++ b/src/sound/LADSPAPluginInstance.h
@@ -0,0 +1,137 @@
+// -*- c-indentation-style:"stroustrup" c-basic-offset: 4 -*-
+
+/*
+ Rosegarden
+ A sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <bownie@bownie.com>
+
+ 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 <vector>
+#include <set>
+#include <qstring.h>
+#include "Instrument.h"
+
+#ifndef _LADSPAPLUGININSTANCE_H_
+#define _LADSPAPLUGININSTANCE_H_
+
+#ifdef HAVE_LADSPA
+
+#include <ladspa.h>
+#include "RunnablePluginInstance.h"
+
+namespace Rosegarden
+{
+
+// LADSPA plugin instance. LADSPA is a variable block size API, but
+// for one reason and another it's more convenient to use a fixed
+// block size in this wrapper.
+//
+class LADSPAPluginInstance : public RunnablePluginInstance
+{
+public:
+ virtual ~LADSPAPluginInstance();
+
+ virtual bool isOK() const { return m_instanceHandles.size() != 0; }
+
+ InstrumentId getInstrument() const { return m_instrument; }
+ virtual QString getIdentifier() const { return m_identifier; }
+ int getPosition() const { return m_position; }
+
+ virtual void run(const RealTime &rt);
+
+ virtual void setPortValue(unsigned int portNumber, float value);
+ virtual float getPortValue(unsigned int portNumber);
+
+ virtual size_t getBufferSize() { return m_blockSize; }
+ virtual size_t getAudioInputCount() { return m_instanceCount * m_audioPortsIn.size(); }
+ virtual size_t getAudioOutputCount() { return m_instanceCount * m_audioPortsOut.size(); }
+ virtual sample_t **getAudioInputBuffers() { return m_inputBuffers; }
+ virtual sample_t **getAudioOutputBuffers() { return m_outputBuffers; }
+
+ virtual bool isBypassed() const { return m_bypassed; }
+ virtual void setBypassed(bool bypassed) { m_bypassed = bypassed; }
+
+ virtual size_t getLatency();
+
+ virtual void silence();
+ virtual void setIdealChannelCount(size_t channels); // may re-instantiate
+
+protected:
+ // To be constructed only by LADSPAPluginFactory
+ friend class LADSPAPluginFactory;
+
+ // Constructor that creates the buffers internally
+ //
+ LADSPAPluginInstance(PluginFactory *factory,
+ InstrumentId instrument,
+ QString identifier,
+ int position,
+ unsigned long sampleRate,
+ size_t blockSize,
+ int idealChannelCount,
+ const LADSPA_Descriptor* descriptor);
+
+ // Constructor that uses shared buffers
+ //
+ LADSPAPluginInstance(PluginFactory *factory,
+ InstrumentId instrument,
+ QString identifier,
+ int position,
+ unsigned long sampleRate,
+ size_t blockSize,
+ sample_t **inputBuffers,
+ sample_t **outputBuffers,
+ const LADSPA_Descriptor* descriptor);
+
+ void init(int idealChannelCount = 0);
+ void instantiate(unsigned long sampleRate);
+ void cleanup();
+ void activate();
+ void deactivate();
+
+ // Connection of data (and behind the scenes control) ports
+ //
+ void connectPorts();
+
+ InstrumentId m_instrument;
+ int m_position;
+ std::vector<LADSPA_Handle> m_instanceHandles;
+ size_t m_instanceCount;
+ const LADSPA_Descriptor *m_descriptor;
+
+ std::vector<std::pair<unsigned long, LADSPA_Data*> > m_controlPortsIn;
+ std::vector<std::pair<unsigned long, LADSPA_Data*> > m_controlPortsOut;
+
+ std::vector<int> m_audioPortsIn;
+ std::vector<int> m_audioPortsOut;
+
+ size_t m_blockSize;
+ sample_t **m_inputBuffers;
+ sample_t **m_outputBuffers;
+ bool m_ownBuffers;
+ size_t m_sampleRate;
+ float *m_latencyPort;
+ bool m_run;
+
+ bool m_bypassed;
+};
+
+}
+
+#endif // HAVE_LADSPA
+
+#endif // _LADSPAPLUGININSTANCE_H_
+
diff --git a/src/sound/MP3AudioFile.cpp b/src/sound/MP3AudioFile.cpp
new file mode 100644
index 0000000..700169f
--- /dev/null
+++ b/src/sound/MP3AudioFile.cpp
@@ -0,0 +1,329 @@
+// -*- c-basic-offset: 4 -*-
+
+/*
+ Rosegarden
+ A sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <bownie@bownie.com>
+
+ 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 "MP3AudioFile.h"
+
+#ifdef HAVE_LIBMAD
+
+#include <mad.h>
+
+namespace Rosegarden
+{
+
+
+/*
+ * This is a private message structure. A generic pointer to this structure
+ * is passed to each of the callback functions. Put here any data you need
+ * to access from within the callbacks.
+ */
+
+struct player
+{
+ unsigned char const *start;
+ unsigned long length;
+ //int default_driver;
+ //ao_device *device;
+ //ao_sample_format format;
+ //class SoundTouch *touch;
+};
+
+
+
+MP3AudioFile::MP3AudioFile(const unsigned int &id,
+ const std::string &name,
+ const std::string &fileName):
+ AudioFile(id, name, fileName)
+{
+ m_type = MP3;
+}
+
+
+MP3AudioFile::MP3AudioFile(const std::string &fileName,
+ unsigned int /*channels*/,
+ unsigned int /*sampleRate*/,
+ unsigned int /*bytesPerSecond*/,
+ unsigned int /*bytesPerSample*/,
+ unsigned int /*bitsPerSample*/):
+ AudioFile(0, std::string(""), fileName)
+{
+ m_type = MP3;
+}
+
+
+MP3AudioFile::~MP3AudioFile()
+{}
+
+bool
+MP3AudioFile::open()
+{
+ // if already open
+ if (m_inFile && (*m_inFile))
+ return true;
+
+ m_inFile = new std::ifstream(m_fileName.c_str(),
+ std::ios::in | std::ios::binary);
+
+ if (!(*m_inFile)) {
+ m_type = UNKNOWN;
+ return false;
+ }
+
+ // Get the file size and store it for comparison later
+ m_fileSize = m_fileInfo->size();
+
+ try {
+ parseHeader();
+ } catch (BadSoundFileException s) {
+ throw(s);
+ }
+
+ return true;
+}
+
+bool
+MP3AudioFile::write()
+{
+ return false;
+}
+
+void
+MP3AudioFile::close()
+{}
+
+void
+MP3AudioFile::parseHeader()
+{
+ const std::string MP3_TAG("TAG");
+ if (m_inFile == 0)
+ return ;
+
+ // store size conveniently
+ m_fileSize = m_fileInfo->size();
+
+ if (m_fileSize == 0) {
+ std::string mess = std::string("\"") + m_fileName +
+ std::string("\" is empty - invalid MP3 file");
+ throw(mess);
+ }
+
+ // seek to beginning
+ m_inFile->seekg(0, std::ios::beg);
+
+ // get some header information
+ //
+ const int bufferLength = 3096;
+ std::string hS = getBytes(bufferLength);
+ bool foundMP3 = false;
+
+ for (unsigned int i = 0; i < hS.length() - 1; ++i) {
+ if ((hS[i] & 0xff) == 0xff && (hS[i + 1] & 0xe0) == 0xe0) {
+ foundMP3 = true;
+ break;
+ }
+ }
+
+ if (foundMP3 == false || (int)hS.length() < bufferLength) {
+ std::string mess = std::string("\"") + m_fileName +
+ std::string("\" doesn't appear to be a valid MP3 file");
+ throw(mess);
+ }
+
+ // guess most likely values - these are reset during decoding
+ m_channels = 2;
+ m_sampleRate = 44100;
+
+ mad_synth synth;
+ mad_frame frame;
+ mad_stream stream;
+
+ mad_synth_init(&synth);
+ mad_stream_init(&stream);
+ mad_frame_init(&frame);
+
+ /*
+ mad_stream_buffer(&stream, hS.data(), hS.length());
+
+ if (mad_header_decode(&frame.header, &stream) == -1)
+ {
+ throw("Can't decode header");
+ }
+
+ mad_frame_decode(&frame, &stream);
+
+ m_sampleRate = frame.header.samplerate;
+
+ mad_synth_frame(&synth, &frame);
+ struct mad_pcm *pcm = &synth.pcm;
+
+ m_channels = pcm->channels;
+ */
+
+ /*
+ struct player player;
+ struct mad_decoder decoder;
+ struct stat stat;
+ void *fdm;
+ int result;
+
+ if (fstat(fd, &stat) == -1 ||
+ stat.st_size == 0)
+ return 0;
+
+ fdm = mmap(0, stat.st_size, PROT_READ, MAP_SHARED, fd, 0);
+ if (fdm == MAP_FAILED) {
+ fprintf(stderr, "mmap failed, aborting...\n");
+ return 0;
+ }
+
+ player.start = (unsigned char *)fdm;
+ player.length = stat.st_size;
+ player.default_driver = ao_default_driver_id();
+ player.device = NULL;
+ player.touch = new SoundTouch;
+ player.touch->setTempo(tempo);
+ player.touch->setPitch(pitch);
+ mad_decoder_init(&decoder, &player,
+ input, 0 , 0 , process_output,
+ decode_error, 0);
+
+ result = mad_decoder_run(&decoder, MAD_DECODER_MODE_SYNC);
+ mad_decoder_finish(&decoder);
+ delete player.touch;
+ ao_close(player.device);
+ if (munmap((void *)player.start, stat.st_size) == -1)
+ return 4;
+
+ return result;
+ */
+}
+
+std::streampos
+MP3AudioFile::getDataOffset()
+{
+ return 0;
+}
+
+bool
+MP3AudioFile::scanTo(const RealTime & /*time*/)
+{
+ return false;
+}
+
+bool
+MP3AudioFile::scanTo(std::ifstream * /*file*/, const RealTime & /*time*/)
+{
+ return false;
+}
+
+
+// Scan forward in a file by a certain amount of time
+//
+bool
+MP3AudioFile::scanForward(const RealTime & /*time*/)
+{
+ return false;
+}
+
+bool
+MP3AudioFile::scanForward(std::ifstream * /*file*/, const RealTime & /*time*/)
+{
+ return false;
+}
+
+
+// Return a number of samples - caller will have to
+// de-interleave n-channel samples themselves.
+//
+std::string
+MP3AudioFile::getSampleFrames(std::ifstream * /*file*/,
+ unsigned int /*frames*/)
+{
+ return "";
+}
+
+unsigned int
+MP3AudioFile::getSampleFrames(std::ifstream * /*file*/,
+ char * /* buf */,
+ unsigned int /*frames*/)
+{
+ return 0;
+}
+
+std::string
+MP3AudioFile::getSampleFrames(unsigned int /*frames*/)
+{
+ return "";
+}
+
+
+// Return a number of (possibly) interleaved samples
+// over a time slice from current file pointer position.
+//
+std::string
+MP3AudioFile::getSampleFrameSlice(std::ifstream * /*file*/,
+ const RealTime & /*time*/)
+{
+ return "";
+}
+
+std::string
+MP3AudioFile::getSampleFrameSlice(const RealTime & /*time*/)
+{
+ return "";
+}
+
+
+// Append a string of samples to an already open (for writing)
+// audio file.
+//
+bool
+MP3AudioFile::appendSamples(const std::string & /*buffer*/)
+{
+ return false;
+}
+
+bool
+MP3AudioFile::appendSamples(const char * /*buffer*/, unsigned int)
+{
+ return false;
+}
+
+
+// Get the length of the sample in Seconds/Microseconds
+//
+RealTime
+MP3AudioFile::getLength()
+{
+ return RealTime(0, 0);
+}
+
+void
+MP3AudioFile::printStats()
+{}
+
+
+
+
+
+}
+
+#endif // HAVE_LIBMAD
diff --git a/src/sound/MP3AudioFile.h b/src/sound/MP3AudioFile.h
new file mode 100644
index 0000000..2423cd9
--- /dev/null
+++ b/src/sound/MP3AudioFile.h
@@ -0,0 +1,128 @@
+// -*- c-basic-offset: 4 -*-
+
+/*
+ Rosegarden
+ A sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <bownie@bownie.com>
+
+ 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 _MP3AUDIOFILE_H_
+#define _MP3AUDIOFILE_H_
+
+#ifdef HAVE_LIBMAD
+
+#include "AudioFile.h"
+
+namespace Rosegarden
+{
+
+class MP3AudioFile : public AudioFile
+{
+public:
+ MP3AudioFile(const unsigned int &id,
+ const std::string &name,
+ const std::string &fileName);
+
+ MP3AudioFile(const std::string &fileName,
+ unsigned int channels,
+ unsigned int sampleRate,
+ unsigned int bytesPerSecond,
+ unsigned int bytesPerSample,
+ unsigned int bitsPerSample);
+
+ ~MP3AudioFile();
+
+ // Override these methods for the WAV
+ //
+ virtual bool open();
+ virtual bool write();
+ virtual void close();
+
+ // Show the information we have on this file
+ //
+ virtual void printStats();
+
+ // Get all header information
+ //
+ void parseHeader();
+
+ // Offset to start of sample data
+ //
+ virtual std::streampos getDataOffset();
+
+ // Peak file name
+ //
+ virtual std::string getPeakFilename()
+ { return (m_fileName + std::string(".pk")); }
+
+ // scan time was valid and successful.
+ //
+ virtual bool scanTo(const RealTime &time);
+ virtual bool scanTo(std::ifstream *file, const RealTime &time);
+
+ // Scan forward in a file by a certain amount of time
+ //
+ virtual bool scanForward(const RealTime &time);
+ virtual bool scanForward(std::ifstream *file, const RealTime &time);
+
+ // Return a number of samples - caller will have to
+ // de-interleave n-channel samples themselves.
+ //
+ virtual std::string getSampleFrames(std::ifstream *file,
+ unsigned int frames);
+ virtual unsigned int getSampleFrames(std::ifstream *file,
+ char *buf,
+ unsigned int frames);
+ virtual std::string getSampleFrames(unsigned int frames);
+
+ // Return a number of (possibly) interleaved samples
+ // over a time slice from current file pointer position.
+ //
+ virtual std::string getSampleFrameSlice(std::ifstream *file,
+ const RealTime &time);
+ virtual std::string getSampleFrameSlice(const RealTime &time);
+
+ // Append a string of samples to an already open (for writing)
+ // audio file.
+ //
+ virtual bool appendSamples(const std::string &buffer);
+ virtual bool appendSamples(const char *buffer, unsigned int samples);
+
+ // Get the length of the sample in Seconds/Microseconds
+ //
+ virtual RealTime getLength();
+
+ virtual unsigned int getBytesPerFrame() { return 0; }
+
+
+ //!!! NOT IMPLEMENTED YET
+ //
+ virtual bool decode(const unsigned char *sourceData,
+ size_t sourceBytes,
+ size_t targetSampleRate,
+ size_t targetChannels,
+ size_t targetFrames,
+ std::vector<float *> &targetData,
+ bool addToResultBuffers = false) { return false; }
+
+};
+
+}
+
+#endif // HAVE_LIBMAD
+
+#endif // _MP3AUDIOFILE_H_
+
diff --git a/src/sound/MappedCommon.h b/src/sound/MappedCommon.h
new file mode 100644
index 0000000..5ef5487
--- /dev/null
+++ b/src/sound/MappedCommon.h
@@ -0,0 +1,68 @@
+// -*- c-indentation-style:"stroustrup" c-basic-offset: 4 -*-
+
+/*
+ Rosegarden
+ A sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <bownie@bownie.com>
+
+ 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 _MAPPEDCOMMON_H_
+#define _MAPPEDCOMMON_H_
+
+// Some Mapped types that gui and sound libraries use to communicate
+// plugin and Studio information. Putting them here so we can change
+// MappedStudio regularly without having to rebuild the gui.
+//
+#include <vector>
+
+#include <qstring.h>
+#include <qdatastream.h>
+
+namespace Rosegarden
+{
+
+typedef int MappedObjectId;
+typedef QString MappedObjectProperty;
+typedef float MappedObjectValue;
+
+// typedef QValueVector<MappedObjectProperty> MappedObjectPropertyList;
+// replaced with a std::vector<> for Qt2 compatibility
+
+typedef std::vector<MappedObjectId> MappedObjectIdList;
+typedef std::vector<MappedObjectProperty> MappedObjectPropertyList;
+typedef std::vector<MappedObjectValue> MappedObjectValueList;
+
+// The direction in which a port operates.
+//
+typedef enum
+{
+ ReadOnly, // input port
+ WriteOnly, // output port
+ Duplex
+} PortDirection;
+
+QDataStream& operator>>(QDataStream& s, MappedObjectIdList&);
+QDataStream& operator<<(QDataStream&, const MappedObjectIdList&);
+
+QDataStream& operator>>(QDataStream& s, MappedObjectPropertyList&);
+QDataStream& operator<<(QDataStream&, const MappedObjectPropertyList&);
+
+QDataStream& operator>>(QDataStream& s, MappedObjectValueList&);
+QDataStream& operator<<(QDataStream&, const MappedObjectValueList&);
+
+}
+
+#endif // _MAPPEDCOMMON_H_
diff --git a/src/sound/MappedComposition.cpp b/src/sound/MappedComposition.cpp
new file mode 100644
index 0000000..975fccf
--- /dev/null
+++ b/src/sound/MappedComposition.cpp
@@ -0,0 +1,216 @@
+// -*- c-indentation-style:"stroustrup" c-basic-offset: 4 -*-
+
+/*
+ Rosegarden
+ A sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <bownie@bownie.com>
+
+ 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 <qdatastream.h>
+#include "MappedComposition.h"
+#include "MappedEvent.h"
+#include "SegmentPerformanceHelper.h"
+#include <iostream>
+
+namespace Rosegarden
+{
+
+using std::cerr;
+using std::cout;
+using std::endl;
+
+MappedComposition::~MappedComposition()
+{
+ clear();
+}
+
+// copy constructor
+MappedComposition::MappedComposition(const MappedComposition &mC):
+ std::multiset<MappedEvent *, MappedEvent::MappedEventCmp>()
+{
+ clear();
+
+ // deep copy
+ for (MappedComposition::const_iterator it = mC.begin(); it != mC.end(); it++)
+ this->insert(new MappedEvent(**it));
+
+}
+
+// Turn a MappedComposition into a QDataStream - MappedEvents can
+// stream themselves.
+//
+QDataStream&
+operator<<(QDataStream &dS, MappedComposition *mC)
+{
+ dS << int(mC->size());
+
+ for (MappedCompositionIterator it = mC->begin(); it != mC->end(); ++it )
+ dS << (*it);
+
+ return dS;
+}
+
+
+QDataStream&
+operator<<(QDataStream &dS, const MappedComposition &mC)
+{
+ dS << int(mC.size());
+
+ for (MappedComposition::const_iterator it = mC.begin(); it != mC.end(); ++it )
+ dS << (*it);
+
+ return dS;
+}
+
+
+// Turn a QDataStream into a MappedComposition
+//
+QDataStream&
+operator>>(QDataStream &dS, MappedComposition *mC)
+{
+ int sliceSize;
+ MappedEvent *mE;
+
+ dS >> sliceSize;
+
+ while (!dS.atEnd() && sliceSize) {
+ mE = new MappedEvent();
+ dS >> mE;
+
+ try {
+ mC->insert(mE);
+ } catch (...) {
+ ;
+ }
+
+ sliceSize--;
+
+ }
+
+#ifdef DEBUG_MAPPEDCOMPOSITION
+ if (sliceSize) {
+ cerr << "operator>> - wrong number of events received" << endl;
+ }
+#endif
+
+ return dS;
+}
+
+QDataStream&
+operator>>(QDataStream &dS, MappedComposition &mC)
+{
+ int sliceSize;
+ MappedEvent *mE;
+
+ dS >> sliceSize;
+
+ while (!dS.atEnd() && sliceSize) {
+ mE = new MappedEvent();
+
+ dS >> mE;
+
+ try {
+ mC.insert(mE);
+ } catch (...) {
+ ;
+ }
+
+ sliceSize--;
+
+ }
+
+#ifdef DEBUG_MAPPEDCOMPOSITION
+ if (sliceSize) {
+ cerr << "operator>> - wrong number of events received" << endl;
+ }
+#endif
+
+
+ return dS;
+}
+
+// Move the start time of this MappedComposition and all its events.
+// Actually - we have a special case for audio events at the moment..
+//
+//
+void
+MappedComposition::moveStartTime(const RealTime &mT)
+{
+ MappedCompositionIterator it;
+
+ for (it = this->begin(); it != this->end(); ++it) {
+ // Reset start time and duration
+ //
+ (*it)->setEventTime((*it)->getEventTime() + mT);
+ (*it)->setDuration((*it)->getDuration() - mT);
+
+ // For audio adjust the start index
+ //
+ if ((*it)->getType() == MappedEvent::Audio)
+ (*it)->setAudioStartMarker((*it)->getAudioStartMarker() + mT);
+ }
+
+ m_startTime = m_startTime + mT;
+ m_endTime = m_endTime + mT;
+
+}
+
+
+// Concatenate MappedComposition
+//
+MappedComposition&
+MappedComposition::operator+(const MappedComposition &mC)
+{
+ for (MappedComposition::const_iterator it = mC.begin(); it != mC.end(); it++)
+ this->insert(new MappedEvent(**it)); // deep copy
+
+ return *this;
+}
+
+// Assign (clear and deep copy)
+//
+MappedComposition&
+MappedComposition::operator=(const MappedComposition &mC)
+{
+ if (&mC == this)
+ return * this;
+
+ clear();
+
+ // deep copy
+ for (MappedComposition::const_iterator it = mC.begin(); it != mC.end(); it++)
+ this->insert(new MappedEvent(**it));
+
+ return *this;
+}
+
+void
+MappedComposition::clear()
+{
+ // Only clear if the events aren't persistent
+ //
+ for (MappedCompositionIterator it = this->begin(); it != this->end(); it++)
+ if (!(*it)->isPersistent())
+ delete (*it);
+
+ this->erase(this->begin(), this->end());
+}
+
+
+
+}
+
+
diff --git a/src/sound/MappedComposition.h b/src/sound/MappedComposition.h
new file mode 100644
index 0000000..bfa7c05
--- /dev/null
+++ b/src/sound/MappedComposition.h
@@ -0,0 +1,93 @@
+// -*- c-indentation-style:"stroustrup" c-basic-offset: 4 -*-
+
+/*
+ Rosegarden
+ A sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <bownie@bownie.com>
+
+ 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 _MAPPEDCOMPOSITION_H_
+#define _MAPPEDCOMPOSITION_H_
+
+
+// MappedComposition is used with MappedEvent to create a sequence
+// of MIDI ready events ready for playing. The QDataStream operators
+// are a necessary part of the DCOP transmission process allowing
+// the whole class to be serialized. The core application is sent
+// a request specifying a time slice between given start and end
+// points which it fills with MappedEvents which are cut down
+// (sequencer suitable) versions of the core Events.
+//
+
+#include <Composition.h>
+#include "MappedEvent.h"
+#include <set>
+#include <qdatastream.h>
+
+namespace Rosegarden
+{
+
+class MappedComposition : public std::multiset<MappedEvent *,
+ MappedEvent::MappedEventCmp>
+{
+public:
+ MappedComposition():m_startTime(0, 0), m_endTime(0, 0) {;}
+
+ MappedComposition(const RealTime &sT,
+ const RealTime &eT):
+ m_startTime(sT), m_endTime(eT) {;}
+
+ MappedComposition(const MappedComposition &mC);
+
+ ~MappedComposition();
+
+ const RealTime getStartTime() const { return m_startTime; }
+ const RealTime getEndTime() const { return m_endTime; }
+ void setStartTime(const RealTime &sT) { m_startTime = sT; }
+ void setEndTime(const RealTime &eT) { m_endTime = eT; }
+
+ // When we're looping we want to be able to move the start
+ // time of MappedEvents around in this container
+ //
+ void moveStartTime(const RealTime &mT);
+
+ MappedComposition& operator+(const MappedComposition &mC);
+ MappedComposition& operator=(const MappedComposition &mC);
+
+ // This section is used for serialising this class over DCOP
+ //
+ //
+ friend QDataStream& operator>>(QDataStream &dS, MappedComposition *mC);
+ friend QDataStream& operator<<(QDataStream &dS, MappedComposition *mC);
+ friend QDataStream& operator>>(QDataStream &dS, MappedComposition &mC);
+ friend QDataStream& operator<<(QDataStream &dS, const MappedComposition &mC);
+
+ // Clear out
+ void clear();
+
+private:
+ RealTime m_startTime;
+ RealTime m_endTime;
+
+};
+
+typedef std::multiset<MappedEvent *, MappedEvent::MappedEventCmp>::iterator MappedCompositionIterator;
+
+
+}
+
+#endif // _MAPPEDCOMPOSITION_H_
diff --git a/src/sound/MappedDevice.cpp b/src/sound/MappedDevice.cpp
new file mode 100644
index 0000000..619be2a
--- /dev/null
+++ b/src/sound/MappedDevice.cpp
@@ -0,0 +1,250 @@
+// -*- c-indentation-style:"stroustrup" c-basic-offset: 4 -*-
+
+/*
+ Rosegarden
+ A sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <bownie@bownie.com>
+
+ 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 "MappedDevice.h"
+#include "MappedInstrument.h"
+#include <iostream>
+
+namespace Rosegarden
+{
+
+MappedDevice::MappedDevice():
+ std::vector<MappedInstrument*>(),
+ m_id(Device::NO_DEVICE),
+ m_type(Device::Midi),
+ m_name("Unconfigured device"),
+ m_connection(""),
+ m_direction(MidiDevice::Play),
+ m_recording(false)
+{}
+
+MappedDevice::MappedDevice(DeviceId id,
+ Device::DeviceType type,
+ std::string name,
+ std::string connection):
+ std::vector<MappedInstrument*>(),
+ m_id(id),
+ m_type(type),
+ m_name(name),
+ m_connection(connection),
+ m_direction(MidiDevice::Play),
+ m_recording(false)
+{}
+
+MappedDevice::~MappedDevice()
+{}
+
+MappedDevice::MappedDevice(const MappedDevice &mD):
+ std::vector<MappedInstrument*>()
+{
+ clear();
+
+ for (MappedDeviceConstIterator it = mD.begin(); it != mD.end(); it++)
+ this->push_back(new MappedInstrument(**it));
+
+ m_id = mD.getId();
+ m_type = mD.getType();
+ m_name = mD.getName();
+ m_connection = mD.getConnection();
+ m_direction = mD.getDirection();
+ m_recording = mD.isRecording();
+}
+
+void
+MappedDevice::clear()
+{
+ MappedDeviceIterator it;
+
+ for (it = this->begin(); it != this->end(); it++)
+ delete (*it);
+
+ this->erase(this->begin(), this->end());
+}
+
+MappedDevice&
+MappedDevice::operator+(const MappedDevice &mD)
+{
+ for (MappedDeviceConstIterator it = mD.begin(); it != mD.end(); it++)
+ this->push_back(new MappedInstrument(**it));
+
+ return *this;
+}
+
+MappedDevice&
+MappedDevice::operator=(const MappedDevice &mD)
+{
+ if (&mD == this)
+ return * this;
+
+ clear();
+
+ for (MappedDeviceConstIterator it = mD.begin(); it != mD.end(); it++)
+ this->push_back(new MappedInstrument(**it));
+
+ m_id = mD.getId();
+ m_type = mD.getType();
+ m_name = mD.getName();
+ m_connection = mD.getConnection();
+ m_direction = mD.getDirection();
+ m_recording = mD.isRecording();
+
+ return *this;
+}
+
+
+QDataStream&
+operator>>(QDataStream &dS, MappedDevice *mD)
+{
+ int instruments = 0;
+ dS >> instruments;
+
+ MappedInstrument mI;
+ while (!dS.atEnd() && instruments) {
+ dS >> mI;
+ mD->push_back(new MappedInstrument(mI));
+ instruments--;
+ }
+
+ QString name;
+ unsigned int id, dType;
+ QString connection;
+ unsigned int direction;
+ unsigned int recording;
+
+ dS >> id;
+ dS >> dType;
+ dS >> name;
+ dS >> connection;
+ dS >> direction;
+ dS >> recording;
+ mD->setId(id);
+ mD->setType(Device::DeviceType(dType));
+ mD->setName(std::string(name.data()));
+ mD->setConnection(connection.data());
+ mD->setDirection(MidiDevice::DeviceDirection(direction));
+ mD->setRecording((bool)recording);
+
+#ifdef DEBUG_MAPPEDDEVICE
+
+ if (instruments) {
+ std::cerr << "MappedDevice::operator>> - "
+ << "wrong number of events received" << std::endl;
+ }
+#endif
+
+ return dS;
+}
+
+
+QDataStream&
+operator>>(QDataStream &dS, MappedDevice &mD)
+{
+ int instruments;
+ dS >> instruments;
+
+ MappedInstrument mI;
+
+ while (!dS.atEnd() && instruments) {
+ dS >> mI;
+ mD.push_back(new MappedInstrument(mI));
+ instruments--;
+ }
+
+ unsigned int id, dType;
+ QString name;
+ QString connection;
+ unsigned int direction;
+ unsigned int recording;
+
+ dS >> id;
+ dS >> dType;
+ dS >> name;
+ dS >> connection;
+ dS >> direction;
+ dS >> recording;
+ mD.setId(id);
+ mD.setType(Device::DeviceType(dType));
+ mD.setName(std::string(name.data()));
+ mD.setConnection(connection.data());
+ mD.setDirection(MidiDevice::DeviceDirection(direction));
+ mD.setRecording((bool)recording);
+
+#ifdef DEBUG_MAPPEDDEVICE
+
+ if (instruments) {
+ std::cerr << "MappedDevice::operator>> - "
+ << "wrong number of events received" << std::endl;
+ }
+#endif
+
+ return dS;
+}
+
+QDataStream&
+operator<<(QDataStream &dS, MappedDevice *mD)
+{
+ dS << (int)mD->size();
+
+ for (MappedDeviceIterator it = mD->begin(); it != mD->end(); it++)
+ dS << (*it);
+
+ dS << (unsigned int)(mD->getId());
+ dS << (int)(mD->getType());
+ dS << QString(mD->getName().c_str());
+ dS << QString(mD->getConnection().c_str());
+ dS << mD->getDirection();
+ dS << (unsigned int)(mD->isRecording());
+
+#ifdef DEBUG_MAPPEDDEVICE
+
+ std::cerr << "MappedDevice::operator>> - wrote \"" << mD->getConnection() << "\""
+ << std::endl;
+#endif
+
+ return dS;
+}
+
+QDataStream&
+operator<<(QDataStream &dS, const MappedDevice &mD)
+{
+ dS << (int)mD.size();
+
+ for (MappedDeviceConstIterator it = mD.begin(); it != mD.end(); it++)
+ dS << (*it);
+
+ dS << (unsigned int)(mD.getId());
+ dS << (int)(mD.getType());
+ dS << QString(mD.getName().c_str());
+ dS << QString(mD.getConnection().c_str());
+ dS << mD.getDirection();
+ dS << (unsigned int)(mD.isRecording());
+
+#ifdef DEBUG_MAPPEDDEVICE
+
+ std::cerr << "MappedDevice::operator>> - wrote \"" << mD.getConnection() << "\""
+ << std::endl;
+#endif
+
+ return dS;
+}
+
+}
+
diff --git a/src/sound/MappedDevice.h b/src/sound/MappedDevice.h
new file mode 100644
index 0000000..f53bb37
--- /dev/null
+++ b/src/sound/MappedDevice.h
@@ -0,0 +1,103 @@
+// -*- c-indentation-style:"stroustrup" c-basic-offset: 4 -*-
+
+/*
+ Rosegarden
+ A sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <bownie@bownie.com>
+
+ 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 <string>
+#include <vector>
+
+#include <qdatastream.h>
+
+#include "Device.h"
+#include "MidiDevice.h"
+#include "MappedCommon.h"
+
+#ifndef _MAPPEDDEVICE_H_
+#define _MAPPEDDEVICE_H_
+
+// A DCOP wrapper to get MappedInstruments across to the GUI
+//
+
+namespace Rosegarden
+{
+
+class MappedInstrument;
+
+class MappedDevice : public std::vector<MappedInstrument*>
+{
+public:
+ MappedDevice();
+ MappedDevice(DeviceId id,
+ Device::DeviceType type,
+ std::string name,
+ std::string connection = "");
+
+ MappedDevice(const MappedDevice &mD);
+ ~MappedDevice();
+
+ // Clear down
+ //
+ void clear();
+
+ MappedDevice& operator+(const MappedDevice &mD);
+ MappedDevice& operator=(const MappedDevice &mD);
+
+ friend QDataStream& operator>>(QDataStream &dS, MappedDevice *mD);
+ friend QDataStream& operator<<(QDataStream &dS, MappedDevice *mD);
+ friend QDataStream& operator>>(QDataStream &dS, MappedDevice &mD);
+ friend QDataStream& operator<<(QDataStream &dS, const MappedDevice &mD);
+
+ std::string getName() const { return m_name; }
+ void setName(const std::string &name) { m_name = name; }
+
+ DeviceId getId() const { return m_id; }
+ void setId(DeviceId id) { m_id = id; }
+
+ Device::DeviceType getType() const { return m_type; }
+ void setType(Device::DeviceType type) { m_type = type; }
+
+ std::string getConnection() const { return m_connection; }
+ void setConnection(std::string connection) { m_connection = connection; }
+
+ MidiDevice::DeviceDirection getDirection() const { return m_direction; }
+ void setDirection(MidiDevice::DeviceDirection direction) { m_direction = direction; }
+
+ bool isRecording() const { return m_recording; }
+ void setRecording(bool recording) { m_recording = recording; }
+
+protected:
+
+ DeviceId m_id;
+ Device::DeviceType m_type;
+ std::string m_name;
+ std::string m_connection;
+ MidiDevice::DeviceDirection m_direction;
+ bool m_recording;
+};
+
+typedef std::vector<MappedInstrument*>::const_iterator
+ MappedDeviceConstIterator;
+
+typedef std::vector<MappedInstrument*>::iterator
+ MappedDeviceIterator;
+
+}
+
+#endif // _MAPPEDDEVICE_H_
+
diff --git a/src/sound/MappedEvent.cpp b/src/sound/MappedEvent.cpp
new file mode 100644
index 0000000..9b4ccab
--- /dev/null
+++ b/src/sound/MappedEvent.cpp
@@ -0,0 +1,593 @@
+// -*- c-indentation-style:"stroustrup" c-basic-offset: 4 -*-
+
+/*
+ Rosegarden
+ A sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <bownie@bownie.com>
+
+ 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 <qdir.h>
+#include <qfile.h>
+#include <qfileinfo.h>
+
+#include <kstddirs.h>
+
+#include "MappedEvent.h"
+#include "BaseProperties.h"
+#include "Midi.h"
+#include "MidiTypes.h"
+
+#define DEBUG_MAPPEDEVENT 1
+
+namespace Rosegarden
+{
+
+MappedEvent::MappedEvent(InstrumentId id,
+ const Event &e,
+ const RealTime &eventTime,
+ const RealTime &duration):
+ m_trackId(0),
+ m_instrument(id),
+ m_type(MidiNote),
+ m_data1(0),
+ m_data2(0),
+ m_eventTime(eventTime),
+ m_duration(duration),
+ m_audioStartMarker(0, 0),
+ m_dataBlockId(0),
+ m_isPersistent(false),
+ m_runtimeSegmentId( -1),
+ m_autoFade(false),
+ m_fadeInTime(RealTime::zeroTime),
+ m_fadeOutTime(RealTime::zeroTime),
+ m_recordedChannel(0),
+ m_recordedDevice(0)
+
+{
+ try {
+
+ // For each event type, we set the properties in a particular
+ // order: first the type, then whichever of data1 and data2 fits
+ // less well with its default value. This way if one throws an
+ // exception for no data, we still have a good event with the
+ // defaults set.
+
+ if (e.isa(Note::EventType)) {
+ m_type = MidiNoteOneShot;
+ long v = MidiMaxValue;
+ e.get<Int>(BaseProperties::VELOCITY, v);
+ m_data2 = v;
+ m_data1 = e.get<Int>(BaseProperties::PITCH);
+ } else if (e.isa(PitchBend::EventType)) {
+ m_type = MidiPitchBend;
+ PitchBend pb(e);
+ m_data1 = pb.getMSB();
+ m_data2 = pb.getLSB();
+ } else if (e.isa(Controller::EventType)) {
+ m_type = MidiController;
+ Controller c(e);
+ m_data1 = c.getNumber();
+ m_data2 = c.getValue();
+ } else if (e.isa(ProgramChange::EventType)) {
+ m_type = MidiProgramChange;
+ ProgramChange pc(e);
+ m_data1 = pc.getProgram();
+ } else if (e.isa(KeyPressure::EventType)) {
+ m_type = MidiKeyPressure;
+ KeyPressure kp(e);
+ m_data1 = kp.getPitch();
+ m_data2 = kp.getPressure();
+ } else if (e.isa(ChannelPressure::EventType)) {
+ m_type = MidiChannelPressure;
+ ChannelPressure cp(e);
+ m_data1 = cp.getPressure();
+ } else if (e.isa(SystemExclusive::EventType)) {
+ m_type = MidiSystemMessage;
+ m_data1 = MIDI_SYSTEM_EXCLUSIVE;
+ SystemExclusive s(e);
+ std::string dataBlock = s.getRawData();
+ DataBlockRepository::getInstance()->registerDataBlockForEvent(dataBlock, this);
+ } else {
+ m_type = InvalidMappedEvent;
+ }
+ } catch (MIDIValueOutOfRange r) {
+
+#ifdef DEBUG_MAPPEDEVENT
+ std::cerr << "MIDI value out of range in MappedEvent ctor"
+ << std::endl;
+#else
+
+ ;
+#endif
+
+ } catch (Event::NoData d) {
+
+#ifdef DEBUG_MAPPEDEVENT
+ std::cerr << "Caught Event::NoData in MappedEvent ctor, message is:"
+ << std::endl << d.getMessage() << std::endl;
+#else
+
+ ;
+#endif
+
+ } catch (Event::BadType b) {
+
+#ifdef DEBUG_MAPPEDEVENT
+ std::cerr << "Caught Event::BadType in MappedEvent ctor, message is:"
+ << std::endl << b.getMessage() << std::endl;
+#else
+
+ ;
+#endif
+
+ } catch (SystemExclusive::BadEncoding e) {
+
+#ifdef DEBUG_MAPPEDEVENT
+ std::cerr << "Caught bad SysEx encoding in MappedEvent ctor"
+ << std::endl;
+#else
+
+ ;
+#endif
+
+ }
+}
+
+bool
+operator<(const MappedEvent &a, const MappedEvent &b)
+{
+ return a.getEventTime() < b.getEventTime();
+}
+
+MappedEvent&
+MappedEvent::operator=(const MappedEvent &mE)
+{
+ if (&mE == this)
+ return * this;
+
+ m_trackId = mE.getTrackId();
+ m_instrument = mE.getInstrument();
+ m_type = mE.getType();
+ m_data1 = mE.getData1();
+ m_data2 = mE.getData2();
+ m_eventTime = mE.getEventTime();
+ m_duration = mE.getDuration();
+ m_audioStartMarker = mE.getAudioStartMarker();
+ m_dataBlockId = mE.getDataBlockId();
+ m_runtimeSegmentId = mE.getRuntimeSegmentId();
+ m_autoFade = mE.isAutoFading();
+ m_fadeInTime = mE.getFadeInTime();
+ m_fadeOutTime = mE.getFadeOutTime();
+ m_recordedChannel = mE.getRecordedChannel();
+ m_recordedDevice = mE.getRecordedDevice();
+
+ return *this;
+}
+
+// Do we use this? It looks dangerous so just commenting it out - rwb
+//
+//const size_t MappedEvent::streamedSize = 12 * sizeof(unsigned int);
+
+QDataStream&
+operator<<(QDataStream &dS, MappedEvent *mE)
+{
+ dS << (unsigned int)mE->getTrackId();
+ dS << (unsigned int)mE->getInstrument();
+ dS << (unsigned int)mE->getType();
+ dS << (unsigned int)mE->getData1();
+ dS << (unsigned int)mE->getData2();
+ dS << (unsigned int)mE->getEventTime().sec;
+ dS << (unsigned int)mE->getEventTime().nsec;
+ dS << (unsigned int)mE->getDuration().sec;
+ dS << (unsigned int)mE->getDuration().nsec;
+ dS << (unsigned int)mE->getAudioStartMarker().sec;
+ dS << (unsigned int)mE->getAudioStartMarker().nsec;
+ dS << (unsigned long)mE->getDataBlockId();
+ dS << mE->getRuntimeSegmentId();
+ dS << (unsigned int)mE->isAutoFading();
+ dS << (unsigned int)mE->getFadeInTime().sec;
+ dS << (unsigned int)mE->getFadeInTime().nsec;
+ dS << (unsigned int)mE->getFadeOutTime().sec;
+ dS << (unsigned int)mE->getFadeOutTime().nsec;
+ dS << (unsigned int)mE->getRecordedChannel();
+ dS << (unsigned int)mE->getRecordedDevice();
+
+ return dS;
+}
+
+QDataStream&
+operator<<(QDataStream &dS, const MappedEvent &mE)
+{
+ dS << (unsigned int)mE.getTrackId();
+ dS << (unsigned int)mE.getInstrument();
+ dS << (unsigned int)mE.getType();
+ dS << (unsigned int)mE.getData1();
+ dS << (unsigned int)mE.getData2();
+ dS << (unsigned int)mE.getEventTime().sec;
+ dS << (unsigned int)mE.getEventTime().nsec;
+ dS << (unsigned int)mE.getDuration().sec;
+ dS << (unsigned int)mE.getDuration().nsec;
+ dS << (unsigned int)mE.getAudioStartMarker().sec;
+ dS << (unsigned int)mE.getAudioStartMarker().nsec;
+ dS << (unsigned long)mE.getDataBlockId();
+ dS << mE.getRuntimeSegmentId();
+ dS << (unsigned int)mE.isAutoFading();
+ dS << (unsigned int)mE.getFadeInTime().sec;
+ dS << (unsigned int)mE.getFadeInTime().nsec;
+ dS << (unsigned int)mE.getFadeOutTime().sec;
+ dS << (unsigned int)mE.getFadeOutTime().nsec;
+ dS << (unsigned int)mE.getRecordedChannel();
+ dS << (unsigned int)mE.getRecordedDevice();
+
+ return dS;
+}
+
+QDataStream&
+operator>>(QDataStream &dS, MappedEvent *mE)
+{
+ unsigned int trackId = 0, instrument = 0, type = 0, data1 = 0, data2 = 0;
+ long eventTimeSec = 0, eventTimeNsec = 0, durationSec = 0, durationNsec = 0,
+ audioSec = 0, audioNsec = 0;
+ std::string dataBlock;
+ unsigned long dataBlockId = 0;
+ int runtimeSegmentId = -1;
+ unsigned int autoFade = 0,
+ fadeInSec = 0, fadeInNsec = 0, fadeOutSec = 0, fadeOutNsec = 0,
+ recordedChannel = 0, recordedDevice = 0;
+
+ dS >> trackId;
+ dS >> instrument;
+ dS >> type;
+ dS >> data1;
+ dS >> data2;
+ dS >> eventTimeSec;
+ dS >> eventTimeNsec;
+ dS >> durationSec;
+ dS >> durationNsec;
+ dS >> audioSec;
+ dS >> audioNsec;
+ dS >> dataBlockId;
+ dS >> runtimeSegmentId;
+ dS >> autoFade;
+ dS >> fadeInSec;
+ dS >> fadeInNsec;
+ dS >> fadeOutSec;
+ dS >> fadeOutNsec;
+ dS >> recordedChannel;
+ dS >> recordedDevice;
+
+ mE->setTrackId((TrackId)trackId);
+ mE->setInstrument((InstrumentId)instrument);
+ mE->setType((MappedEvent::MappedEventType)type);
+ mE->setData1((MidiByte)data1);
+ mE->setData2((MidiByte)data2);
+ mE->setEventTime(RealTime(eventTimeSec, eventTimeNsec));
+ mE->setDuration(RealTime(durationSec, durationNsec));
+ mE->setAudioStartMarker(RealTime(audioSec, audioNsec));
+ mE->setDataBlockId(dataBlockId);
+ mE->setRuntimeSegmentId(runtimeSegmentId);
+ mE->setAutoFade(autoFade);
+ mE->setFadeInTime(RealTime(fadeInSec, fadeInNsec));
+ mE->setFadeOutTime(RealTime(fadeOutSec, fadeOutNsec));
+ mE->setRecordedChannel(recordedChannel);
+ mE->setRecordedDevice(recordedDevice);
+
+ return dS;
+}
+
+QDataStream&
+operator>>(QDataStream &dS, MappedEvent &mE)
+{
+ unsigned int trackId = 0, instrument = 0, type = 0, data1 = 0, data2 = 0;
+ long eventTimeSec = 0, eventTimeNsec = 0, durationSec = 0, durationNsec = 0,
+ audioSec = 0, audioNsec = 0;
+ std::string dataBlock;
+ unsigned long dataBlockId = 0;
+ int runtimeSegmentId = -1;
+ unsigned int autoFade = 0,
+ fadeInSec = 0, fadeInNsec = 0, fadeOutSec = 0, fadeOutNsec = 0,
+ recordedChannel = 0, recordedDevice = 0;
+
+ dS >> trackId;
+ dS >> instrument;
+ dS >> type;
+ dS >> data1;
+ dS >> data2;
+ dS >> eventTimeSec;
+ dS >> eventTimeNsec;
+ dS >> durationSec;
+ dS >> durationNsec;
+ dS >> audioSec;
+ dS >> audioNsec;
+ dS >> dataBlockId;
+ dS >> runtimeSegmentId;
+ dS >> autoFade;
+ dS >> fadeInSec;
+ dS >> fadeInNsec;
+ dS >> fadeOutSec;
+ dS >> fadeOutNsec;
+ dS >> recordedChannel;
+ dS >> recordedDevice;
+
+ mE.setTrackId((TrackId)trackId);
+ mE.setInstrument((InstrumentId)instrument);
+ mE.setType((MappedEvent::MappedEventType)type);
+ mE.setData1((MidiByte)data1);
+ mE.setData2((MidiByte)data2);
+ mE.setEventTime(RealTime(eventTimeSec, eventTimeNsec));
+ mE.setDuration(RealTime(durationSec, durationNsec));
+ mE.setAudioStartMarker(RealTime(audioSec, audioNsec));
+ mE.setDataBlockId(dataBlockId);
+ mE.setRuntimeSegmentId(runtimeSegmentId);
+ mE.setAutoFade(autoFade);
+ mE.setFadeInTime(RealTime(fadeInSec, fadeInNsec));
+ mE.setFadeOutTime(RealTime(fadeOutSec, fadeOutNsec));
+ mE.setRecordedChannel(recordedChannel);
+ mE.setRecordedDevice(recordedDevice);
+
+ return dS;
+}
+
+void
+MappedEvent::addDataByte(MidiByte byte)
+{
+ DataBlockRepository::getInstance()->addDataByteForEvent(byte, this);
+}
+
+void
+MappedEvent::addDataString(const std::string& data)
+{
+ DataBlockRepository::getInstance()->addDataStringForEvent(data, this);
+}
+
+
+
+//--------------------------------------------------
+
+class DataBlockFile
+{
+public:
+ DataBlockFile(DataBlockRepository::blockid id);
+ ~DataBlockFile();
+
+ QString getFileName()
+ {
+ return m_fileName;
+ }
+
+ void addDataByte(MidiByte);
+ void addDataString(const std::string&);
+
+ void clear()
+ {
+ m_cleared = true;
+ }
+ bool exists();
+ void setData(const std::string&);
+ std::string getData();
+
+protected:
+ void prepareToWrite();
+ void prepareToRead();
+
+ //--------------- Data members ---------------------------------
+ QString m_fileName;
+ QFile m_file;
+ bool m_cleared;
+};
+
+DataBlockFile::DataBlockFile(DataBlockRepository::blockid id)
+ : m_fileName(KGlobal::dirs()->resourceDirs("tmp").first() + QString("/rosegarden_datablock_%1").arg(id)),
+ m_file(m_fileName),
+ m_cleared(false)
+{
+ // std::cerr << "DataBlockFile " << m_fileName.latin1() << std::endl;
+}
+
+DataBlockFile::~DataBlockFile()
+{
+ if (m_cleared) {
+// std::cerr << "~DataBlockFile : removing " << m_fileName.latin1() << std::endl;
+ QFile::remove
+ (m_fileName);
+ }
+
+}
+
+bool DataBlockFile::exists()
+{
+ return QFile::exists(m_fileName);
+}
+
+void DataBlockFile::setData(const std::string& s)
+{
+ // std::cerr << "DataBlockFile::setData() : setting data to " << m_fileName << std::endl;
+ prepareToWrite();
+
+ QDataStream stream(&m_file);
+ stream.writeRawBytes(s.data(), s.length());
+}
+
+std::string DataBlockFile::getData()
+{
+ if (!exists())
+ return std::string();
+
+ prepareToRead();
+
+ QDataStream stream(&m_file);
+ // std::cerr << "DataBlockFile::getData() : file size = " << m_file.size() << std::endl;
+ char* tmp = new char[m_file.size()];
+ stream.readRawBytes(tmp, m_file.size());
+ std::string res(tmp, m_file.size());
+ delete[] tmp;
+
+ return res;
+}
+
+void DataBlockFile::addDataByte(MidiByte byte)
+{
+ prepareToWrite();
+ m_file.putch(byte);
+}
+
+void DataBlockFile::addDataString(const std::string& s)
+{
+ prepareToWrite();
+ QDataStream stream(&m_file);
+ stream.writeRawBytes(s.data(), s.length());
+}
+
+void DataBlockFile::prepareToWrite()
+{
+ // std::cerr << "DataBlockFile[" << m_fileName << "]: prepareToWrite" << std::endl;
+ if (!m_file.isWritable()) {
+ m_file.close();
+ m_file.open(IO_WriteOnly | IO_Append);
+ assert(m_file.isWritable());
+ }
+}
+
+void DataBlockFile::prepareToRead()
+{
+// std::cerr << "DataBlockFile[" << m_fileName << "]: prepareToRead" << std::endl;
+ if (!m_file.isReadable()) {
+ m_file.close();
+ m_file.open(IO_ReadOnly);
+ assert(m_file.isReadable());
+ }
+}
+
+
+
+//--------------------------------------------------
+
+DataBlockRepository* DataBlockRepository::getInstance()
+{
+ if (!m_instance)
+ m_instance = new DataBlockRepository;
+ return m_instance;
+}
+
+std::string DataBlockRepository::getDataBlock(DataBlockRepository::blockid id)
+{
+ DataBlockFile dataBlockFile(id);
+
+ if (dataBlockFile.exists())
+ return dataBlockFile.getData();
+
+ return std::string();
+}
+
+
+std::string DataBlockRepository::getDataBlockForEvent(MappedEvent* e)
+{
+ blockid id = e->getDataBlockId();
+ if (id == 0) {
+ // std::cerr << "WARNING: DataBlockRepository::getDataBlockForEvent called on event with data block id 0" << std::endl;
+ return "";
+ }
+ return getInstance()->getDataBlock(id);
+}
+
+void DataBlockRepository::setDataBlockForEvent(MappedEvent* e, const std::string& s)
+{
+ blockid id = e->getDataBlockId();
+ if (id == 0) {
+ // std::cerr << "Creating new datablock for event" << std::endl;
+ getInstance()->registerDataBlockForEvent(s, e);
+ } else {
+ // std::cerr << "Writing " << s.length() << " chars to file for datablock " << id << std::endl;
+ DataBlockFile dataBlockFile(id);
+ dataBlockFile.setData(s);
+ }
+}
+
+bool DataBlockRepository::hasDataBlock(DataBlockRepository::blockid id)
+{
+ return DataBlockFile(id).exists();
+}
+
+DataBlockRepository::blockid DataBlockRepository::registerDataBlock(const std::string& s)
+{
+ blockid id = 0;
+ while (id == 0 || DataBlockFile(id).exists())
+ id = (blockid)random();
+
+ // std::cerr << "DataBlockRepository::registerDataBlock: " << s.length() << " chars, id is " << id << std::endl;
+
+ DataBlockFile dataBlockFile(id);
+ dataBlockFile.setData(s);
+
+ return id;
+}
+
+void DataBlockRepository::unregisterDataBlock(DataBlockRepository::blockid id)
+{
+ DataBlockFile dataBlockFile(id);
+
+ dataBlockFile.clear();
+}
+
+void DataBlockRepository::registerDataBlockForEvent(const std::string& s, MappedEvent* e)
+{
+ e->setDataBlockId(registerDataBlock(s));
+}
+
+void DataBlockRepository::unregisterDataBlockForEvent(MappedEvent* e)
+{
+ unregisterDataBlock(e->getDataBlockId());
+}
+
+
+DataBlockRepository::DataBlockRepository()
+{}
+
+void DataBlockRepository::clear()
+{
+#ifdef DEBUG_MAPPEDEVENT
+ std::cerr << "DataBlockRepository::clear()\n";
+#endif
+
+ // Erase all 'datablock_*' files
+ //
+ QString tmpPath = KGlobal::dirs()->resourceDirs("tmp").first();
+
+ QDir segmentsDir(tmpPath, "rosegarden_datablock_*");
+ for (unsigned int i = 0; i < segmentsDir.count(); ++i) {
+ QString segmentName = tmpPath + '/' + segmentsDir[i];
+ QFile::remove
+ (segmentName);
+ }
+}
+
+
+void DataBlockRepository::addDataByteForEvent(MidiByte byte, MappedEvent* e)
+{
+ DataBlockFile dataBlockFile(e->getDataBlockId());
+ dataBlockFile.addDataByte(byte);
+
+}
+
+void DataBlockRepository::addDataStringForEvent(const std::string& s, MappedEvent* e)
+{
+ DataBlockFile dataBlockFile(e->getDataBlockId());
+ dataBlockFile.addDataString(s);
+}
+
+DataBlockRepository* DataBlockRepository::m_instance = 0;
+
+}
diff --git a/src/sound/MappedEvent.h b/src/sound/MappedEvent.h
new file mode 100644
index 0000000..cc4e3f3
--- /dev/null
+++ b/src/sound/MappedEvent.h
@@ -0,0 +1,546 @@
+// -*- c-indentation-style:"stroustrup" c-basic-offset: 4 -*-
+
+/*
+ Rosegarden
+ A sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <bownie@bownie.com>
+
+ 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 <qdatastream.h>
+
+#include "Composition.h" // for RealTime
+#include "Event.h"
+
+
+#ifndef _MAPPEDEVENT_H_
+#define _MAPPEDEVENT_H_
+
+// Used as a transformation stage between Composition, Events and output
+// at the Sequencer this class and MidiComposition eliminate the notion
+// of the Segment and Track for ease of Event access. The MappedEvents
+// are ready for playing or routing through an Instrument or Effects
+// boxes.
+//
+// MappedEvents can also represent instructions for playback of audio
+// samples - if the m_type is Audio then the sequencer will attempt to
+// map the Pitch (m_data1) to the audio id. Note that this limits us
+// to 256 audio files in the Composition unless we use a different
+// parameter for storing these IDs.
+//
+// The MappedEvent/Instrument relationship is interesting - we don't
+// want to duplicate the entire Instrument at the Sequencer level as
+// it'd be messy and unnecessary. Instead we use a MappedInstrument
+// which is just a very cut down Sequencer-side version of an Instrument.
+//
+// Some of these Events are unidirectional, some are bidirectional -
+// that is they only have a meaning in one direction (they are still
+// legal at either end). They are broadcast in both directions using
+// the "getSequencerSlice" and "processAsync/Recorded" interfaces on
+// which the control messages can piggyback and eventually stripped out.
+//
+
+namespace Rosegarden
+{
+class MappedEvent;
+
+class DataBlockRepository
+{
+public:
+ friend class MappedEvent;
+ typedef unsigned long blockid;
+
+ static DataBlockRepository* getInstance();
+ static std::string getDataBlockForEvent(MappedEvent*);
+ static void setDataBlockForEvent(MappedEvent*, const std::string&);
+ /**
+ * Clear all block files
+ */
+ static void clear();
+ bool hasDataBlock(blockid);
+
+protected:
+ DataBlockRepository();
+
+ std::string getDataBlock(blockid);
+
+ void addDataByteForEvent(MidiByte byte, MappedEvent*);
+ void addDataStringForEvent(const std::string&, MappedEvent*);
+
+
+ blockid registerDataBlock(const std::string&);
+ void unregisterDataBlock(blockid);
+
+ void registerDataBlockForEvent(const std::string&, MappedEvent*);
+ void unregisterDataBlockForEvent(MappedEvent*);
+
+
+ //--------------- Data members ---------------------------------
+
+ static DataBlockRepository* m_instance;
+};
+
+
+class MappedEvent
+{
+public:
+ typedef enum
+ {
+ // INVALID
+ //
+ InvalidMappedEvent = 0,
+
+ // Keep the MidiNotes bit flaggable so that filtering works
+ //
+ MidiNote = 1 << 0,
+ MidiNoteOneShot = 1 << 1, // doesn't need NOTE OFFs
+ MidiProgramChange = 1 << 2,
+ MidiKeyPressure = 1 << 3,
+ MidiChannelPressure = 1 << 4,
+ MidiPitchBend = 1 << 5,
+ MidiController = 1 << 6,
+ MidiSystemMessage = 1 << 7,
+
+ // Sent from the gui to play an audio file
+ Audio = 1 << 8,
+ // Sent from gui to cancel playing an audio file
+ AudioCancel = 1 << 9,
+ // Sent to the gui with audio level on Instrument
+ AudioLevel = 1 << 10,
+ // Sent to the gui to inform an audio file stopped
+ AudioStopped = 1 << 11,
+ // The gui is clear to generate a preview for a new audio file
+ AudioGeneratePreview = 1 << 12,
+
+ // Update Instruments - new ALSA client detected
+ SystemUpdateInstruments = 1 << 13,
+ // Set RG as JACK master/slave
+ SystemJackTransport = 1 << 14,
+ // Set RG as MMC master/slave
+ SystemMMCTransport = 1 << 15,
+ // Set System Messages and MIDI Clock
+ SystemMIDIClock = 1 << 16,
+ // Set Record device
+ SystemRecordDevice = 1 << 17,
+ // Set Metronome device
+ SystemMetronomeDevice = 1 << 18,
+ // Set Audio inputs/outputs: data1 num inputs, data2 num submasters
+ SystemAudioPortCounts = 1 << 19,
+ // Set whether we create various Audio ports (data1 is an AudioOutMask)
+ SystemAudioPorts = 1 << 20,
+ // Some failure has occurred: data1 contains FailureCode
+ SystemFailure = 1 << 21,
+
+ // Time sig. event (from time sig. composition reference segment)
+ TimeSignature = 1 << 22,
+ // Tempo event (from tempo composition reference segment)
+ Tempo = 1 << 23,
+
+ // Panic function
+ Panic = 1 << 24,
+
+ // Set RG as MTC master/slave
+ SystemMTCTransport = 1 << 25,
+ // Auto-connect sync outputs
+ SystemMIDISyncAuto = 1 << 26,
+ // File format used for audio recording (data1 is 0=PCM,1=float)
+ SystemAudioFileFormat = 1 << 27
+
+ } MappedEventType;
+
+ typedef enum
+ {
+ // These values are OR'd to produce the data2 field in a
+ // SystemAudioPorts event.
+ FaderOuts = 1 << 0,
+ SubmasterOuts = 1 << 1
+
+ } MappedEventAudioOutMask;
+
+ typedef enum
+ {
+ // JACK is having some xruns - warn the user maybe
+ FailureXRuns = 0,
+ // JACK has died or kicked us out
+ FailureJackDied = 1,
+ // Audio subsystem failed to read from disc fast enough
+ FailureDiscUnderrun = 2,
+ // Audio subsystem failed to write to disc fast enough
+ FailureDiscOverrun = 3,
+ // Audio subsystem failed to mix busses fast enough
+ FailureBussMixUnderrun = 4,
+ // Audio subsystem failed to mix instruments fast enough
+ FailureMixUnderrun = 5,
+ // Using a timer that has too low a resolution (e.g. 100Hz system timer)
+ WarningImpreciseTimer = 6,
+ // Too much CPU time spent in audio processing -- risk of xruns and lockup
+ FailureCPUOverload = 7,
+ // JACK kicked us out, but we've reconnected
+ FailureJackRestart = 8,
+ // JACK kicked us out, and now the reconnection has failed
+ FailureJackRestartFailed = 9,
+ // A necessary ALSA call has returned an error code
+ FailureALSACallFailed = 10,
+ // Using a timer that has too low a resolution, but RTC might work
+ WarningImpreciseTimerTryRTC = 11,
+ } FailureCode;
+
+ MappedEvent(): m_trackId(0),
+ m_instrument(0),
+ m_type(MidiNote),
+ m_data1(0),
+ m_data2(0),
+ m_eventTime(0, 0),
+ m_duration(0, 0),
+ m_audioStartMarker(0, 0),
+ m_dataBlockId(0),
+ m_isPersistent(false),
+ m_runtimeSegmentId(-1),
+ m_autoFade(false),
+ m_fadeInTime(RealTime::zeroTime),
+ m_fadeOutTime(RealTime::zeroTime),
+ m_recordedChannel(0),
+ m_recordedDevice(0) {}
+
+ // Construct from Events to Internal (MIDI) type MappedEvent
+ //
+ MappedEvent(const Event &e);
+
+ // Another Internal constructor from Events
+ MappedEvent(InstrumentId id,
+ const Event &e,
+ const RealTime &eventTime,
+ const RealTime &duration);
+
+ // A general MappedEvent constructor for any MappedEvent type
+ //
+ MappedEvent(InstrumentId id,
+ MappedEventType type,
+ MidiByte pitch,
+ MidiByte velocity,
+ const RealTime &absTime,
+ const RealTime &duration,
+ const RealTime &audioStartMarker):
+ m_trackId(0),
+ m_instrument(id),
+ m_type(type),
+ m_data1(pitch),
+ m_data2(velocity),
+ m_eventTime(absTime),
+ m_duration(duration),
+ m_audioStartMarker(audioStartMarker),
+ m_dataBlockId(0),
+ m_isPersistent(false),
+ m_runtimeSegmentId(-1),
+ m_autoFade(false),
+ m_fadeInTime(RealTime::zeroTime),
+ m_fadeOutTime(RealTime::zeroTime),
+ m_recordedChannel(0),
+ m_recordedDevice(0) {}
+
+ // Audio MappedEvent shortcut constructor
+ //
+ MappedEvent(InstrumentId id,
+ unsigned short audioID,
+ const RealTime &eventTime,
+ const RealTime &duration,
+ const RealTime &audioStartMarker):
+ m_trackId(0),
+ m_instrument(id),
+ m_type(Audio),
+ m_data1(audioID % 256),
+ m_data2(audioID / 256),
+ m_eventTime(eventTime),
+ m_duration(duration),
+ m_audioStartMarker(audioStartMarker),
+ m_dataBlockId(0),
+ m_isPersistent(false),
+ m_runtimeSegmentId(-1),
+ m_autoFade(false),
+ m_fadeInTime(RealTime::zeroTime),
+ m_fadeOutTime(RealTime::zeroTime),
+ m_recordedChannel(0),
+ m_recordedDevice(0) {}
+
+ // More generalised MIDI event containers for
+ // large and small events (one param, two param)
+ //
+ MappedEvent(InstrumentId id,
+ MappedEventType type,
+ MidiByte data1,
+ MidiByte data2):
+ m_trackId(0),
+ m_instrument(id),
+ m_type(type),
+ m_data1(data1),
+ m_data2(data2),
+ m_eventTime(RealTime(0, 0)),
+ m_duration(RealTime(0, 0)),
+ m_audioStartMarker(RealTime(0, 0)),
+ m_dataBlockId(0),
+ m_isPersistent(false),
+ m_runtimeSegmentId(-1),
+ m_autoFade(false),
+ m_fadeInTime(RealTime::zeroTime),
+ m_fadeOutTime(RealTime::zeroTime),
+ m_recordedChannel(0),
+ m_recordedDevice(0) {}
+
+ MappedEvent(InstrumentId id,
+ MappedEventType type,
+ MidiByte data1):
+ m_trackId(0),
+ m_instrument(id),
+ m_type(type),
+ m_data1(data1),
+ m_data2(0),
+ m_eventTime(RealTime(0, 0)),
+ m_duration(RealTime(0, 0)),
+ m_audioStartMarker(RealTime(0, 0)),
+ m_dataBlockId(0),
+ m_isPersistent(false),
+ m_runtimeSegmentId(-1),
+ m_autoFade(false),
+ m_fadeInTime(RealTime::zeroTime),
+ m_fadeOutTime(RealTime::zeroTime),
+ m_recordedChannel(0),
+ m_recordedDevice(0) {}
+
+
+ // Construct SysExs say
+ //
+ MappedEvent(InstrumentId id,
+ MappedEventType type):
+ m_trackId(0),
+ m_instrument(id),
+ m_type(type),
+ m_data1(0),
+ m_data2(0),
+ m_eventTime(RealTime(0, 0)),
+ m_duration(RealTime(0, 0)),
+ m_audioStartMarker(RealTime(0, 0)),
+ m_dataBlockId(0),
+ m_isPersistent(false),
+ m_runtimeSegmentId(-1),
+ m_autoFade(false),
+ m_fadeInTime(RealTime::zeroTime),
+ m_fadeOutTime(RealTime::zeroTime),
+ m_recordedChannel(0),
+ m_recordedDevice(0) {}
+
+ // Copy constructor
+ //
+ // Fix for 674731 by Pedro Lopez-Cabanillas (20030531)
+ MappedEvent(const MappedEvent &mE):
+ m_trackId(mE.getTrackId()),
+ m_instrument(mE.getInstrument()),
+ m_type(mE.getType()),
+ m_data1(mE.getData1()),
+ m_data2(mE.getData2()),
+ m_eventTime(mE.getEventTime()),
+ m_duration(mE.getDuration()),
+ m_audioStartMarker(mE.getAudioStartMarker()),
+ m_dataBlockId(mE.getDataBlockId()),
+ m_isPersistent(false),
+ m_runtimeSegmentId(mE.getRuntimeSegmentId()),
+ m_autoFade(mE.isAutoFading()),
+ m_fadeInTime(mE.getFadeInTime()),
+ m_fadeOutTime(mE.getFadeOutTime()),
+ m_recordedChannel(mE.getRecordedChannel()),
+ m_recordedDevice(mE.getRecordedDevice()) {}
+
+ // Copy from pointer
+ // Fix for 674731 by Pedro Lopez-Cabanillas (20030531)
+ MappedEvent(MappedEvent *mE):
+ m_trackId(mE->getTrackId()),
+ m_instrument(mE->getInstrument()),
+ m_type(mE->getType()),
+ m_data1(mE->getData1()),
+ m_data2(mE->getData2()),
+ m_eventTime(mE->getEventTime()),
+ m_duration(mE->getDuration()),
+ m_audioStartMarker(mE->getAudioStartMarker()),
+ m_dataBlockId(mE->getDataBlockId()),
+ m_isPersistent(false),
+ m_runtimeSegmentId(mE->getRuntimeSegmentId()),
+ m_autoFade(mE->isAutoFading()),
+ m_fadeInTime(mE->getFadeInTime()),
+ m_fadeOutTime(mE->getFadeOutTime()),
+ m_recordedChannel(mE->getRecordedChannel()),
+ m_recordedDevice(mE->getRecordedDevice()) {}
+
+ // Construct perhaps without initialising, for placement new or equivalent
+ MappedEvent(bool initialise) {
+ if (initialise) *this = MappedEvent();
+ }
+
+ // Event time
+ //
+ void setEventTime(const RealTime &a) { m_eventTime = a; }
+ RealTime getEventTime() const { return m_eventTime; }
+
+ // Duration
+ //
+ void setDuration(const RealTime &d) { m_duration = d; }
+ RealTime getDuration() const { return m_duration; }
+
+ // Instrument
+ void setInstrument(InstrumentId id) { m_instrument = id; }
+ InstrumentId getInstrument() const { return m_instrument; }
+
+ // Track
+ void setTrackId(TrackId id) { m_trackId = id; }
+ TrackId getTrackId() const { return m_trackId; }
+
+ MidiByte getPitch() const { return m_data1; }
+
+ // Keep pitch within MIDI limits
+ //
+ void setPitch(MidiByte p)
+ {
+ m_data1 = p;
+ if (m_data1 > MidiMaxValue) m_data1 = MidiMaxValue;
+ }
+
+ void setVelocity(MidiByte v) { m_data2 = v; }
+ MidiByte getVelocity() const { return m_data2; }
+
+ // And the trendy names for them
+ //
+ MidiByte getData1() const { return m_data1; }
+ MidiByte getData2() const { return m_data2; }
+ void setData1(MidiByte d1) { m_data1 = d1; }
+ void setData2(MidiByte d2) { m_data2 = d2; }
+
+ void setAudioID(unsigned short id) { m_data1 = id % 256; m_data2 = id / 256; }
+ int getAudioID() const { return m_data1 + 256 * m_data2; }
+
+ // A sample doesn't have to be played from the beginning. When
+ // passing an Audio event this value may be set to indicate from
+ // where in the sample it should be played. Duration is measured
+ // against total sounding length (not absolute position).
+ //
+ void setAudioStartMarker(const RealTime &aS)
+ { m_audioStartMarker = aS; }
+ RealTime getAudioStartMarker() const
+ { return m_audioStartMarker; }
+
+ MappedEventType getType() const { return m_type; }
+ void setType(const MappedEventType &value) { m_type = value; }
+
+ // Data block id
+ //
+ DataBlockRepository::blockid getDataBlockId() const { return m_dataBlockId; }
+ void setDataBlockId(DataBlockRepository::blockid dataBlockId) { m_dataBlockId = dataBlockId; }
+
+ // How MappedEvents are ordered in the MappedComposition
+ //
+ struct MappedEventCmp
+ {
+ bool operator()(const MappedEvent *mE1, const MappedEvent *mE2) const
+ {
+ return *mE1 < *mE2;
+ }
+ };
+
+ friend bool operator<(const MappedEvent &a, const MappedEvent &b);
+
+ MappedEvent& operator=(const MappedEvent &mE);
+
+ friend QDataStream& operator>>(QDataStream &dS, MappedEvent *mE);
+ friend QDataStream& operator<<(QDataStream &dS, MappedEvent *mE);
+ friend QDataStream& operator>>(QDataStream &dS, MappedEvent &mE);
+ friend QDataStream& operator<<(QDataStream &dS, const MappedEvent &mE);
+
+ /// Add a single byte to the event's datablock (for SysExs)
+ void addDataByte(MidiByte byte);
+ /// Add several bytes to the event's datablock
+ void addDataString(const std::string& data);
+
+ void setPersistent(bool value) { m_isPersistent = value; }
+ bool isPersistent() const { return m_isPersistent; }
+
+ /// Size of a MappedEvent in a stream
+ static const size_t streamedSize;
+
+ // The runtime segment id of an audio file
+ //
+ int getRuntimeSegmentId() const { return m_runtimeSegmentId; }
+ void setRuntimeSegmentId(int id) { m_runtimeSegmentId = id; }
+
+ bool isAutoFading() const { return m_autoFade; }
+ void setAutoFade(bool value) { m_autoFade = value; }
+
+ RealTime getFadeInTime() const { return m_fadeInTime; }
+ void setFadeInTime(const RealTime &time)
+ { m_fadeInTime = time; }
+
+ RealTime getFadeOutTime() const { return m_fadeOutTime; }
+ void setFadeOutTime(const RealTime &time)
+ { m_fadeOutTime = time; }
+
+ // Original event input channel as it was recorded
+ //
+ unsigned int getRecordedChannel() const { return m_recordedChannel; }
+ void setRecordedChannel(const unsigned int channel)
+ { m_recordedChannel = channel; }
+
+ // Original event record device as it was recorded
+ //
+ unsigned int getRecordedDevice() const { return m_recordedDevice; }
+ void setRecordedDevice(const unsigned int device) { m_recordedDevice = device; }
+
+private:
+ TrackId m_trackId;
+ InstrumentId m_instrument;
+ MappedEventType m_type;
+ MidiByte m_data1;
+ MidiByte m_data2;
+ RealTime m_eventTime;
+ RealTime m_duration;
+ RealTime m_audioStartMarker;
+
+ // Use this when we want to store something in addition to the
+ // other bytes in this type, e.g. System Exclusive.
+ //
+ DataBlockRepository::blockid m_dataBlockId;
+
+ // Should a MappedComposition try and delete this MappedEvent or
+ // if it persistent?
+ //
+ bool m_isPersistent;
+
+
+ // Id of the segment that this (audio) event is derived from
+ //
+ int m_runtimeSegmentId;
+
+ // Audio autofading
+ //
+ bool m_autoFade;
+ RealTime m_fadeInTime;
+ RealTime m_fadeOutTime;
+
+ // input event original data,
+ // stored as it was recorded
+ //
+ unsigned int m_recordedChannel;
+ unsigned int m_recordedDevice;
+};
+
+
+}
+
+#endif
diff --git a/src/sound/MappedInstrument.cpp b/src/sound/MappedInstrument.cpp
new file mode 100644
index 0000000..b353f78
--- /dev/null
+++ b/src/sound/MappedInstrument.cpp
@@ -0,0 +1,153 @@
+// -*- c-indentation-style:"stroustrup" c-basic-offset: 4 -*-
+
+/*
+ Rosegarden
+ A sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <bownie@bownie.com>
+
+ 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 "MappedInstrument.h"
+
+namespace Rosegarden
+{
+
+
+MappedInstrument::MappedInstrument():
+ m_type(Instrument::Midi),
+ m_channel(0),
+ m_id(0),
+ m_name(std::string("")),
+ m_audioChannels(0)
+{}
+
+MappedInstrument::MappedInstrument(Instrument::InstrumentType type,
+ MidiByte channel,
+ InstrumentId id):
+ m_type(type),
+ m_channel(channel),
+ m_id(id),
+ m_name(std::string("")),
+ m_audioChannels(0)
+{}
+
+MappedInstrument::MappedInstrument(Instrument::InstrumentType type,
+ MidiByte channel,
+ InstrumentId id,
+ const std::string &name,
+ DeviceId device):
+ m_type(type),
+ m_channel(channel),
+ m_id(id),
+ m_name(name),
+ m_device(device),
+ m_audioChannels(0)
+{}
+
+MappedInstrument::MappedInstrument(const Instrument &instr):
+ m_type(instr.getType()),
+ m_channel(instr.getMidiChannel()),
+ m_id(instr.getId()),
+ m_name(instr.getName()),
+ m_device((instr.getDevice())->getId()),
+ m_audioChannels(instr.getAudioChannels())
+{}
+
+MappedInstrument::MappedInstrument(Instrument *instr):
+ m_type(instr->getType()),
+ m_channel(instr->getMidiChannel()),
+ m_id(instr->getId()),
+ m_name(instr->getName()),
+ m_device(instr->getDevice()->getId()),
+ m_audioChannels(instr->getAudioChannels())
+{}
+
+QDataStream&
+operator>>(QDataStream &dS, MappedInstrument *mI)
+{
+ unsigned int type, channel, id, device, audioChannels;
+ QString name;
+
+ dS >> type;
+ dS >> channel;
+ dS >> id;
+ dS >> name;
+ dS >> device;
+ dS >> audioChannels;
+
+ mI->setType(Instrument::InstrumentType(type));
+ mI->setChannel(MidiByte(channel));
+ mI->setId(InstrumentId(id));
+ mI->setName(std::string(name.data()));
+ mI->setDevice(DeviceId(device));
+ mI->setAudioChannels(audioChannels);
+
+ return dS;
+}
+
+QDataStream&
+operator>>(QDataStream &dS, MappedInstrument &mI)
+{
+ unsigned int type, channel, id, device, audioChannels;
+ QString name;
+
+ dS >> type;
+ dS >> channel;
+ dS >> id;
+ dS >> name;
+ dS >> device;
+ dS >> audioChannels;
+
+ mI.setType(Instrument::InstrumentType(type));
+ mI.setChannel(MidiByte(channel));
+ mI.setId(InstrumentId(id));
+ mI.setName(std::string(name.data()));
+ mI.setDevice(DeviceId(device));
+ mI.setAudioChannels(audioChannels);
+
+ return dS;
+}
+
+QDataStream&
+operator<<(QDataStream &dS, MappedInstrument *mI)
+{
+ dS << (unsigned int)mI->getType();
+ dS << (unsigned int)mI->getChannel();
+ dS << (unsigned int)mI->getId();
+ ;
+ dS << QString(mI->getName().c_str());
+ dS << (unsigned int)mI->getDevice();
+ dS << (unsigned int)mI->getAudioChannels();
+
+ return dS;
+}
+
+
+QDataStream&
+operator<<(QDataStream &dS, const MappedInstrument &mI)
+{
+ dS << (unsigned int)mI.getType();
+ dS << (unsigned int)mI.getChannel();
+ dS << (unsigned int)mI.getId();
+ ;
+ dS << QString(mI.getName().c_str());
+ dS << (unsigned int)mI.getDevice();
+ dS << (unsigned int)mI.getAudioChannels();
+
+ return dS;
+}
+
+}
+
diff --git a/src/sound/MappedInstrument.h b/src/sound/MappedInstrument.h
new file mode 100644
index 0000000..49f0167
--- /dev/null
+++ b/src/sound/MappedInstrument.h
@@ -0,0 +1,106 @@
+// -*- c-indentation-style:"stroustrup" c-basic-offset: 4 -*-
+
+/*
+ Rosegarden
+ A sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <bownie@bownie.com>
+
+ 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 "Instrument.h"
+#include "MappedDevice.h"
+#include "MappedCommon.h"
+
+#ifndef _MAPPEDINSTRUMENT_H_
+#define _MAPPEDINSTRUMENT_H_
+
+// A scaled-down version of an Instrument that we keep Sequencer
+// side. IDs match with those on the GUI.
+//
+//
+
+namespace Rosegarden
+{
+
+class MappedInstrument
+{
+public:
+
+ MappedInstrument();
+
+ // GUI uses this constructor because it already knows
+ // the name of the Instrument
+ //
+ MappedInstrument(Instrument::InstrumentType type,
+ MidiByte channel,
+ InstrumentId id);
+
+ // Driver uses this constructor (because the gui will want
+ // to know the name)
+ //
+ MappedInstrument(Instrument::InstrumentType type,
+ MidiByte channel,
+ InstrumentId id,
+ const std::string &name,
+ DeviceId device);
+
+ // from instrument
+ MappedInstrument(const Instrument &instrument);
+ MappedInstrument(Instrument *instrument);
+
+ ~MappedInstrument() { ;}
+
+ void setId(InstrumentId id) { m_id = id; }
+ InstrumentId getId() const { return m_id; }
+
+ void setChannel(MidiByte channel) { m_channel = channel; }
+ MidiByte getChannel() const { return m_channel; }
+
+ void setType(Instrument::InstrumentType type) { m_type = type; }
+ Instrument::InstrumentType getType() const { return m_type; }
+
+ void setName(const std::string &name) { m_name = name; }
+ const std::string& getName() const { return m_name; }
+
+ void setDevice(DeviceId device) { m_device = device; }
+ DeviceId getDevice() const { return m_device; }
+
+ // How many audio channels we've got on this audio MappedInstrument
+ //
+ unsigned int getAudioChannels() const { return m_audioChannels; }
+ void setAudioChannels(unsigned int channels) { m_audioChannels = channels; }
+
+ friend QDataStream& operator>>(QDataStream &dS, MappedInstrument *mI);
+ friend QDataStream& operator<<(QDataStream &dS, MappedInstrument *mI);
+ friend QDataStream& operator>>(QDataStream &dS, MappedInstrument &mI);
+ friend QDataStream& operator<<(QDataStream &dS, const MappedInstrument &mI);
+
+private:
+
+ Instrument::InstrumentType m_type;
+ MidiByte m_channel;
+ InstrumentId m_id;
+ std::string m_name;
+ DeviceId m_device;
+
+ // If this is an audio MappedInstrument then how many channels
+ // are associated with it?
+ //
+ unsigned int m_audioChannels;
+};
+
+}
+
+#endif // _MAPPEDINSTRUMENT_H_
diff --git a/src/sound/MappedRealTime.cpp b/src/sound/MappedRealTime.cpp
new file mode 100644
index 0000000..ba596fd
--- /dev/null
+++ b/src/sound/MappedRealTime.cpp
@@ -0,0 +1,62 @@
+// -*- c-indentation-style:"stroustrup" c-basic-offset: 4 -*-
+
+/*
+ Rosegarden
+ A sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <bownie@bownie.com>
+
+ 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 "MappedRealTime.h"
+
+namespace Rosegarden
+{
+
+QDataStream&
+operator>>(QDataStream &dS, MappedRealTime *mRT)
+{
+ dS >> mRT->sec;
+ dS >> mRT->nsec;
+ return dS;
+}
+
+QDataStream&
+operator<<(QDataStream &dS, MappedRealTime *mRT)
+{
+ dS << mRT->sec;
+ dS << mRT->nsec;
+ return dS;
+}
+
+QDataStream&
+operator>>(QDataStream &dS, MappedRealTime &mRT)
+{
+ dS >> mRT.sec;
+ dS >> mRT.nsec;
+ return dS;
+}
+
+
+QDataStream&
+operator<<(QDataStream &dS, const MappedRealTime &mRT)
+{
+ dS << mRT.sec;
+ dS << mRT.nsec;
+ return dS;
+}
+
+
+}
+
diff --git a/src/sound/MappedRealTime.h b/src/sound/MappedRealTime.h
new file mode 100644
index 0000000..ca2afd0
--- /dev/null
+++ b/src/sound/MappedRealTime.h
@@ -0,0 +1,56 @@
+// -*- c-indentation-style:"stroustrup" c-basic-offset: 4 -*-
+
+/*
+ Rosegarden
+ A sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <bownie@bownie.com>
+
+ 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 <qdatastream.h>
+#include "RealTime.h"
+
+// Just a DCOP wrapper to RealTime
+//
+
+#ifndef _MAPPEDREALTIME_H_
+#define _MAPPEDREALTIME_H_
+
+namespace Rosegarden
+{
+
+class MappedRealTime : public RealTime
+{
+public:
+ MappedRealTime() : RealTime(0, 0) {;}
+ MappedRealTime(const RealTime &t) : RealTime(t.sec, t.nsec) {;}
+
+ // Return as RealTime
+ RealTime getRealTime() { return RealTime(sec, nsec); }
+
+ // DCOP datastream
+ //
+ friend QDataStream& operator>>(QDataStream &dS, MappedRealTime *mRT);
+ friend QDataStream& operator<<(QDataStream &dS, MappedRealTime *mRT);
+ friend QDataStream& operator>>(QDataStream &dS, MappedRealTime &mRT);
+ friend QDataStream& operator<<(QDataStream &dS, const MappedRealTime &mRT);
+
+
+};
+
+}
+
+#endif // _MAPPEDREALTIME_H_
+
diff --git a/src/sound/MappedStudio.cpp b/src/sound/MappedStudio.cpp
new file mode 100644
index 0000000..4b35122
--- /dev/null
+++ b/src/sound/MappedStudio.cpp
@@ -0,0 +1,1719 @@
+// -*- c-indentation-style:"stroustrup" c-basic-offset: 4 -*-
+
+/*
+ Rosegarden
+ A sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <bownie@bownie.com>
+
+ 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 <iostream>
+
+#include "MappedStudio.h"
+#include "SoundDriver.h"
+#include "PluginFactory.h"
+
+#include <pthread.h> // for mutex
+
+//#define DEBUG_MAPPEDSTUDIO 1
+
+namespace Rosegarden
+{
+
+static pthread_mutex_t _mappedObjectContainerLock;
+
+#ifdef DEBUG_MAPPEDSTUDIO
+static int _approxLockCount = 0;
+#endif
+
+static inline void getLock(const char *file, int line)
+{
+#ifdef DEBUG_MAPPEDSTUDIO
+ std::cerr << "Acquiring MappedStudio container lock at " << file << ":" << line << ": count " << _approxLockCount++ << std::endl;
+#endif
+
+ pthread_mutex_lock(&_mappedObjectContainerLock);
+}
+
+static inline void releaseLock(const char *file, int line)
+{
+ pthread_mutex_unlock(&_mappedObjectContainerLock);
+#ifdef DEBUG_MAPPEDSTUDIO
+
+ std::cerr << "Released container lock at " << file << ":" << line << ": count " << --_approxLockCount << std::endl;
+#endif
+}
+
+#define GET_LOCK getLock(__FILE__,__LINE__)
+#define RELEASE_LOCK releaseLock(__FILE__,__LINE__)
+
+// These stream functions are stolen and adapted from Qt3 qvaluevector.h
+//
+// ** Copyright (C) 1992-2000 Trolltech AS. All rights reserved.
+//
+QDataStream& operator>>(QDataStream& s, MappedObjectIdList& v)
+{
+ v.clear();
+ Q_UINT32 c;
+ s >> c;
+ v.resize(c);
+ for (Q_UINT32 i = 0; i < c; ++i) {
+ MappedObjectId t;
+ s >> t;
+ v[i] = t;
+ }
+ return s;
+}
+
+QDataStream& operator<<(QDataStream& s, const MappedObjectIdList& v)
+{
+ s << (Q_UINT32)v.size();
+ MappedObjectIdList::const_iterator it = v.begin();
+ for ( ; it != v.end(); ++it )
+ s << *it;
+ return s;
+}
+
+QDataStream& operator>>(QDataStream& s, MappedObjectPropertyList& v)
+{
+ v.clear();
+ Q_UINT32 c;
+ s >> c;
+ v.resize(c);
+ for (Q_UINT32 i = 0; i < c; ++i) {
+ MappedObjectProperty t;
+ s >> t;
+ v[i] = t;
+ }
+ return s;
+}
+
+QDataStream& operator<<(QDataStream& s, const MappedObjectPropertyList& v)
+{
+ s << (Q_UINT32)v.size();
+ MappedObjectPropertyList::const_iterator it = v.begin();
+ for ( ; it != v.end(); ++it )
+ s << *it;
+ return s;
+}
+
+QDataStream& operator>>(QDataStream& s, MappedObjectValueList& v)
+{
+ v.clear();
+ Q_UINT32 c;
+ s >> c;
+ v.resize(c);
+ for (Q_UINT32 i = 0; i < c; ++i) {
+ MappedObjectValue t;
+ s >> t;
+ v[i] = t;
+ }
+ return s;
+}
+
+QDataStream& operator<<(QDataStream& s, const MappedObjectValueList& v)
+{
+ s << (Q_UINT32)v.size();
+ MappedObjectValueList::const_iterator it = v.begin();
+ for ( ; it != v.end(); ++it )
+ s << *it;
+ return s;
+}
+
+// Define our object properties - these can be queried and set.
+//
+
+// General things
+//
+const MappedObjectProperty MappedObject::Name = "name";
+const MappedObjectProperty MappedObject::Instrument = "instrument";
+const MappedObjectProperty MappedObject::Position = "position";
+
+const MappedObjectProperty MappedConnectableObject::ConnectionsIn = "connectionsIn";
+const MappedObjectProperty MappedConnectableObject::ConnectionsOut = "connectionsOut";
+
+const MappedObjectProperty MappedAudioFader::Channels = "channels";
+const MappedObjectProperty MappedAudioFader::FaderLevel = "faderLevel";
+const MappedObjectProperty MappedAudioFader::FaderRecordLevel = "faderRecordLevel";
+const MappedObjectProperty MappedAudioFader::Pan = "pan";
+const MappedObjectProperty MappedAudioFader::InputChannel = "inputChannel";
+
+const MappedObjectProperty MappedAudioBuss::BussId = "bussId";
+const MappedObjectProperty MappedAudioBuss::Level = "level";
+const MappedObjectProperty MappedAudioBuss::Pan = "pan";
+
+const MappedObjectProperty MappedAudioInput::InputNumber = "inputNumber";
+
+const MappedObjectProperty MappedPluginSlot::Identifier = "identifier";
+const MappedObjectProperty MappedPluginSlot::PluginName = "pluginname";
+const MappedObjectProperty MappedPluginSlot::Label = "label";
+const MappedObjectProperty MappedPluginSlot::Author = "author";
+const MappedObjectProperty MappedPluginSlot::Copyright = "copyright";
+const MappedObjectProperty MappedPluginSlot::Category = "category";
+const MappedObjectProperty MappedPluginSlot::PortCount = "portcount";
+const MappedObjectProperty MappedPluginSlot::Ports = "ports";
+const MappedObjectProperty MappedPluginSlot::Instrument = "instrument";
+const MappedObjectProperty MappedPluginSlot::Position = "position";
+const MappedObjectProperty MappedPluginSlot::Bypassed = "bypassed";
+const MappedObjectProperty MappedPluginSlot::Programs = "programs";
+const MappedObjectProperty MappedPluginSlot::Program = "program";
+const MappedObjectProperty MappedPluginSlot::Configuration = "configuration";
+
+const MappedObjectProperty MappedPluginPort::PortNumber = "portnumber";
+const MappedObjectProperty MappedPluginPort::Name = "name";
+const MappedObjectProperty MappedPluginPort::Minimum = "minimum";
+const MappedObjectProperty MappedPluginPort::Maximum = "maximum";
+const MappedObjectProperty MappedPluginPort::Default = "default";
+const MappedObjectProperty MappedPluginPort::DisplayHint = "displayhint";
+const MappedObjectProperty MappedPluginPort::Value = "value";
+
+// --------- MappedObject ---------
+//
+
+void
+MappedObject::addChild(MappedObject *object)
+{
+ std::vector<MappedObject*>::iterator it = m_children.begin();
+ for (; it != m_children.end(); it++)
+ if ((*it) == object)
+ return ;
+
+ m_children.push_back(object);
+}
+
+void
+MappedObject::removeChild(MappedObject *object)
+{
+ std::vector<MappedObject*>::iterator it = m_children.begin();
+ for (; it != m_children.end(); it++) {
+ if ((*it) == object) {
+ m_children.erase(it);
+ return ;
+ }
+ }
+}
+
+// Return all child ids
+//
+MappedObjectPropertyList
+MappedObject::getChildren()
+{
+ MappedObjectPropertyList list;
+ std::vector<MappedObject*>::iterator it = m_children.begin();
+ for (; it != m_children.end(); it++)
+ list.push_back(QString("%1").arg((*it)->getId()));
+
+ return list;
+}
+
+
+// Return all child ids of a certain type
+//
+MappedObjectPropertyList
+MappedObject::getChildren(MappedObjectType type)
+{
+ MappedObjectPropertyList list;
+ std::vector<MappedObject*>::iterator it = m_children.begin();
+ for (; it != m_children.end(); it++) {
+ if ((*it)->getType() == type)
+ list.push_back(QString("%1").arg((*it)->getId()));
+ }
+
+ return list;
+}
+
+void
+MappedObject::destroyChildren()
+{
+ // remove references from the studio as well as from the object
+ MappedObject *studioObject = getParent();
+ while (!dynamic_cast<MappedStudio*>(studioObject))
+ studioObject = studioObject->getParent();
+
+ // see note in destroy() below
+
+ std::vector<MappedObject *> children = m_children;
+ m_children.clear();
+
+ std::vector<MappedObject *>::iterator it = children.begin();
+ for (; it != children.end(); it++)
+ (*it)->destroy(); // remove from studio and destroy
+}
+
+// Destroy this object and remove it from the studio and
+// do the same for all its children.
+//
+void
+MappedObject::destroy()
+{
+ MappedObject *studioObject = getParent();
+ while (!dynamic_cast<MappedStudio*>(studioObject))
+ studioObject = studioObject->getParent();
+
+ MappedStudio *studio = dynamic_cast<MappedStudio*>(studioObject);
+
+ // The destroy method on each child calls studio->clearObject,
+ // which calls back on the parent (in this case us) to remove the
+ // child. (That's necessary for the case of destroying a plugin,
+ // where we need to remove it from its plugin manager -- etc.) So
+ // we don't want to be iterating over m_children here, as it will
+ // change from under us.
+
+ std::vector<MappedObject *> children = m_children;
+ m_children.clear();
+
+ std::vector<MappedObject *>::iterator it = children.begin();
+ for (; it != children.end(); it++) {
+ (*it)->destroy();
+ }
+
+ (void)studio->clearObject(m_id);
+ delete this;
+}
+
+
+// ------- MappedStudio -------
+//
+
+MappedStudio::MappedStudio() :
+ MappedObject(0,
+ "MappedStudio",
+ Studio,
+ 0),
+ m_runningObjectId(1)
+{
+ pthread_mutexattr_t attr;
+ pthread_mutexattr_init(&attr);
+#ifdef HAVE_PTHREAD_MUTEX_RECURSIVE
+
+ pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
+#else
+#ifdef PTHREAD_MUTEX_RECURSIVE
+
+ pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
+#else
+
+ pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE_NP);
+#endif
+#endif
+
+ pthread_mutex_init(&_mappedObjectContainerLock, &attr);
+}
+
+MappedStudio::~MappedStudio()
+{
+#ifdef DEBUG_MAPPEDSTUDIO
+ std::cout << "MappedStudio::~MappedStudio" << std::endl;
+#endif
+
+ clear();
+}
+
+
+// Object factory
+//
+MappedObject*
+MappedStudio::createObject(MappedObjectType type)
+{
+ GET_LOCK;
+
+ MappedObject *mO = 0;
+
+ // Ensure we've got an empty slot
+ //
+ while (getObjectById(m_runningObjectId))
+ m_runningObjectId++;
+
+ mO = createObject(type, m_runningObjectId);
+
+ // If we've got a new object increase the running id
+ //
+ if (mO)
+ m_runningObjectId++;
+
+ RELEASE_LOCK;
+ return mO;
+}
+
+MappedObject*
+MappedStudio::createObject(MappedObjectType type,
+ MappedObjectId id)
+{
+ GET_LOCK;
+
+ // fail if the object already exists and it's not zero
+ if (id != 0 && getObjectById(id)) {
+ RELEASE_LOCK;
+ return 0;
+ }
+
+ MappedObject *mO = 0;
+
+ if (type == MappedObject::AudioFader) {
+ mO = new MappedAudioFader(this,
+ id,
+ 2); // channels
+
+ // push to the studio's child stack
+ addChild(mO);
+ } else if (type == MappedObject::AudioBuss) {
+ mO = new MappedAudioBuss(this,
+ id);
+
+ // push to the studio's child stack
+ addChild(mO);
+ } else if (type == MappedObject::AudioInput) {
+ mO = new MappedAudioInput(this,
+ id);
+
+ // push to the studio's child stack
+ addChild(mO);
+ } else if (type == MappedObject::PluginSlot) {
+ mO = new MappedPluginSlot(this,
+ id);
+ addChild(mO);
+ } else if (type == MappedObject::PluginPort) {
+ mO = new MappedPluginPort(this,
+ id);
+ // reset the port's parent after creation outside this method
+ }
+
+ // Insert
+ if (mO) {
+#ifdef DEBUG_MAPPEDSTUDIO
+ std::cerr << "Adding object " << id << " to category " << type << std::endl;
+#endif
+
+ m_objects[type][id] = mO;
+ }
+
+ RELEASE_LOCK;
+
+ return mO;
+}
+
+MappedObject*
+MappedStudio::getObjectOfType(MappedObjectType type)
+{
+ MappedObject *rv = 0;
+
+ GET_LOCK;
+
+ MappedObjectCategory &category = m_objects[type];
+ if (!category.empty())
+ rv = category.begin()->second;
+
+ RELEASE_LOCK;
+
+ return rv;
+}
+
+std::vector<MappedObject *>
+MappedStudio::getObjectsOfType(MappedObjectType type)
+{
+ std::vector<MappedObject *> rv;
+
+ GET_LOCK;
+
+ MappedObjectCategory &category = m_objects[type];
+
+ for (MappedObjectCategory::iterator i = category.begin();
+ i != category.end(); ++i) {
+ rv.push_back(i->second);
+ }
+
+ RELEASE_LOCK;
+
+ return rv;
+}
+
+unsigned int
+MappedStudio::getObjectCount(MappedObjectType type)
+{
+ unsigned int count = 0;
+
+ GET_LOCK;
+
+ MappedObjectCategory &category = m_objects[type];
+ count = category.size();
+
+ RELEASE_LOCK;
+
+ return count;
+}
+
+
+bool
+MappedStudio::destroyObject(MappedObjectId id)
+{
+ GET_LOCK;
+
+ MappedObject *obj = getObjectById(id);
+
+ bool rv = false;
+
+ if (obj) {
+ obj->destroy();
+ rv = true;
+ }
+
+ RELEASE_LOCK;
+
+ return rv;
+}
+
+bool
+MappedStudio::connectObjects(MappedObjectId mId1, MappedObjectId mId2)
+{
+ GET_LOCK;
+
+ bool rv = false;
+
+ // objects must exist and be of connectable types
+ MappedConnectableObject *obj1 =
+ dynamic_cast<MappedConnectableObject *>(getObjectById(mId1));
+ MappedConnectableObject *obj2 =
+ dynamic_cast<MappedConnectableObject *>(getObjectById(mId2));
+
+ if (obj1 && obj2) {
+ obj1->addConnection(MappedConnectableObject::Out, mId2);
+ obj2->addConnection(MappedConnectableObject::In, mId1);
+ rv = true;
+ }
+
+ RELEASE_LOCK;
+
+ return rv;
+}
+
+bool
+MappedStudio::disconnectObjects(MappedObjectId mId1, MappedObjectId mId2)
+{
+ GET_LOCK;
+
+ bool rv = false;
+
+ // objects must exist and be of connectable types
+ MappedConnectableObject *obj1 =
+ dynamic_cast<MappedConnectableObject *>(getObjectById(mId1));
+ MappedConnectableObject *obj2 =
+ dynamic_cast<MappedConnectableObject *>(getObjectById(mId2));
+
+ if (obj1 && obj2) {
+ obj1->removeConnection(MappedConnectableObject::Out, mId2);
+ obj2->removeConnection(MappedConnectableObject::In, mId1);
+ rv = true;
+ }
+
+ RELEASE_LOCK;
+
+ return rv;
+}
+
+bool
+MappedStudio::disconnectObject(MappedObjectId mId)
+{
+ GET_LOCK;
+
+ bool rv = false;
+
+ MappedConnectableObject *obj =
+ dynamic_cast<MappedConnectableObject *>(getObjectById(mId));
+
+ if (obj) {
+ while (1) {
+ MappedObjectValueList list =
+ obj->getConnections(MappedConnectableObject::In);
+ if (list.empty())
+ break;
+ MappedObjectId otherId = MappedObjectId(*list.begin());
+ disconnectObjects(otherId, mId);
+ }
+ while (1) {
+ MappedObjectValueList list =
+ obj->getConnections(MappedConnectableObject::Out);
+ if (list.empty())
+ break;
+ MappedObjectId otherId = MappedObjectId(*list.begin());
+ disconnectObjects(mId, otherId);
+ }
+ }
+
+ rv = true;
+
+ RELEASE_LOCK;
+
+ return rv;
+}
+
+
+
+// Clear down the whole studio
+//
+void
+MappedStudio::clear()
+{
+ GET_LOCK;
+
+ for (MappedObjectMap::iterator i = m_objects.begin();
+ i != m_objects.end(); ++i) {
+
+ for (MappedObjectCategory::iterator j = i->second.begin();
+ j != i->second.end(); ++j) {
+
+ delete j->second;
+ }
+ }
+
+ m_objects.clear();
+
+ // reset running object id
+ m_runningObjectId = 1;
+
+ RELEASE_LOCK;
+}
+
+bool
+MappedStudio::clearObject(MappedObjectId id)
+{
+ bool rv = false;
+
+ GET_LOCK;
+
+ for (MappedObjectMap::iterator i = m_objects.begin();
+ i != m_objects.end(); ++i) {
+
+ MappedObjectCategory::iterator j = i->second.find(id);
+ if (j != i->second.end()) {
+ // if the object has a parent other than the studio,
+ // persuade that parent to abandon it
+ MappedObject *parent = j->second->getParent();
+ if (parent && !dynamic_cast<MappedStudio *>(parent)) {
+ parent->removeChild(j->second);
+ }
+
+ i->second.erase(j);
+ rv = true;
+ break;
+ }
+ }
+
+ RELEASE_LOCK;
+
+ return rv;
+}
+
+
+MappedObjectPropertyList
+MappedStudio::getPropertyList(const MappedObjectProperty &property)
+{
+ MappedObjectPropertyList list;
+
+ if (property == "") {
+ // something
+ }
+
+ return list;
+}
+
+bool
+MappedStudio::getProperty(const MappedObjectProperty &,
+ MappedObjectValue &)
+{
+ return false;
+}
+
+MappedObject*
+MappedStudio::getObjectById(MappedObjectId id)
+{
+ GET_LOCK;
+ MappedObject *rv = 0;
+
+ for (MappedObjectMap::iterator i = m_objects.begin();
+ i != m_objects.end(); ++i) {
+
+ MappedObjectCategory::iterator j = i->second.find(id);
+ if (j != i->second.end()) {
+ rv = j->second;
+ break;
+ }
+ }
+
+ RELEASE_LOCK;
+ return rv;
+}
+
+MappedObject*
+MappedStudio::getObjectByIdAndType(MappedObjectId id, MappedObjectType type)
+{
+ GET_LOCK;
+ MappedObject *rv = 0;
+
+ MappedObjectCategory &category = m_objects[type];
+ MappedObjectCategory::iterator i = category.find(id);
+ if (i != category.end()) {
+ rv = i->second;
+ }
+
+ RELEASE_LOCK;
+ return rv;
+}
+
+MappedObject*
+MappedStudio::getFirst(MappedObjectType type)
+{
+ return getObjectOfType(type);
+}
+
+MappedObject*
+MappedStudio::getNext(MappedObject *object)
+{
+ GET_LOCK;
+
+ MappedObjectCategory &category = m_objects[object->getType()];
+
+ bool next = false;
+ MappedObject *rv = 0;
+
+ for (MappedObjectCategory::iterator i = category.begin();
+ i != category.end(); ++i) {
+ if (i->second->getId() == object->getId())
+ next = true;
+ else if (next) {
+ rv = i->second;
+ break;
+ }
+ }
+
+ RELEASE_LOCK;
+ return rv;
+}
+
+void
+MappedStudio::setProperty(const MappedObjectProperty &property,
+ MappedObjectValue /*value*/)
+{
+ if (property == "") {}
+
+}
+
+MappedAudioFader *
+MappedStudio::getAudioFader(InstrumentId id)
+{
+ GET_LOCK;
+
+ MappedObjectCategory &category = m_objects[AudioFader];
+ MappedAudioFader *rv = 0;
+
+ for (MappedObjectCategory::iterator i = category.begin();
+ i != category.end(); ++i) {
+ MappedAudioFader *fader = dynamic_cast<MappedAudioFader *>(i->second);
+ if (fader && (fader->getInstrument() == id)) {
+ rv = fader;
+ break;
+ }
+ }
+
+ RELEASE_LOCK;
+ return rv;
+}
+
+MappedAudioBuss *
+MappedStudio::getAudioBuss(int bussNumber)
+{
+ GET_LOCK;
+
+ MappedObjectCategory &category = m_objects[AudioBuss];
+ MappedAudioBuss *rv = 0;
+
+ for (MappedObjectCategory::iterator i = category.begin();
+ i != category.end(); ++i) {
+ MappedAudioBuss *buss = dynamic_cast<MappedAudioBuss *>(i->second);
+ if (buss && (buss->getBussId() == bussNumber)) {
+ rv = buss;
+ break;
+ }
+ }
+
+ RELEASE_LOCK;
+ return rv;
+}
+
+MappedAudioInput *
+MappedStudio::getAudioInput(int inputNumber)
+{
+ GET_LOCK;
+
+ MappedObjectCategory &category = m_objects[AudioInput];
+ MappedAudioInput *rv = 0;
+
+ for (MappedObjectCategory::iterator i = category.begin();
+ i != category.end(); ++i) {
+ MappedAudioInput *input = dynamic_cast<MappedAudioInput *>(i->second);
+ if (input && (input->getInputNumber() == inputNumber)) {
+ rv = input;
+ break;
+ }
+ }
+
+ RELEASE_LOCK;
+ return rv;
+}
+
+
+// -------------- MappedConnectableObject -----------------
+//
+//
+MappedConnectableObject::MappedConnectableObject(MappedObject *parent,
+ const std::string &name,
+ MappedObjectType type,
+ MappedObjectId id):
+ MappedObject(parent,
+ name,
+ type,
+ id)
+{}
+
+MappedConnectableObject::~MappedConnectableObject()
+{}
+
+void
+MappedConnectableObject::setConnections(ConnectionDirection dir,
+ MappedObjectValueList conns)
+{
+ if (dir == In)
+ m_connectionsIn = conns;
+ else
+ m_connectionsOut = conns;
+}
+
+void
+MappedConnectableObject::addConnection(ConnectionDirection dir,
+ MappedObjectId id)
+{
+ MappedObjectValueList &list =
+ (dir == In ? m_connectionsIn : m_connectionsOut);
+
+ for (MappedObjectValueList::iterator i = list.begin(); i != list.end(); ++i) {
+ if (*i == id) {
+ return ;
+ }
+ }
+
+ list.push_back(MappedObjectValue(id));
+}
+
+void
+MappedConnectableObject::removeConnection(ConnectionDirection dir,
+ MappedObjectId id)
+{
+ MappedObjectValueList &list =
+ (dir == In ? m_connectionsIn : m_connectionsOut);
+
+ for (MappedObjectValueList::iterator i = list.begin(); i != list.end(); ++i) {
+ if (*i == id) {
+ list.erase(i);
+ return ;
+ }
+ }
+}
+
+MappedObjectValueList
+MappedConnectableObject::getConnections(ConnectionDirection dir)
+{
+ if (dir == In)
+ return m_connectionsIn;
+ else
+ return m_connectionsOut;
+}
+
+
+// ------------ MappedAudioFader ----------------
+//
+MappedAudioFader::MappedAudioFader(MappedObject *parent,
+ MappedObjectId id,
+ MappedObjectValue channels):
+ MappedConnectableObject(parent,
+ "MappedAudioFader",
+ AudioFader,
+ id),
+ m_level(0.0), // dB
+ m_recordLevel(0.0),
+ m_instrumentId(0),
+ m_pan(0),
+ m_channels(channels),
+ m_inputChannel(0)
+{}
+
+MappedAudioFader::~MappedAudioFader()
+{}
+
+
+MappedObjectPropertyList
+MappedAudioFader::getPropertyList(const MappedObjectProperty &property)
+{
+ MappedObjectPropertyList list;
+
+ if (property == "") {
+ list.push_back(MappedAudioFader::FaderLevel);
+ list.push_back(MappedAudioFader::FaderRecordLevel);
+ list.push_back(MappedObject::Instrument);
+ list.push_back(MappedAudioFader::Pan);
+ list.push_back(MappedAudioFader::Channels);
+ list.push_back(MappedConnectableObject::ConnectionsIn);
+ list.push_back(MappedConnectableObject::ConnectionsOut);
+ } else if (property == MappedObject::Instrument) {
+ list.push_back(MappedObjectProperty("%1").arg(m_instrumentId));
+ } else if (property == MappedAudioFader::FaderLevel) {
+ list.push_back(MappedObjectProperty("%1").arg(m_level));
+ } else if (property == MappedAudioFader::FaderRecordLevel) {
+ list.push_back(MappedObjectProperty("%1").arg(m_recordLevel));
+ } else if (property == MappedAudioFader::Channels) {
+ list.push_back(MappedObjectProperty("%1").arg(m_channels));
+ } else if (property == MappedAudioFader::InputChannel) {
+ list.push_back(MappedObjectProperty("%1").arg(m_inputChannel));
+ } else if (property == MappedAudioFader::Pan) {
+ list.push_back(MappedObjectProperty("%1").arg(m_pan));
+ } else if (property == MappedConnectableObject::ConnectionsIn) {
+ MappedObjectValueList::const_iterator
+ it = m_connectionsIn.begin();
+
+ for ( ; it != m_connectionsIn.end(); ++it) {
+ list.push_back(QString("%1").arg(*it));
+ }
+ } else if (property == MappedConnectableObject::ConnectionsOut) {
+ MappedObjectValueList::const_iterator
+ it = m_connectionsOut.begin();
+
+ for ( ; it != m_connectionsOut.end(); ++it) {
+ list.push_back(QString("%1").arg(*it));
+ }
+ }
+
+ return list;
+}
+
+bool
+MappedAudioFader::getProperty(const MappedObjectProperty &property,
+ MappedObjectValue &value)
+{
+ if (property == FaderLevel) {
+ value = m_level;
+ } else if (property == Instrument) {
+ value = m_instrumentId;
+ } else if (property == FaderRecordLevel) {
+ value = m_recordLevel;
+ } else if (property == Channels) {
+ value = m_channels;
+ } else if (property == InputChannel) {
+ value = m_inputChannel;
+ } else if (property == Pan) {
+ value = m_pan;
+ } else {
+#ifdef DEBUG_MAPPEDSTUDIO
+ std::cerr << "MappedAudioFader::getProperty - "
+ << "unsupported or non-scalar property" << std::endl;
+#endif
+
+ return false;
+ }
+ return true;
+}
+
+void
+MappedAudioFader::setProperty(const MappedObjectProperty &property,
+ MappedObjectValue value)
+{
+ bool updateLevels = false;
+
+ if (property == MappedAudioFader::FaderLevel) {
+ m_level = value;
+ updateLevels = true;
+ } else if (property == MappedObject::Instrument) {
+ m_instrumentId = InstrumentId(value);
+ updateLevels = true;
+ } else if (property == MappedAudioFader::FaderRecordLevel) {
+ m_recordLevel = value;
+ } else if (property == MappedAudioFader::Channels) {
+ m_channels = value;
+ } else if (property == MappedAudioFader::InputChannel) {
+ m_inputChannel = value;
+ } else if (property == MappedAudioFader::Pan) {
+ m_pan = value;
+ updateLevels = true;
+ } else if (property == MappedConnectableObject::ConnectionsIn) {
+ m_connectionsIn.clear();
+ m_connectionsIn.push_back(value);
+ } else if (property == MappedConnectableObject::ConnectionsOut) {
+ m_connectionsOut.clear();
+ m_connectionsOut.push_back(value);
+ } else {
+#ifdef DEBUG_MAPPEDSTUDIO
+ std::cerr << "MappedAudioFader::setProperty - "
+ << "unsupported property" << std::endl;
+#endif
+
+ return ;
+ }
+
+ /*
+ std::cout << "MappedAudioFader::setProperty - "
+ << property << " = " << value << std::endl;
+ */
+
+ if (updateLevels) {
+ MappedStudio *studio =
+ dynamic_cast<MappedStudio*>(getParent());
+
+ if (studio) {
+ studio->getSoundDriver()->setAudioInstrumentLevels
+ (m_instrumentId, m_level, m_pan);
+ }
+ }
+}
+
+// ---------------- MappedAudioBuss -------------------
+//
+//
+MappedAudioBuss::MappedAudioBuss(MappedObject *parent,
+ MappedObjectId id) :
+ MappedConnectableObject(parent,
+ "MappedAudioBuss",
+ AudioBuss,
+ id),
+ m_bussId(0),
+ m_level(0),
+ m_pan(0)
+{}
+
+MappedAudioBuss::~MappedAudioBuss()
+{}
+
+MappedObjectPropertyList
+MappedAudioBuss::getPropertyList(const MappedObjectProperty &property)
+{
+ MappedObjectPropertyList list;
+
+ if (property == "") {
+ list.push_back(MappedAudioBuss::BussId);
+ list.push_back(MappedAudioBuss::Level);
+ list.push_back(MappedAudioBuss::Pan);
+ list.push_back(MappedConnectableObject::ConnectionsIn);
+ list.push_back(MappedConnectableObject::ConnectionsOut);
+ } else if (property == BussId) {
+ list.push_back(MappedObjectProperty("%1").arg(m_bussId));
+ } else if (property == Level) {
+ list.push_back(MappedObjectProperty("%1").arg(m_level));
+ } else if (property == MappedConnectableObject::ConnectionsIn) {
+ MappedObjectValueList::const_iterator
+ it = m_connectionsIn.begin();
+
+ for ( ; it != m_connectionsIn.end(); ++it) {
+ list.push_back(QString("%1").arg(*it));
+ }
+ } else if (property == MappedConnectableObject::ConnectionsOut) {
+ MappedObjectValueList::const_iterator
+ it = m_connectionsOut.begin();
+
+ for ( ; it != m_connectionsOut.end(); ++it) {
+ list.push_back(QString("%1").arg(*it));
+ }
+ }
+
+ return list;
+}
+
+bool
+MappedAudioBuss::getProperty(const MappedObjectProperty &property,
+ MappedObjectValue &value)
+{
+ if (property == BussId) {
+ value = m_bussId;
+ } else if (property == Level) {
+ value = m_level;
+ } else if (property == Pan) {
+ value = m_pan;
+ } else {
+#ifdef DEBUG_MAPPEDSTUDIO
+ std::cerr << "MappedAudioBuss::getProperty - "
+ << "unsupported or non-scalar property" << std::endl;
+#endif
+
+ return false;
+ }
+ return true;
+}
+
+void
+MappedAudioBuss::setProperty(const MappedObjectProperty &property,
+ MappedObjectValue value)
+{
+ bool updateLevels = false;
+
+ if (property == MappedAudioBuss::BussId) {
+ m_bussId = (int)value;
+ updateLevels = true;
+ } else if (property == MappedAudioBuss::Level) {
+ m_level = value;
+ updateLevels = true;
+ } else if (property == MappedAudioBuss::Pan) {
+ m_pan = value;
+ updateLevels = true;
+ } else if (property == MappedConnectableObject::ConnectionsIn) {
+ m_connectionsIn.clear();
+ m_connectionsIn.push_back(value);
+ } else if (property == MappedConnectableObject::ConnectionsOut) {
+ m_connectionsOut.clear();
+ m_connectionsOut.push_back(value);
+ } else {
+#ifdef DEBUG_MAPPEDSTUDIO
+ std::cerr << "MappedAudioBuss::setProperty - "
+ << "unsupported property" << std::endl;
+#endif
+
+ return ;
+ }
+
+ if (updateLevels) {
+ MappedStudio *studio =
+ dynamic_cast<MappedStudio*>(getParent());
+
+ if (studio) {
+ studio->getSoundDriver()->setAudioBussLevels
+ (m_bussId, m_level, m_pan);
+ }
+ }
+}
+
+std::vector<InstrumentId>
+MappedAudioBuss::getInstruments()
+{
+ std::vector<InstrumentId> rv;
+
+ GET_LOCK;
+
+ MappedObject *studioObject = getParent();
+ while (!dynamic_cast<MappedStudio *>(studioObject))
+ studioObject = studioObject->getParent();
+
+ std::vector<MappedObject *> objects =
+ static_cast<MappedStudio *>(studioObject)->
+ getObjectsOfType(MappedObject::AudioFader);
+
+ for (std::vector<MappedObject *>::iterator i = objects.begin();
+ i != objects.end(); ++i) {
+ MappedAudioFader *fader = dynamic_cast<MappedAudioFader *>(*i);
+ if (fader) {
+ MappedObjectValueList connections = fader->getConnections
+ (MappedConnectableObject::Out);
+ if (!connections.empty() && (*connections.begin() == getId())) {
+ rv.push_back(fader->getInstrument());
+ }
+ }
+ }
+
+ RELEASE_LOCK;
+
+ return rv;
+}
+
+
+// ---------------- MappedAudioInput -------------------
+//
+//
+MappedAudioInput::MappedAudioInput(MappedObject *parent,
+ MappedObjectId id) :
+ MappedConnectableObject(parent,
+ "MappedAudioInput",
+ AudioInput,
+ id)
+{}
+
+MappedAudioInput::~MappedAudioInput()
+{}
+
+MappedObjectPropertyList
+MappedAudioInput::getPropertyList(const MappedObjectProperty &property)
+{
+ MappedObjectPropertyList list;
+
+ if (property == "") {
+ list.push_back(MappedAudioInput::InputNumber);
+ } else if (property == InputNumber) {
+ list.push_back(MappedObjectProperty("%1").arg(m_inputNumber));
+ }
+
+ return list;
+}
+
+bool
+MappedAudioInput::getProperty(const MappedObjectProperty &property,
+ MappedObjectValue &value)
+{
+ if (property == InputNumber) {
+ value = m_inputNumber;
+ } else {
+#ifdef DEBUG_MAPPEDSTUDIO
+ std::cerr << "MappedAudioInput::getProperty - "
+ << "no properties available" << std::endl;
+#endif
+
+ }
+ return false;
+}
+
+void
+MappedAudioInput::setProperty(const MappedObjectProperty &property,
+ MappedObjectValue value)
+{
+ if (property == InputNumber) {
+ m_inputNumber = value;
+ } else {
+#ifdef DEBUG_MAPPEDSTUDIO
+ std::cerr << "MappedAudioInput::setProperty - "
+ << "no properties available" << std::endl;
+#endif
+
+ }
+ return ;
+}
+
+
+MappedPluginSlot::MappedPluginSlot(MappedObject *parent, MappedObjectId id) :
+ MappedObject(parent, "MappedPluginSlot", PluginSlot, id)
+{
+#ifdef DEBUG_MAPPEDSTUDIO
+ std::cerr << "MappedPluginSlot::MappedPluginSlot: id = " << id << std::endl;
+#endif
+}
+
+MappedPluginSlot::~MappedPluginSlot()
+{
+#ifdef DEBUG_MAPPEDSTUDIO
+ std::cerr << "MappedPluginSlot::~MappedPluginSlot: id = " << getId() << ", identifier = " << m_identifier << std::endl;
+#endif
+
+ if (m_identifier != "") {
+
+ // shut down and remove the plugin instance we have running
+
+ MappedStudio *studio =
+ dynamic_cast<MappedStudio*>(getParent());
+
+ if (studio) {
+ SoundDriver *drv = studio->getSoundDriver();
+
+ if (drv) {
+ drv->removePluginInstance(m_instrument, m_position);
+ }
+ }
+ }
+}
+
+MappedObjectPropertyList
+MappedPluginSlot::getPropertyList(const MappedObjectProperty &property)
+{
+ MappedObjectPropertyList list;
+
+ if (property == "") {
+ list.push_back(PortCount);
+ list.push_back(Instrument);
+ list.push_back(Bypassed);
+ list.push_back(PluginName);
+ list.push_back(Label);
+ list.push_back(Author);
+ list.push_back(Copyright);
+ list.push_back(Category);
+ } else if (property == Programs) {
+
+ // The set of available programs is dynamic -- it can change
+ // while a plugin is instantiated. So we query it on demand
+ // each time.
+
+ MappedStudio *studio =
+ dynamic_cast<MappedStudio*>(getParent());
+
+ if (studio) {
+ QStringList programs =
+ studio->getSoundDriver()->getPluginInstancePrograms(m_instrument,
+ m_position);
+
+ for (int i = 0; i < int(programs.count()); ++i) {
+ list.push_back(programs[i]);
+ }
+ }
+
+ } else {
+ std::cerr << "MappedPluginSlot::getPropertyList: not a list property"
+ << std::endl;
+ }
+
+ return list;
+}
+
+bool
+MappedPluginSlot::getProperty(const MappedObjectProperty &property,
+ MappedObjectValue &value)
+{
+ if (property == PortCount) {
+ value = m_portCount;
+ } else if (property == Instrument) {
+ value = m_instrument;
+ } else if (property == Position) {
+ value = m_position;
+ } else if (property == Bypassed) {
+ value = m_bypassed;
+ } else {
+#ifdef DEBUG_MAPPEDSTUDIO
+ std::cerr << "MappedPluginSlot::getProperty - "
+ << "unsupported or non-scalar property" << std::endl;
+#endif
+
+ return false;
+ }
+ return true;
+}
+
+bool
+MappedPluginSlot::getProperty(const MappedObjectProperty &property,
+ QString &value)
+{
+ if (property == Identifier) {
+ value = m_identifier;
+ } else if (property == PluginName) {
+ value = m_name;
+ } else if (property == Label) {
+ value = m_label;
+ } else if (property == Author) {
+ value = m_author;
+ } else if (property == Copyright) {
+ value = m_copyright;
+ } else if (property == Category) {
+ value = m_category;
+ } else if (property == Program) {
+
+ MappedStudio *studio =
+ dynamic_cast<MappedStudio*>(getParent());
+
+ if (studio) {
+ value = studio->getSoundDriver()->getPluginInstanceProgram(m_instrument,
+ m_position);
+ }
+ } else {
+#ifdef DEBUG_MAPPEDSTUDIO
+ std::cerr << "MappedPluginSlot::getProperty - "
+ << "unsupported or non-scalar property" << std::endl;
+#endif
+
+ return false;
+ }
+ return true;
+}
+
+QString
+MappedPluginSlot::getProgram(int bank, int program)
+{
+ MappedStudio *studio =
+ dynamic_cast<MappedStudio*>(getParent());
+
+ if (studio) {
+ return
+ studio->getSoundDriver()->getPluginInstanceProgram(m_instrument,
+ m_position,
+ bank,
+ program);
+ }
+
+ return QString();
+}
+
+unsigned long
+MappedPluginSlot::getProgram(QString name)
+{
+ MappedStudio *studio =
+ dynamic_cast<MappedStudio*>(getParent());
+
+ if (studio) {
+ return
+ studio->getSoundDriver()->getPluginInstanceProgram(m_instrument,
+ m_position,
+ name);
+ }
+
+ return 0;
+}
+
+void
+MappedPluginSlot::setProperty(const MappedObjectProperty &property,
+ MappedObjectValue value)
+{
+ if (property == Instrument) {
+ m_instrument = InstrumentId(value);
+ } else if (property == PortCount) {
+ m_portCount = int(value);
+ } else if (property == Position) {
+ m_position = int(value);
+ } else if (property == Bypassed) {
+ m_bypassed = bool(value);
+
+ MappedStudio *studio =
+ dynamic_cast<MappedStudio*>(getParent());
+
+ if (studio) {
+ studio->getSoundDriver()->setPluginInstanceBypass(m_instrument,
+ m_position,
+ m_bypassed);
+ }
+ }
+}
+
+void
+MappedPluginSlot::setProperty(const MappedObjectProperty &property,
+ QString value)
+{
+ if (property == Identifier) {
+
+ if (m_identifier == value)
+ return ;
+
+ // shut down and remove the plugin instance we have running
+
+ MappedStudio *studio =
+ dynamic_cast<MappedStudio*>(getParent());
+
+ if (studio) {
+ SoundDriver *drv = studio->getSoundDriver();
+
+ if (drv) {
+
+ // We don't call drv->removePluginInstance at this
+ // point: the sequencer will deal with that when we
+ // call setPluginInstance below. If we removed the
+ // instance here, we might cause the library we want
+ // for the new plugin instance to be unloaded and then
+ // loaded again, which is hardly the most efficient.
+
+ m_identifier = value;
+
+ // populate myself and my ports
+ PluginFactory *factory = PluginFactory::instanceFor(m_identifier);
+ if (!factory) {
+ std::cerr << "WARNING: MappedPluginSlot::setProperty(identifier): No plugin factory for identifier " << m_identifier << "!" << std::endl;
+ m_identifier = "";
+ return ;
+ }
+
+ factory->populatePluginSlot(m_identifier, *this);
+
+ // now create the new instance
+ drv->setPluginInstance(m_instrument,
+ m_identifier,
+ m_position);
+ }
+ }
+
+ m_configuration.clear();
+
+ } else if (property == PluginName) {
+ m_name = value;
+ } else if (property == Label) {
+ m_label = value;
+ } else if (property == Author) {
+ m_author = value;
+ } else if (property == Copyright) {
+ m_copyright = value;
+ } else if (property == Category) {
+ m_category = value;
+ } else if (property == Program) {
+
+ MappedStudio *studio =
+ dynamic_cast<MappedStudio*>(getParent());
+
+ if (studio) {
+ studio->getSoundDriver()->setPluginInstanceProgram(m_instrument,
+ m_position,
+ value);
+ }
+ } else {
+
+#ifdef DEBUG_MAPPEDSTUDIO
+ std::cerr << "MappedPluginSlot::setProperty - "
+ << "unsupported or non-scalar property" << std::endl;
+#endif
+
+ }
+}
+
+void
+MappedPluginSlot::setPropertyList(const MappedObjectProperty &property,
+ const MappedObjectPropertyList &values)
+{
+ if (property == Configuration) {
+
+#ifdef DEBUG_MAPPEDSTUDIO
+ std::cerr << "MappedPluginSlot::setPropertyList(configuration): configuration is:" << std::endl;
+#endif
+
+ MappedStudio *studio =
+ dynamic_cast<MappedStudio*>(getParent());
+
+ for (MappedObjectPropertyList::const_iterator i = values.begin();
+ i != values.end(); ++i) {
+
+ QString key = *i;
+ QString value = *++i;
+
+#ifdef DEBUG_MAPPEDSTUDIO
+
+ std::cerr << key << " = " << value << std::endl;
+#endif
+
+ if (m_configuration.find(key) != m_configuration.end() &&
+ m_configuration[key] == value)
+ continue;
+
+ if (studio) {
+ QString rv =
+ studio->getSoundDriver()->configurePlugin(m_instrument,
+ m_position,
+ key, value);
+ if (rv && rv != "") {
+ throw(rv);
+ }
+ }
+ }
+
+ m_configuration.clear();
+
+ for (MappedObjectPropertyList::const_iterator i = values.begin();
+ i != values.end(); ++i) {
+
+ QString key = *i;
+ QString value = *++i;
+
+ m_configuration[key] = value;
+ }
+ } else {
+
+#ifdef DEBUG_MAPPEDSTUDIO
+ std::cerr << "MappedPluginSlot::setPropertyList - "
+ << "not a list property" << std::endl;
+#endif
+
+ }
+}
+
+void
+MappedPluginSlot::setPort(unsigned long portNumber, float value)
+{
+ std::vector<MappedObject*> ports = getChildObjects();
+ std::vector<MappedObject*>::iterator it = ports.begin();
+ MappedPluginPort *port = 0;
+
+ for (; it != ports.end(); it++) {
+ port = dynamic_cast<MappedPluginPort *>(*it);
+ if (port && (unsigned long)port->getPortNumber() == portNumber) {
+ port->setValue(value);
+ }
+ }
+}
+
+float
+MappedPluginSlot::getPort(unsigned long portNumber)
+{
+ std::vector<MappedObject*> ports = getChildObjects();
+ std::vector<MappedObject*>::iterator it = ports.begin();
+ MappedPluginPort *port = 0;
+
+ for (; it != ports.end(); it++) {
+ port = dynamic_cast<MappedPluginPort *>(*it);
+ if (port && (unsigned long)port->getPortNumber() == portNumber) {
+ return port->getValue();
+ }
+ }
+
+ return 0;
+}
+
+
+MappedPluginPort::MappedPluginPort(MappedObject *parent, MappedObjectId id) :
+ MappedObject(parent, "MappedPluginPort", PluginPort, id)
+{}
+
+MappedPluginPort::~MappedPluginPort()
+{}
+
+MappedObjectPropertyList
+MappedPluginPort::getPropertyList(const MappedObjectProperty &property)
+{
+ MappedObjectPropertyList list;
+
+ if (property == "") {
+ list.push_back(PortNumber);
+ list.push_back(Minimum);
+ list.push_back(Maximum);
+ list.push_back(Default);
+ list.push_back(DisplayHint);
+ list.push_back(Value);
+ list.push_back(Name);
+ } else {
+ std::cerr << "MappedPluginSlot::getPropertyList: not a list property"
+ << std::endl;
+ }
+
+ return list;
+}
+
+bool
+MappedPluginPort::getProperty(const MappedObjectProperty &property,
+ MappedObjectValue &value)
+{
+ if (property == PortNumber) {
+ value = m_portNumber;
+ } else if (property == Minimum) {
+ value = m_minimum;
+ } else if (property == Maximum) {
+ value = m_maximum;
+ } else if (property == Default) {
+ value = m_default;
+ } else if (property == DisplayHint) {
+ value = m_displayHint;
+ } else if (property == Value) {
+ return getValue();
+ } else {
+#ifdef DEBUG_MAPPEDSTUDIO
+ std::cerr << "MappedPluginPort::getProperty - "
+ << "unsupported or non-scalar property" << std::endl;
+#endif
+
+ return false;
+ }
+ return true;
+}
+
+bool
+MappedPluginPort::getProperty(const MappedObjectProperty &property,
+ QString &value)
+{
+ if (property == Name) {
+ value = m_name;
+ } else {
+
+#ifdef DEBUG_MAPPEDSTUDIO
+ std::cerr << "MappedPluginPort::getProperty - "
+ << "unsupported or non-scalar property" << std::endl;
+#endif
+
+ return false;
+ }
+ return true;
+}
+
+void
+MappedPluginPort::setValue(MappedObjectValue value)
+{
+ MappedPluginSlot *slot =
+ dynamic_cast<MappedPluginSlot *>(getParent());
+
+ if (slot) {
+
+ MappedStudio *studio =
+ dynamic_cast<MappedStudio *>(slot->getParent());
+
+ if (studio) {
+ SoundDriver *drv = studio->getSoundDriver();
+
+ if (drv) {
+ drv->setPluginInstancePortValue(slot->getInstrument(),
+ slot->getPosition(),
+ m_portNumber, value);
+ }
+ }
+ }
+}
+
+float
+MappedPluginPort::getValue() const
+{
+ const MappedPluginSlot *slot =
+ dynamic_cast<const MappedPluginSlot *>(getParent());
+
+ if (slot) {
+
+ const MappedStudio *studio =
+ dynamic_cast<const MappedStudio *>(slot->getParent());
+
+ if (studio) {
+ SoundDriver *drv =
+ const_cast<SoundDriver *>(studio->getSoundDriver());
+
+ if (drv) {
+ return drv->getPluginInstancePortValue(slot->getInstrument(),
+ slot->getPosition(),
+ m_portNumber);
+ }
+ }
+ }
+
+ return 0;
+}
+
+void
+MappedPluginPort::setProperty(const MappedObjectProperty &property,
+ MappedObjectValue value)
+{
+ if (property == PortNumber) {
+ m_portNumber = int(value);
+ } else if (property == Minimum) {
+ m_minimum = value;
+ } else if (property == Maximum) {
+ m_maximum = value;
+ } else if (property == Default) {
+ m_default = value;
+ } else if (property == DisplayHint) {
+ m_displayHint = PluginPort::PortDisplayHint(value);
+ } else if (property == Value) {
+ setValue(value);
+ } else {
+#ifdef DEBUG_MAPPEDSTUDIO
+ std::cerr << "MappedPluginPort::setProperty - "
+ << "unsupported or non-scalar property" << std::endl;
+#endif
+
+ }
+}
+
+
+void
+MappedPluginPort::setProperty(const MappedObjectProperty &property,
+ QString value)
+{
+ if (property == Name) {
+ m_name = value;
+ } else {
+
+#ifdef DEBUG_MAPPEDSTUDIO
+ std::cerr << "MappedPluginPort::setProperty - "
+ << "unsupported or non-scalar property" << std::endl;
+#endif
+
+ }
+}
+
+
+}
+
+
+
diff --git a/src/sound/MappedStudio.h b/src/sound/MappedStudio.h
new file mode 100644
index 0000000..0896e6b
--- /dev/null
+++ b/src/sound/MappedStudio.h
@@ -0,0 +1,552 @@
+// -*- c-indentation-style:"stroustrup" c-basic-offset: 4 -*-
+
+/*
+ Rosegarden
+ A sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <bownie@bownie.com>
+
+ 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 <map>
+#include <string>
+#include <vector>
+#include <qdatastream.h>
+#include <qstring.h>
+
+#include "MappedCommon.h"
+#include "Instrument.h"
+#include "Device.h"
+
+#include "AudioPluginInstance.h" // for PluginPort::PortDisplayHint //!!!???
+
+#ifndef _MAPPEDSTUDIO_H_
+#define _MAPPEDSTUDIO_H_
+
+
+// A sequencer-side representation of certain elements in the
+// gui that enables us to control outgoing or incoming audio
+// and MIDI with run-time only persistence. Placeholders for
+// our Studio elements on the sequencer.
+
+namespace Rosegarden
+{
+
+class SoundDriver;
+
+
+// Types are in MappedCommon.h
+//
+class MappedObject
+{
+public:
+
+ // Some common properties
+ //
+ static const MappedObjectProperty Name;
+ static const MappedObjectProperty Instrument;
+ static const MappedObjectProperty Position;
+
+ // The object we can create
+ //
+ typedef enum
+ {
+ Studio,
+ AudioFader, // connectable fader - interfaces with devices
+ AudioBuss, // connectable buss - inferfaces with faders
+ AudioInput, // connectable record input
+ PluginSlot,
+ PluginPort
+
+ } MappedObjectType;
+
+ MappedObject(MappedObject *parent,
+ const std::string &name,
+ MappedObjectType type,
+ MappedObjectId id):
+ m_type(type),
+ m_id(id),
+ m_name(name),
+ m_parent(parent) {;}
+
+ virtual ~MappedObject() {;}
+
+ MappedObjectId getId() { return m_id; }
+ MappedObjectType getType() { return m_type; }
+
+ std::string getName() { return m_name; }
+ void setName(const std::string &name) { m_name= name; }
+
+ // Get and set properties
+ //
+ virtual MappedObjectPropertyList
+ getPropertyList(const MappedObjectProperty &property) = 0;
+
+ virtual bool getProperty(const MappedObjectProperty &property,
+ MappedObjectValue &value) = 0;
+
+ // Only relevant to objects that have string properties
+ //
+ virtual bool getProperty(const MappedObjectProperty &/* property */,
+ QString &/* value */) { return false; }
+
+ virtual void setProperty(const MappedObjectProperty &property,
+ MappedObjectValue value) = 0;
+
+ // Only relevant to objects that have string properties
+ //
+ virtual void setProperty(const MappedObjectProperty &/* property */,
+ QString /* value */) { }
+
+ // Only relevant to objects that have list properties
+ //
+ virtual void setPropertyList(const MappedObjectProperty &/* property */,
+ const MappedObjectPropertyList &/* values */) { }
+
+ // Ownership
+ //
+ MappedObject* getParent() { return m_parent; }
+ const MappedObject* getParent() const { return m_parent; }
+ void setParent(MappedObject *parent) { m_parent = parent; }
+
+ // Get a list of child ids - get a list of a certain type
+ //
+ MappedObjectPropertyList getChildren();
+ MappedObjectPropertyList getChildren(MappedObjectType type);
+
+ // Child management
+ //
+ void addChild(MappedObject *mO);
+ void removeChild(MappedObject *mO);
+
+ // Destruction
+ //
+ void destroy();
+ void destroyChildren();
+
+ std::vector<MappedObject*> getChildObjects() { return m_children; }
+
+protected:
+
+ MappedObjectType m_type;
+ MappedObjectId m_id;
+ std::string m_name;
+
+ MappedObject *m_parent;
+ std::vector<MappedObject*> m_children;
+};
+
+
+class MappedAudioFader;
+class MappedAudioBuss;
+class MappedAudioInput;
+
+// Works as a factory and virtual plug-board for all our other
+// objects whether they be MIDI or audio.
+//
+//
+//
+class MappedStudio : public MappedObject
+{
+public:
+ MappedStudio();
+ ~MappedStudio();
+
+ // Create a new slider of a certain type for a certain
+ // type of device.
+ //
+ MappedObject* createObject(MappedObjectType type);
+
+ // And create an object with a specified id
+ //
+ MappedObject* createObject(MappedObjectType type,
+ MappedObjectId id);
+
+ bool connectObjects(MappedObjectId mId1, MappedObjectId mId2);
+ bool disconnectObjects(MappedObjectId mId1, MappedObjectId mId2);
+ bool disconnectObject(MappedObjectId mId);
+
+ // Destroy a MappedObject by ID
+ //
+ bool destroyObject(MappedObjectId id);
+
+ // Get an object by ID only
+ //
+ MappedObject* getObjectById(MappedObjectId);
+
+ // Get an object by ID and type. (Returns 0 if the ID does not
+ // exist or exists but is not of the correct type.) This is
+ // faster than getObjectById if you know the type already.
+ //
+ MappedObject* getObjectByIdAndType(MappedObjectId, MappedObjectType);
+
+ // Get an arbitrary object of a given type - to see if any exist
+ //
+ MappedObject* getObjectOfType(MappedObjectType type);
+
+ // Find out how many objects there are of a certain type
+ //
+ unsigned int getObjectCount(MappedObjectType type);
+
+ // iterators
+ MappedObject* getFirst(MappedObjectType type);
+ MappedObject* getNext(MappedObject *object);
+
+ std::vector<MappedObject *> getObjectsOfType(MappedObjectType type);
+
+ // Empty the studio of everything
+ //
+ void clear();
+
+ // Clear a MappedObject reference from the Studio
+ //
+ bool clearObject(MappedObjectId id);
+
+ // Property list
+ //
+ virtual MappedObjectPropertyList getPropertyList(
+ const MappedObjectProperty &property);
+
+ virtual bool getProperty(const MappedObjectProperty &property,
+ MappedObjectValue &value);
+
+ virtual void setProperty(const MappedObjectProperty &property,
+ MappedObjectValue value);
+
+ // Get an audio fader for an InstrumentId. Convenience function.
+ //
+ MappedAudioFader *getAudioFader(InstrumentId id);
+ MappedAudioBuss *getAudioBuss(int bussNumber); // not buss no., not object id
+ MappedAudioInput *getAudioInput(int inputNumber); // likewise
+
+ // Return the object vector
+ //
+ //std::vector<MappedObject*>* getObjects() const { return &m_objects; }
+
+ // DCOP streaming
+ //
+ /* dunno if we need this
+ friend QDataStream& operator>>(QDataStream &dS, MappedStudio *mS);
+ friend QDataStream& operator<<(QDataStream &dS, MappedStudio *mS);
+ friend QDataStream& operator>>(QDataStream &dS, MappedStudio &mS);
+ friend QDataStream& operator<<(QDataStream &dS, const MappedStudio &mS);
+ */
+
+
+ // Set the driver object so that we can do things like
+ // initialise plugins etc.
+ //
+ SoundDriver* getSoundDriver() { return m_soundDriver; }
+ const SoundDriver* getSoundDriver() const { return m_soundDriver; }
+ void setSoundDriver(SoundDriver *driver) { m_soundDriver = driver; }
+
+protected:
+
+private:
+
+ // We give everything we create a unique MappedObjectId for
+ // this session. So store the running total in here.
+ //
+ MappedObjectId m_runningObjectId;
+
+ // All of our mapped (virtual) studio resides in this container as
+ // well as having all their parent/child relationships. Because
+ // some things are just blobs with no connections we need to
+ // maintain both - don't forget about this.
+ //
+ // Note that object IDs are globally unique, not just unique within
+ // a category.
+ //
+ typedef std::map<MappedObjectId, MappedObject *> MappedObjectCategory;
+ typedef std::map<MappedObjectType, MappedObjectCategory> MappedObjectMap;
+ MappedObjectMap m_objects;
+
+ // Driver object
+ //
+ SoundDriver *m_soundDriver;
+};
+
+
+// A connectable AudioObject that provides a connection framework
+// for MappedAudioFader and MappedAudioBuss (for example). An
+// abstract base class.
+//
+// n input connections and m output connections - subclasses
+// can do the cleverness if n != m
+//
+
+class MappedConnectableObject : public MappedObject
+{
+public:
+ static const MappedObjectProperty ConnectionsIn;
+ static const MappedObjectProperty ConnectionsOut;
+
+ typedef enum
+ {
+ In,
+ Out
+ } ConnectionDirection;
+
+ MappedConnectableObject(MappedObject *parent,
+ const std::string &name,
+ MappedObjectType type,
+ MappedObjectId id);
+
+ ~MappedConnectableObject();
+
+ void setConnections(ConnectionDirection dir,
+ MappedObjectValueList conns);
+
+ void addConnection(ConnectionDirection dir, MappedObjectId id);
+ void removeConnection(ConnectionDirection dir, MappedObjectId id);
+
+ MappedObjectValueList getConnections (ConnectionDirection dir);
+
+protected:
+
+ // Which audio connections we have
+ //
+ MappedObjectValueList m_connectionsIn;
+ MappedObjectValueList m_connectionsOut;
+};
+
+// Audio fader
+//
+class MappedAudioFader : public MappedConnectableObject
+{
+public:
+ static const MappedObjectProperty Channels;
+
+ // properties
+ //
+ static const MappedObjectProperty FaderLevel;
+ static const MappedObjectProperty FaderRecordLevel;
+ static const MappedObjectProperty Pan;
+ static const MappedObjectProperty InputChannel;
+
+ MappedAudioFader(MappedObject *parent,
+ MappedObjectId id,
+ MappedObjectValue channels = 2); // stereo default
+ ~MappedAudioFader();
+
+ virtual MappedObjectPropertyList getPropertyList(
+ const MappedObjectProperty &property);
+
+ virtual bool getProperty(const MappedObjectProperty &property,
+ MappedObjectValue &value);
+
+ virtual void setProperty(const MappedObjectProperty &property,
+ MappedObjectValue value);
+
+ InstrumentId getInstrument() const { return m_instrumentId; }
+
+protected:
+
+ MappedObjectValue m_level;
+ MappedObjectValue m_recordLevel;
+ InstrumentId m_instrumentId;
+
+ // Stereo pan (-1.0 to +1.0)
+ //
+ MappedObjectValue m_pan;
+
+ // How many channels we carry
+ //
+ MappedObjectValue m_channels;
+
+ // If we have an input, which channel we take from it (if we are
+ // a mono fader at least)
+ //
+ MappedObjectValue m_inputChannel;
+};
+
+class MappedAudioBuss : public MappedConnectableObject
+{
+public:
+ // A buss is much simpler than an instrument fader. It's always
+ // stereo, and just has a level and pan associated with it. The
+ // level may be a submaster fader level or a send mix level, it
+ // depends on what the purpose of the buss is. At the moment we
+ // just have a 1-1 relationship between busses and submasters, and
+ // no send channels.
+
+ static const MappedObjectProperty BussId;
+ static const MappedObjectProperty Pan;
+ static const MappedObjectProperty Level;
+
+ MappedAudioBuss(MappedObject *parent,
+ MappedObjectId id);
+ ~MappedAudioBuss();
+
+ virtual MappedObjectPropertyList getPropertyList(
+ const MappedObjectProperty &property);
+
+ virtual bool getProperty(const MappedObjectProperty &property,
+ MappedObjectValue &value);
+
+ virtual void setProperty(const MappedObjectProperty &property,
+ MappedObjectValue value);
+
+ MappedObjectValue getBussId() { return m_bussId; }
+
+ // super-convenience function: retrieve the ids of the instruments
+ // connected to this buss
+ std::vector<InstrumentId> getInstruments();
+
+protected:
+ int m_bussId;
+ MappedObjectValue m_level;
+ MappedObjectValue m_pan;
+};
+
+class MappedAudioInput : public MappedConnectableObject
+{
+public:
+ // An input is simpler still -- no properties at all, apart from
+ // the input number, otherwise just the connections
+
+ static const MappedObjectProperty InputNumber;
+
+ MappedAudioInput(MappedObject *parent,
+ MappedObjectId id);
+ ~MappedAudioInput();
+
+ virtual MappedObjectPropertyList getPropertyList(
+ const MappedObjectProperty &property);
+
+ virtual bool getProperty(const MappedObjectProperty &property,
+ MappedObjectValue &value);
+
+ virtual void setProperty(const MappedObjectProperty &property,
+ MappedObjectValue value);
+
+ MappedObjectValue getInputNumber() { return m_inputNumber; }
+
+protected:
+ MappedObjectValue m_inputNumber;
+};
+
+class MappedPluginSlot : public MappedObject
+{
+public:
+ static const MappedObjectProperty Identifier;
+ static const MappedObjectProperty PluginName;
+ static const MappedObjectProperty Label;
+ static const MappedObjectProperty Author;
+ static const MappedObjectProperty Copyright;
+ static const MappedObjectProperty Category;
+ static const MappedObjectProperty PortCount;
+ static const MappedObjectProperty Ports;
+ static const MappedObjectProperty Program;
+ static const MappedObjectProperty Programs; // list property
+ static const MappedObjectProperty Instrument;
+ static const MappedObjectProperty Position;
+ static const MappedObjectProperty Bypassed;
+ static const MappedObjectProperty Configuration; // list property
+
+ MappedPluginSlot(MappedObject *parent, MappedObjectId id);
+ ~MappedPluginSlot();
+
+ virtual MappedObjectPropertyList getPropertyList(
+ const MappedObjectProperty &property);
+
+ virtual bool getProperty(const MappedObjectProperty &property,
+ MappedObjectValue &value);
+
+ virtual bool getProperty(const MappedObjectProperty &property,
+ QString &value);
+
+ virtual void setProperty(const MappedObjectProperty &property,
+ MappedObjectValue value);
+
+ virtual void setProperty(const MappedObjectProperty &property,
+ QString value);
+
+ virtual void setPropertyList(const MappedObjectProperty &,
+ const MappedObjectPropertyList &);
+
+ void setPort(unsigned long portNumber, float value);
+ float getPort(unsigned long portNumber);
+
+ InstrumentId getInstrument() const { return m_instrument; }
+ int getPosition() const { return m_position; }
+
+ QString getProgram(int bank, int program);
+ unsigned long getProgram(QString name); // rv is bank << 16 + program
+
+protected:
+ QString m_identifier;
+
+ QString m_name;
+ QString m_label;
+ QString m_author;
+ QString m_copyright;
+ QString m_category;
+ unsigned long m_portCount;
+
+ InstrumentId m_instrument;
+ int m_position;
+ bool m_bypassed;
+
+ std::map<QString, QString> m_configuration;
+};
+
+class MappedPluginPort : public MappedObject
+{
+public:
+ static const MappedObjectProperty PortNumber;
+ static const MappedObjectProperty Name;
+ static const MappedObjectProperty Minimum;
+ static const MappedObjectProperty Maximum;
+ static const MappedObjectProperty Default;
+ static const MappedObjectProperty DisplayHint;
+ static const MappedObjectProperty Value;
+
+ MappedPluginPort(MappedObject *parent, MappedObjectId id);
+ ~MappedPluginPort();
+
+ virtual MappedObjectPropertyList getPropertyList(
+ const MappedObjectProperty &property);
+
+ virtual bool getProperty(const MappedObjectProperty &property,
+ MappedObjectValue &value);
+
+ virtual bool getProperty(const MappedObjectProperty &property,
+ QString &value);
+
+ virtual void setProperty(const MappedObjectProperty &property,
+ MappedObjectValue value);
+
+ virtual void setProperty(const MappedObjectProperty &property,
+ QString value);
+
+ void setValue(MappedObjectValue value);
+ MappedObjectValue getValue() const;
+
+ int getPortNumber() const { return m_portNumber; }
+
+protected:
+ int m_portNumber;
+ QString m_name;
+ MappedObjectValue m_minimum;
+ MappedObjectValue m_maximum;
+ MappedObjectValue m_default;
+ PluginPort::PortDisplayHint m_displayHint;
+
+};
+
+
+}
+
+#endif // _MAPPEDSTUDIO_H_
diff --git a/src/sound/Midi.h b/src/sound/Midi.h
new file mode 100644
index 0000000..65bfe93
--- /dev/null
+++ b/src/sound/Midi.h
@@ -0,0 +1,184 @@
+// -*- c-indentation-style:"stroustrup" c-basic-offset: 4 -*-
+/*
+ Rosegarden
+ A sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <bownie@bownie.com>
+
+ 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_MIDI_H_
+#define _ROSEGARDEN_MIDI_H_
+
+#include "Instrument.h" // for MidiByte
+#include <string>
+
+// Yes we use the STL here. Don't worry, it's fine.
+//
+//
+
+namespace Rosegarden
+{
+// Within the namespace we define our static MIDI messages
+// that'll help us create and understand MIDI files.
+//
+// CreateMessageByte(MSG, CHANNEL) = (MSG) | (CHANNEL)
+//
+//
+
+const std::string MIDI_FILE_HEADER = "MThd";
+const std::string MIDI_TRACK_HEADER = "MTrk";
+
+const MidiByte MIDI_STATUS_BYTE_MASK = 0x80;
+const MidiByte MIDI_MESSAGE_TYPE_MASK = 0xF0;
+const MidiByte MIDI_CHANNEL_NUM_MASK = 0x0F;
+
+// our basic MIDI messages
+//
+const MidiByte MIDI_NOTE_OFF = 0x80;
+const MidiByte MIDI_NOTE_ON = 0x90;
+const MidiByte MIDI_POLY_AFTERTOUCH = 0xA0;
+const MidiByte MIDI_CTRL_CHANGE = 0xB0;
+const MidiByte MIDI_PROG_CHANGE = 0xC0;
+const MidiByte MIDI_CHNL_AFTERTOUCH = 0xD0;
+const MidiByte MIDI_PITCH_BEND = 0xE0;
+
+// channel mode
+//
+const MidiByte MIDI_SELECT_CHNL_MODE = 0xB0;
+
+// system messages
+const MidiByte MIDI_SYSTEM_EXCLUSIVE = 0xF0;
+const MidiByte MIDI_TC_QUARTER_FRAME = 0xF1;
+const MidiByte MIDI_SONG_POSITION_PTR = 0xF2;
+const MidiByte MIDI_SONG_SELECT = 0xF3;
+const MidiByte MIDI_TUNE_REQUEST = 0xF6;
+const MidiByte MIDI_END_OF_EXCLUSIVE = 0xF7;
+
+const MidiByte MIDI_TIMING_CLOCK = 0xF8;
+const MidiByte MIDI_START = 0xFA;
+const MidiByte MIDI_CONTINUE = 0xFB;
+const MidiByte MIDI_STOP = 0xFC;
+const MidiByte MIDI_ACTIVE_SENSING = 0xFE;
+const MidiByte MIDI_SYSTEM_RESET = 0xFF;
+
+// System Exclusive Extensions
+//
+
+// Non-commercial use
+//
+const MidiByte MIDI_SYSEX_NONCOMMERCIAL = 0x7D;
+
+// Universal non-real time use
+// Format:
+//
+// 0xF0 0x7E <device id> <sub id #1> <sub id #2> <data> 0xF7
+//
+const MidiByte MIDI_SYSEX_NON_RT = 0x7E;
+
+// RealTime e.g Midi Machine Control (MMC)
+//
+// 0xF0 0x7F <device id> <sub id #1> <sub id #2> <data> 0xF7
+//
+const MidiByte MIDI_SYSEX_RT = 0x7F;
+
+// Sub IDs for RealTime SysExs
+//
+const MidiByte MIDI_SYSEX_RT_COMMAND = 0x06;
+const MidiByte MIDI_SYSEX_RT_RESPONSE = 0x07;
+
+// MMC commands
+//
+const MidiByte MIDI_MMC_STOP = 0x01;
+const MidiByte MIDI_MMC_PLAY = 0x02;
+const MidiByte MIDI_MMC_DEFERRED_PLAY = 0x03;
+const MidiByte MIDI_MMC_FAST_FORWARD = 0x04;
+const MidiByte MIDI_MMC_REWIND = 0x05;
+const MidiByte MIDI_MMC_RECORD_STROBE = 0x06; // punch in
+const MidiByte MIDI_MMC_RECORD_EXIT = 0x07; // punch out
+const MidiByte MIDI_MMC_RECORD_PAUSE = 0x08;
+const MidiByte MIDI_MMC_PAUSE = 0x08;
+const MidiByte MIDI_MMC_EJECT = 0x0A;
+const MidiByte MIDI_MMC_LOCATE = 0x44; // jump to
+
+
+// Midi Event Code for META Event
+//
+const MidiByte MIDI_FILE_META_EVENT = 0xFF;
+
+// META Event Codes
+//
+const MidiByte MIDI_SEQUENCE_NUMBER = 0x00;
+const MidiByte MIDI_TEXT_EVENT = 0x01;
+const MidiByte MIDI_COPYRIGHT_NOTICE = 0x02;
+const MidiByte MIDI_TRACK_NAME = 0x03;
+const MidiByte MIDI_INSTRUMENT_NAME = 0x04;
+const MidiByte MIDI_LYRIC = 0x05;
+const MidiByte MIDI_TEXT_MARKER = 0x06;
+const MidiByte MIDI_CUE_POINT = 0x07;
+const MidiByte MIDI_CHANNEL_PREFIX = 0x20;
+
+// There is contention over what 0x21 really means.
+// It's either a miswritten CHANNEL PREFIX or it's
+// a non-standard PORT MAPPING used by a sequencer.
+// Either way we include it (and generally ignore it)
+// as it's a part of many MIDI files that already
+// exist.
+const MidiByte MIDI_CHANNEL_PREFIX_OR_PORT = 0x21;
+
+const MidiByte MIDI_END_OF_TRACK = 0x2F;
+const MidiByte MIDI_SET_TEMPO = 0x51;
+const MidiByte MIDI_SMPTE_OFFSET = 0x54;
+const MidiByte MIDI_TIME_SIGNATURE = 0x58;
+const MidiByte MIDI_KEY_SIGNATURE = 0x59;
+const MidiByte MIDI_SEQUENCER_SPECIFIC = 0x7F;
+
+// Some controllers
+//
+const MidiByte MIDI_CONTROLLER_BANK_MSB = 0x00;
+const MidiByte MIDI_CONTROLLER_VOLUME = 0x07;
+const MidiByte MIDI_CONTROLLER_BANK_LSB = 0x20;
+const MidiByte MIDI_CONTROLLER_MODULATION = 0x01;
+const MidiByte MIDI_CONTROLLER_PAN = 0x0A;
+const MidiByte MIDI_CONTROLLER_SUSTAIN = 0x40;
+const MidiByte MIDI_CONTROLLER_RESONANCE = 0x47;
+const MidiByte MIDI_CONTROLLER_RELEASE = 0x48;
+const MidiByte MIDI_CONTROLLER_ATTACK = 0x49;
+const MidiByte MIDI_CONTROLLER_FILTER = 0x4A;
+const MidiByte MIDI_CONTROLLER_REVERB = 0x5B;
+const MidiByte MIDI_CONTROLLER_CHORUS = 0x5D;
+
+// Registered and Non-Registered Parameter Controllers
+//
+const MidiByte MIDI_CONTROLLER_NRPN_1 = 0x62;
+const MidiByte MIDI_CONTROLLER_NRPN_2 = 0x63;
+const MidiByte MIDI_CONTROLLER_RPN_1 = 0x64;
+const MidiByte MIDI_CONTROLLER_RPN_2 = 0x65;
+
+const MidiByte MIDI_CONTROLLER_SOUNDS_OFF = 0x78;
+const MidiByte MIDI_CONTROLLER_RESET = 0x79; // reset all controllers
+const MidiByte MIDI_CONTROLLER_LOCAL = 0x7A; // 0 = off, 127 = on
+const MidiByte MIDI_CONTROLLER_ALL_NOTES_OFF = 0x7B;
+
+
+// MIDI percussion channel
+const MidiByte MIDI_PERCUSSION_CHANNEL = 9;
+
+
+
+}
+
+
+#endif // _ROSEGARDEN_MIDI_H_
diff --git a/src/sound/MidiEvent.cpp b/src/sound/MidiEvent.cpp
new file mode 100644
index 0000000..975b7aa
--- /dev/null
+++ b/src/sound/MidiEvent.cpp
@@ -0,0 +1,289 @@
+// -*- c-indentation-style:"stroustrup" c-basic-offset: 4 -*-
+/*
+ Rosegarden
+ A sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <bownie@bownie.com>
+
+ 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 "Midi.h"
+#include "MidiEvent.h"
+#include <iostream>
+
+// MidiEvent is a representation of MIDI which we use
+// for the import/export of MidiFiles. It uses std::strings for
+// meta event messages which makes them nice and easy to handle.
+//
+//
+namespace Rosegarden
+{
+
+using std::string;
+using std::cout;
+using std::endl;
+
+MidiEvent::MidiEvent()
+{}
+
+MidiEvent::MidiEvent(timeT deltaTime,
+ MidiByte eventCode):
+ m_deltaTime(deltaTime),
+ m_duration(0),
+ m_eventCode(eventCode),
+ m_data1(0),
+ m_data2(0),
+ m_metaEventCode(0),
+ m_metaMessage("")
+{}
+
+MidiEvent::MidiEvent(timeT deltaTime,
+ MidiByte eventCode,
+ MidiByte data1):
+ m_deltaTime(deltaTime),
+ m_duration(0),
+ m_eventCode(eventCode),
+ m_data1(data1),
+ m_data2(0),
+ m_metaEventCode(0),
+ m_metaMessage("")
+{}
+
+MidiEvent::MidiEvent(timeT deltaTime,
+ MidiByte eventCode,
+ MidiByte data1,
+ MidiByte data2):
+ m_deltaTime(deltaTime),
+ m_duration(0),
+ m_eventCode(eventCode),
+ m_data1(data1),
+ m_data2(data2),
+ m_metaEventCode(0),
+ m_metaMessage("")
+
+{}
+
+MidiEvent::MidiEvent(timeT deltaTime,
+ MidiByte eventCode,
+ MidiByte metaEventCode,
+ const string &metaMessage):
+ m_deltaTime(deltaTime),
+ m_duration(0),
+ m_eventCode(eventCode),
+ m_data1(0),
+ m_data2(0),
+ m_metaEventCode(metaEventCode),
+ m_metaMessage(metaMessage)
+{}
+
+MidiEvent::MidiEvent(timeT deltaTime,
+ MidiByte eventCode,
+ const string &sysEx):
+ m_deltaTime(deltaTime),
+ m_duration(0),
+ m_eventCode(eventCode),
+ m_data1(0),
+ m_data2(0),
+ m_metaEventCode(0),
+ m_metaMessage(sysEx)
+{}
+
+MidiEvent::~MidiEvent()
+{}
+
+// Show a representation of our MidiEvent purely for information
+// purposes (also demos how we decode them)
+//
+//
+void
+MidiEvent::print()
+{
+ timeT tempo;
+ int tonality;
+ string sharpflat;
+
+ if (m_metaEventCode) {
+ switch (m_metaEventCode) {
+ case MIDI_SEQUENCE_NUMBER:
+ cout << "MIDI SEQUENCE NUMBER" << endl;
+ break;
+
+ case MIDI_TEXT_EVENT:
+ cout << "MIDI TEXT:\t\"" << m_metaMessage << "\"" << endl;
+ break;
+
+ case MIDI_COPYRIGHT_NOTICE:
+ cout << "COPYRIGHT:\t\"" << m_metaMessage << "\"" << endl;
+
+ case MIDI_TRACK_NAME:
+ cout << "TRACK NAME:\t\"" << m_metaMessage << "\"" << endl;
+ break;
+
+ case MIDI_INSTRUMENT_NAME:
+ cout << "INSTRUMENT NAME:\t\"" << m_metaMessage << "\"" << endl;
+ break;
+
+ case MIDI_LYRIC:
+ cout << "LYRIC:\t\"" << m_metaMessage << "\"" << endl;
+ break;
+
+ case MIDI_TEXT_MARKER:
+ cout << "MARKER:\t\"" << m_metaMessage << "\"" << endl;
+ break;
+
+ case MIDI_CUE_POINT:
+ cout << "CUE POINT:\t\"" << m_metaMessage << "\"" << endl;
+ break;
+
+ // Sets a Channel number for a TRACK before it starts
+ case MIDI_CHANNEL_PREFIX:
+ cout << "CHANNEL PREFIX:\t"
+ << (timeT)m_metaMessage[0]
+ << endl;
+ break;
+
+ // These are actually the same case but this is not an
+ // official META event - it just crops up a lot. We
+ // assume it's a MIDI_CHANNEL_PREFIX though
+ //
+ case MIDI_CHANNEL_PREFIX_OR_PORT:
+ cout << "FIXED CHANNEL PREFIX:\t"
+ << (timeT)m_metaMessage[0] << endl;
+ break;
+
+ case MIDI_END_OF_TRACK:
+ cout << "END OF TRACK" << endl;
+ break;
+
+ case MIDI_SET_TEMPO:
+ tempo =
+ ((timeT)(((MidiByte)m_metaMessage[0]) << 16)) +
+ ((timeT)(((MidiByte)m_metaMessage[1]) << 8)) +
+ (short)(MidiByte)m_metaMessage[2];
+
+ tempo = 60000000 / tempo;
+ cout << "SET TEMPO:\t" << tempo << endl;
+ break;
+
+ case MIDI_SMPTE_OFFSET:
+ cout << "SMPTE TIME CODE:\t"
+ << (timeT)m_metaMessage[0]
+ << ":" << (timeT)m_metaMessage[1]
+ << ":" << (timeT)m_metaMessage[2]
+ << " - fps = " << (timeT)m_metaMessage[3]
+ << " - subdivsperframe = "
+ << (timeT)m_metaMessage[4]
+ << endl;
+ break;
+
+ case MIDI_TIME_SIGNATURE:
+ cout << "TIME SIGNATURE:\t"
+ << (timeT)m_metaMessage[0]
+ << "/"
+ << (1 << (timeT)m_metaMessage[1]) << endl;
+ break;
+
+ case MIDI_KEY_SIGNATURE:
+ tonality = (int)m_metaMessage[0];
+
+ if (tonality < 0) {
+ sharpflat = -tonality + " flat";
+ } else {
+ sharpflat = tonality;
+ sharpflat += " sharp";
+ }
+
+ cout << "KEY SIGNATURE:\t" << sharpflat << " "
+ << (((int)m_metaMessage[1]) == 0 ? "major" : "minor")
+ << endl;
+
+ break;
+
+ case MIDI_SEQUENCER_SPECIFIC:
+ cout << "SEQUENCER SPECIFIC:\t\"" << m_metaMessage << endl;
+ break;
+
+
+ default:
+ cout << "Undefined MIDI META event - "
+ << (timeT)m_metaEventCode << endl;
+ break;
+ }
+ } else {
+ switch (m_eventCode & MIDI_MESSAGE_TYPE_MASK) {
+ case MIDI_NOTE_ON:
+ cout << "NOTE ON:\t" << (int)m_data1 << " - "
+ << (int)m_data2 << endl;
+ break;
+
+ case MIDI_NOTE_OFF:
+ cout << "NOTE OFF:\t" << (int)m_data1 << " - "
+ << (int)m_data2 << endl;
+ break;
+
+ case MIDI_POLY_AFTERTOUCH:
+ cout << "POLY AFTERTOUCH:\t" << (int)m_data1
+ << " - " << (int)m_data2 << endl;
+ break;
+
+ case MIDI_CTRL_CHANGE:
+ cout << "CTRL CHANGE:\t" << (int)m_data1
+ << " - " << (int)m_data2 << endl;
+ break;
+
+ case MIDI_PITCH_BEND:
+ cout << "PITCH BEND:\t" << (int)m_data1
+ << " - " << (int)m_data2 << endl;
+ break;
+
+ case MIDI_PROG_CHANGE:
+ cout << "PROG CHANGE:\t" << (int)m_data1 << endl;
+ break;
+
+ case MIDI_CHNL_AFTERTOUCH:
+ cout << "CHNL AFTERTOUCH\t" << (int)m_data1 << endl;
+ break;
+
+ default:
+ cout << "Undefined MIDI event" << endl;
+ break;
+ }
+ }
+
+
+ return ;
+}
+
+// Adds the argument to _deltaTime and returns the result
+// thus aggregating the times as we go aint
+timeT
+MidiEvent::addTime(const timeT &time)
+{
+ m_deltaTime += time;
+ return m_deltaTime;
+}
+
+
+// Compare based on time
+//
+bool
+operator<(const MidiEvent &a, const MidiEvent &b)
+{
+ return a.getTime() < b.getTime();
+}
+
+
+}
+
+
diff --git a/src/sound/MidiEvent.h b/src/sound/MidiEvent.h
new file mode 100644
index 0000000..b2192b4
--- /dev/null
+++ b/src/sound/MidiEvent.h
@@ -0,0 +1,141 @@
+// -*- c-indentation-style:"stroustrup" c-basic-offset: 4 -*-
+/*
+ Rosegarden
+ A sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <bownie@bownie.com>
+
+ 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_MIDI_EVENT_H_
+#define _ROSEGARDEN_MIDI_EVENT_H_
+
+#include "Midi.h"
+#include "Event.h"
+
+// MidiEvent holds MIDI and Event data during MIDI file I/O.
+// We don't use this class at all for playback or recording of MIDI -
+// for that look at MappedEvent and MappedComposition.
+//
+// Rosegarden doesn't have any internal concept of MIDI events, only
+// Events which are a superset of MIDI functionality.
+//
+// Check out Event in base/ for more information.
+//
+//
+//
+
+namespace Rosegarden
+{
+class MidiEvent
+{
+
+public:
+ MidiEvent();
+
+ // No data event
+ //
+ MidiEvent(timeT deltaTime,
+ MidiByte eventCode);
+
+ // single data byte case
+ //
+ MidiEvent(timeT deltaTime,
+ MidiByte eventCode,
+ MidiByte data1);
+
+ // double data byte
+ //
+ MidiEvent(timeT deltaTime,
+ MidiByte eventCode,
+ MidiByte data1,
+ MidiByte data2);
+
+ // Meta event
+ //
+ MidiEvent(timeT deltaTime,
+ MidiByte eventCode,
+ MidiByte metaEventCode,
+ const std::string &metaMessage);
+
+ // Sysex style constructor
+ //
+ MidiEvent(timeT deltaTime,
+ MidiByte eventCode,
+ const std::string &sysEx);
+
+
+ ~MidiEvent();
+
+ // View our event as text
+ //
+ void print();
+
+
+ void setTime(const timeT &time) { m_deltaTime = time; }
+ void setDuration(const timeT& duration) {m_duration = duration;}
+ timeT addTime(const timeT &time);
+
+ MidiByte getMessageType() const
+ { return ( m_eventCode & MIDI_MESSAGE_TYPE_MASK ); }
+
+ MidiByte getChannelNumber() const
+ { return ( m_eventCode & MIDI_CHANNEL_NUM_MASK ); }
+
+ timeT getTime() const { return m_deltaTime; }
+ timeT getDuration() const { return m_duration; }
+
+ MidiByte getPitch() const { return m_data1; }
+ MidiByte getVelocity() const { return m_data2; }
+ MidiByte getData1() const { return m_data1; }
+ MidiByte getData2() const { return m_data2; }
+ MidiByte getEventCode() const { return m_eventCode; }
+
+ bool isMeta() const { return(m_eventCode == MIDI_FILE_META_EVENT); }
+
+ MidiByte getMetaEventCode() const { return m_metaEventCode; }
+ std::string getMetaMessage() const { return m_metaMessage; }
+ void setMetaMessage(const std::string &meta) { m_metaMessage = meta; }
+
+ friend bool operator<(const MidiEvent &a, const MidiEvent &b);
+
+private:
+
+ MidiEvent& operator=(const MidiEvent);
+
+ timeT m_deltaTime;
+ timeT m_duration;
+ MidiByte m_eventCode;
+ MidiByte m_data1; // or Note
+ MidiByte m_data2; // or Velocity
+
+ MidiByte m_metaEventCode;
+ std::string m_metaMessage;
+
+};
+
+// Comparator for sorting
+//
+struct MidiEventCmp
+{
+ bool operator()(const MidiEvent &mE1, const MidiEvent &mE2) const
+ { return mE1.getTime() < mE2.getTime(); }
+ bool operator()(const MidiEvent *mE1, const MidiEvent *mE2) const
+ { return mE1->getTime() < mE2->getTime(); }
+};
+
+}
+
+#endif // _ROSEGARDEN_MIDI_EVENT_H_
diff --git a/src/sound/MidiFile.cpp b/src/sound/MidiFile.cpp
new file mode 100644
index 0000000..76d5c85
--- /dev/null
+++ b/src/sound/MidiFile.cpp
@@ -0,0 +1,2261 @@
+// -*- c-indentation-style:"stroustrup" c-basic-offset: 4 -*-
+
+/*
+ Rosegarden
+ A sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <bownie@bownie.com>
+
+ 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 <iostream>
+#include "misc/Debug.h"
+#include <kapplication.h>
+#include <fstream>
+#include <string>
+#include <cstdio>
+#include <algorithm>
+
+#include "Midi.h"
+#include "MidiFile.h"
+#include "Segment.h"
+#include "NotationTypes.h"
+#include "BaseProperties.h"
+#include "SegmentNotationHelper.h"
+#include "SegmentPerformanceHelper.h"
+#include "CompositionTimeSliceAdapter.h"
+#include "AnalysisTypes.h"
+#include "Track.h"
+#include "Instrument.h"
+#include "Quantizer.h"
+#include "Studio.h"
+#include "MidiTypes.h"
+#include "Profiler.h"
+
+//#define MIDI_DEBUG 1
+
+#if (__GNUC__ < 3)
+#include <strstream>
+#define stringstream strstream
+#else
+#include <sstream>
+#endif
+
+#include <kapp.h>
+
+namespace Rosegarden
+{
+
+using std::string;
+using std::ifstream;
+using std::stringstream;
+using std::cerr;
+using std::endl;
+using std::ends;
+using std::ios;
+
+MidiFile::MidiFile(Studio *studio):
+ SoundFile(std::string("unnamed.mid")),
+ m_timingDivision(0),
+ m_format(MIDI_FILE_NOT_LOADED),
+ m_numberOfTracks(0),
+ m_containsTimeChanges(false),
+ m_trackByteCount(0),
+ m_decrementCount(false),
+ m_studio(studio)
+{}
+
+MidiFile::MidiFile(const std::string &fn,
+ Studio *studio):
+ SoundFile(fn),
+ m_timingDivision(0),
+ m_format(MIDI_FILE_NOT_LOADED),
+ m_numberOfTracks(0),
+ m_containsTimeChanges(false),
+ m_trackByteCount(0),
+ m_decrementCount(false),
+ m_studio(studio)
+{}
+
+// Make sure we clear away the m_midiComposition
+//
+MidiFile::~MidiFile()
+{
+ clearMidiComposition();
+}
+
+
+// A couple of convenience functions. Watch the byte conversions out
+// of the STL strings.
+//
+//
+long
+MidiFile::midiBytesToLong(const string& bytes)
+{
+ if (bytes.length() != 4) {
+#ifdef MIDI_DEBUG
+ std::cerr << "WARNING: Wrong length for long data (" << bytes.length()
+ << ", should be 4)" << endl;
+#endif
+
+ throw (Exception("Wrong length for long data in MIDI stream"));
+ }
+
+ long longRet = ((long)(((MidiByte)bytes[0]) << 24)) |
+ ((long)(((MidiByte)bytes[1]) << 16)) |
+ ((long)(((MidiByte)bytes[2]) << 8)) |
+ ((long)((MidiByte)(bytes[3])));
+
+ std::cerr << "midiBytesToLong(" << int((MidiByte)bytes[0]) << "," << int((MidiByte)bytes[1]) << "," << int((MidiByte)bytes[2]) << "," << int((MidiByte)bytes[3]) << ") -> " << longRet << std::endl;
+
+ return longRet;
+}
+
+int
+MidiFile::midiBytesToInt(const string& bytes)
+{
+ if (bytes.length() != 2) {
+#ifdef MIDI_DEBUG
+ std::cerr << "WARNING: Wrong length for int data (" << bytes.length()
+ << ", should be 2)" << endl;
+#endif
+
+ throw (Exception("Wrong length for int data in MIDI stream"));
+ }
+
+ int intRet = ((int)(((MidiByte)bytes[0]) << 8)) |
+ ((int)(((MidiByte)bytes[1])));
+ return (intRet);
+}
+
+
+
+// Gets a single byte from the MIDI byte stream. For each track
+// section we can read only a specified number of bytes held in
+// m_trackByteCount.
+//
+MidiByte
+MidiFile::getMidiByte(ifstream* midiFile)
+{
+ static int bytesGot = 0; // purely for progress reporting purposes
+
+ if (midiFile->eof()) {
+ throw(Exception("End of MIDI file encountered while reading"));
+ }
+
+ if (m_decrementCount && m_trackByteCount <= 0) {
+ throw(Exception("Attempt to get more bytes than expected on Track"));
+ }
+
+ char byte;
+ if (midiFile->read(&byte, 1)) {
+
+ --m_trackByteCount;
+
+ // update a progress dialog if we have one
+ //
+ ++bytesGot;
+ if (bytesGot % 2000 == 0) {
+
+ emit setProgress((int)(double(midiFile->tellg()) /
+ double(m_fileSize) * 20.0));
+ kapp->processEvents(50);
+ }
+
+ return (MidiByte)byte;
+ }
+
+ throw(Exception("Attempt to read past MIDI file end"));
+}
+
+
+// Gets a specified number of bytes from the MIDI byte stream. For
+// each track section we can read only a specified number of bytes
+// held in m_trackByteCount.
+//
+string
+MidiFile::getMidiBytes(ifstream* midiFile, unsigned long numberOfBytes)
+{
+ string stringRet;
+ char fileMidiByte;
+ static int bytesGot = 0; // purely for progress reporting purposes
+
+ if (midiFile->eof()) {
+#ifdef MIDI_DEBUG
+ std::cerr << "MIDI file EOF - got "
+ << stringRet.length() << " bytes out of "
+ << numberOfBytes << endl;
+#endif
+
+ throw(Exception("End of MIDI file encountered while reading"));
+
+ }
+
+ if (m_decrementCount && (numberOfBytes > (unsigned long)m_trackByteCount)) {
+#ifdef MIDI_DEBUG
+ std::cerr << "Attempt to get more bytes than allowed on Track ("
+ << numberOfBytes
+ << " > "
+ << m_trackByteCount << endl;
+#endif
+
+ //!!! Investigate -- I'm seeing this on new-notation-quantization
+ // branch: load glazunov.rg, run Interpret on first segment, export
+ // and attempt to import again
+
+ throw(Exception("Attempt to get more bytes than expected on Track"));
+ }
+
+ while (stringRet.length() < numberOfBytes &&
+ midiFile->read(&fileMidiByte, 1)) {
+ stringRet += fileMidiByte;
+ }
+
+ // if we've reached the end of file without fulfilling the
+ // quota then panic as our parsing has performed incorrectly
+ //
+ if (stringRet.length() < numberOfBytes) {
+ stringRet = "";
+#ifdef MIDI_DEBUG
+
+ cerr << "Attempt to read past file end - got "
+ << stringRet.length() << " bytes out of "
+ << numberOfBytes << endl;
+#endif
+
+ throw(Exception("Attempt to read past MIDI file end"));
+
+ }
+
+ // decrement the byte count
+ if (m_decrementCount)
+ m_trackByteCount -= stringRet.length();
+
+ // update a progress dialog if we have one
+ //
+ bytesGot += numberOfBytes;
+ if (bytesGot % 2000 == 0) {
+ emit setProgress((int)(double(midiFile->tellg()) /
+ double(m_fileSize) * 20.0));
+ kapp->processEvents(50);
+ }
+
+ return stringRet;
+}
+
+
+// Get a long number of variable length from the MIDI byte stream.
+//
+//
+long
+MidiFile::getNumberFromMidiBytes(ifstream* midiFile, int firstByte)
+{
+ long longRet = 0;
+ MidiByte midiByte;
+
+ if (firstByte >= 0) {
+ midiByte = (MidiByte)firstByte;
+ } else if (midiFile->eof()) {
+ return longRet;
+ } else {
+ midiByte = getMidiByte(midiFile);
+ }
+
+ longRet = midiByte;
+ if (midiByte & 0x80 ) {
+ longRet &= 0x7F;
+ do {
+ midiByte = getMidiByte(midiFile);
+ longRet = (longRet << 7) + (midiByte & 0x7F);
+ } while (!midiFile->eof() && (midiByte & 0x80));
+ }
+
+ return longRet;
+}
+
+
+
+// Seeks to the next track in the midi file and sets the number
+// of bytes to be read in the counter m_trackByteCount.
+//
+bool
+MidiFile::skipToNextTrack(ifstream *midiFile)
+{
+ string buffer, buffer2;
+ m_trackByteCount = -1;
+ m_decrementCount = false;
+
+ while (!midiFile->eof() && (m_decrementCount == false )) {
+ buffer = getMidiBytes(midiFile, 4);
+
+#if (__GNUC__ < 3)
+
+ if (buffer.compare(MIDI_TRACK_HEADER, 0, 4) == 0)
+#else
+
+ if (buffer.compare(0, 4, MIDI_TRACK_HEADER) == 0)
+#endif
+
+ {
+ m_trackByteCount = midiBytesToLong(getMidiBytes(midiFile, 4));
+ m_decrementCount = true;
+ }
+
+ }
+
+ if ( m_trackByteCount == -1 ) // we haven't found a track
+ return (false);
+ else
+ return (true);
+}
+
+
+// Read in a MIDI file. The parsing process throws string
+// exceptions back up here if we run into trouble which we
+// can then pass back out to whoever called us using a nice
+// bool.
+//
+//
+bool
+MidiFile::open()
+{
+ bool retOK = true;
+ m_error = "";
+
+#ifdef MIDI_DEBUG
+
+ std::cerr << "MidiFile::open() : fileName = " << m_fileName.c_str() << endl;
+#endif
+
+ // Open the file
+ ifstream *midiFile = new ifstream(m_fileName.c_str(), ios::in | ios::binary);
+
+ try {
+ if (*midiFile) {
+
+ // Set file size so we can count it off
+ //
+ midiFile->seekg(0, std::ios::end);
+ m_fileSize = midiFile->tellg();
+ midiFile->seekg(0, std::ios::beg);
+
+ // Parse the MIDI header first. The first 14 bytes of the file.
+ if (!parseHeader(getMidiBytes(midiFile, 14))) {
+ m_format = MIDI_FILE_NOT_LOADED;
+ m_error = "Not a MIDI file.";
+ return (false);
+ }
+
+ m_containsTimeChanges = false;
+
+ TrackId i = 0;
+
+ for (unsigned int j = 0; j < m_numberOfTracks; ++j) {
+
+//#ifdef MIDI_DEBUG
+ std::cerr << "Parsing Track " << j << endl;
+//#endif
+
+ if (!skipToNextTrack(midiFile)) {
+#ifdef MIDI_DEBUG
+ cerr << "Couldn't find Track " << j << endl;
+#endif
+
+ m_error = "File corrupted or in non-standard format?";
+ m_format = MIDI_FILE_NOT_LOADED;
+ return (false);
+ }
+
+#ifdef MIDI_DEBUG
+ std::cerr << "Track has " << m_trackByteCount << " bytes" << std::endl;
+#endif
+
+ // Run through the events taking them into our internal
+ // representation.
+ if (!parseTrack(midiFile, i)) {
+//#ifdef MIDI_DEBUG
+ std::cerr << "Track " << j << " parsing failed" << endl;
+//#endif
+
+ m_error = "File corrupted or in non-standard format?";
+ m_format = MIDI_FILE_NOT_LOADED;
+ return (false);
+ }
+
+ ++i; // j is the source track number, i the destination
+ }
+
+ m_numberOfTracks = i;
+ } else {
+ m_error = "File not found or not readable.";
+ m_format = MIDI_FILE_NOT_LOADED;
+ return (false);
+ }
+
+ // Close the file now
+ midiFile->close();
+ } catch (Exception e) {
+#ifdef MIDI_DEBUG
+ std::cerr << "MidiFile::open() - caught exception - "
+ << e.getMessage() << endl;
+#endif
+
+ m_error = e.getMessage();
+ retOK = false;
+ }
+
+ return (retOK);
+}
+
+// Parse and ensure the MIDI Header is legitimate
+//
+//
+bool
+MidiFile::parseHeader(const string &midiHeader)
+{
+ if (midiHeader.size() < 14) {
+#ifdef MIDI_DEBUG
+ std::cerr << "MidiFile::parseHeader() - file header undersized" << endl;
+#endif
+
+ return (false);
+ }
+
+#if (__GNUC__ < 3)
+ if (midiHeader.compare(MIDI_FILE_HEADER, 0, 4) != 0)
+#else
+
+ if (midiHeader.compare(0, 4, MIDI_FILE_HEADER) != 0)
+#endif
+
+ {
+#ifdef MIDI_DEBUG
+ std::cerr << "MidiFile::parseHeader()"
+ << "- file header not found or malformed"
+ << endl;
+#endif
+
+ return (false);
+ }
+
+ if (midiBytesToLong(midiHeader.substr(4, 4)) != 6L) {
+#ifdef MIDI_DEBUG
+ std::cerr << "MidiFile::parseHeader()"
+ << " - header length incorrect"
+ << endl;
+#endif
+
+ return (false);
+ }
+
+ m_format = (MIDIFileFormatType) midiBytesToInt(midiHeader.substr(8, 2));
+ m_numberOfTracks = midiBytesToInt(midiHeader.substr(10, 2));
+ m_timingDivision = midiBytesToInt(midiHeader.substr(12, 2));
+
+ if ( m_format == MIDI_SEQUENTIAL_TRACK_FILE ) {
+#ifdef MIDI_DEBUG
+ std::cerr << "MidiFile::parseHeader()"
+ << "- can't load sequential track file"
+ << endl;
+#endif
+
+ return (false);
+ }
+
+
+#ifdef MIDI_DEBUG
+ if ( m_timingDivision < 0 ) {
+ std::cerr << "MidiFile::parseHeader()"
+ << " - file uses SMPTE timing"
+ << endl;
+ }
+#endif
+
+ return (true);
+}
+
+
+
+// Extract the contents from a MIDI file track and places it into
+// our local map of MIDI events.
+//
+//
+bool
+MidiFile::parseTrack(ifstream* midiFile, TrackId &lastTrackNum)
+{
+ MidiByte midiByte, metaEventCode, data1, data2;
+ MidiByte eventCode = 0x80;
+ std::string metaMessage;
+ unsigned int messageLength;
+ unsigned long deltaTime;
+ unsigned long accumulatedTime = 0;
+
+ // The trackNum passed in to this method is the default track for
+ // all events provided they're all on the same channel. If we find
+ // events on more than one channel, we increment trackNum and record
+ // the mapping from channel to trackNum in this channelTrackMap.
+ // We then return the new trackNum by reference so the calling
+ // method knows we've got more tracks than expected.
+
+ // This would be a vector<TrackId> but TrackId is unsigned
+ // and we need -1 to indicate "not yet used"
+ std::vector<int> channelTrackMap(16, -1);
+
+ // This is used to store the last absolute time found on each track,
+ // allowing us to modify delta-times correctly when separating events
+ // out from one to multiple tracks
+ //
+ std::map<int, unsigned long> trackTimeMap;
+
+ // Meta-events don't have a channel, so we place them in a fixed
+ // track number instead
+ TrackId metaTrack = lastTrackNum;
+
+ // Remember the last non-meta status byte (-1 if we haven't seen one)
+ int runningStatus = -1;
+
+ bool firstTrack = true;
+
+ std::cerr << "Parse track: last track number is " << lastTrackNum << std::endl;
+
+ while (!midiFile->eof() && ( m_trackByteCount > 0 ) ) {
+ if (eventCode < 0x80) {
+#ifdef MIDI_DEBUG
+ cerr << "WARNING: Invalid event code " << eventCode
+ << " in MIDI file" << endl;
+#endif
+
+ throw (Exception("Invalid event code found"));
+ }
+
+ deltaTime = getNumberFromMidiBytes(midiFile);
+
+#ifdef MIDI_DEBUG
+ cerr << "read delta time " << deltaTime << endl;
+#endif
+
+ // Get a single byte
+ midiByte = getMidiByte(midiFile);
+
+ if (!(midiByte & MIDI_STATUS_BYTE_MASK)) {
+ if (runningStatus < 0) {
+ throw (Exception("Running status used for first event in track"));
+ }
+
+ eventCode = (MidiByte)runningStatus;
+ data1 = midiByte;
+
+#ifdef MIDI_DEBUG
+ std::cerr << "using running status (byte " << int(midiByte) << " found)" << std::endl;
+#endif
+
+ } else {
+#ifdef MIDI_DEBUG
+ std::cerr << "have new event code " << int(midiByte) << std::endl;
+#endif
+
+ eventCode = midiByte;
+ data1 = getMidiByte(midiFile);
+ }
+
+ if (eventCode == MIDI_FILE_META_EVENT) // meta events
+ {
+ // metaEventCode = getMidiByte(midiFile);
+ metaEventCode = data1;
+ messageLength = getNumberFromMidiBytes(midiFile);
+
+#ifdef MIDI_DEBUG
+
+ std::cerr << "Meta event of type " << int(metaEventCode) << " and " << messageLength << " bytes found" << std::endl;
+#endif
+
+ metaMessage = getMidiBytes(midiFile, messageLength);
+
+ if (metaEventCode == MIDI_TIME_SIGNATURE ||
+ metaEventCode == MIDI_SET_TEMPO)
+ {
+ m_containsTimeChanges = true;
+ }
+
+ long gap = accumulatedTime - trackTimeMap[metaTrack];
+ accumulatedTime += deltaTime;
+ deltaTime += gap;
+ trackTimeMap[metaTrack] = accumulatedTime;
+
+ MidiEvent *e = new MidiEvent(deltaTime,
+ MIDI_FILE_META_EVENT,
+ metaEventCode,
+ metaMessage);
+
+ m_midiComposition[metaTrack].push_back(e);
+
+ } else // the rest
+ {
+ runningStatus = eventCode;
+
+ MidiEvent *midiEvent;
+
+ int channel = (eventCode & MIDI_CHANNEL_NUM_MASK);
+ if (channelTrackMap[channel] == -1) {
+ if (!firstTrack) {
+ ++lastTrackNum;
+ } else {
+ firstTrack = false;
+ }
+ std::cerr << "MidiFile: new channel map entry: channel " << channel << " -> track " << lastTrackNum << std::endl;
+ channelTrackMap[channel] = lastTrackNum;
+ m_trackChannelMap[lastTrackNum] = channel;
+ }
+
+ TrackId trackNum = channelTrackMap[channel];
+
+ {
+ static int prevTrackNum = -1, prevChannel = -1;
+ if (prevTrackNum != (int) trackNum ||
+ prevChannel != (int) channel) {
+ std::cerr << "MidiFile: track number for channel " << channel << " is " << trackNum << std::endl;
+ prevTrackNum = trackNum;
+ prevChannel = channel;
+ }
+ }
+
+ // accumulatedTime is abs time of last event on any track;
+ // trackTimeMap[trackNum] is that of last event on this track
+
+ long gap = accumulatedTime - trackTimeMap[trackNum];
+ accumulatedTime += deltaTime;
+ deltaTime += gap;
+ trackTimeMap[trackNum] = accumulatedTime;
+
+ switch (eventCode & MIDI_MESSAGE_TYPE_MASK) {
+ case MIDI_NOTE_ON:
+ case MIDI_NOTE_OFF:
+ case MIDI_POLY_AFTERTOUCH:
+ case MIDI_CTRL_CHANGE:
+ data2 = getMidiByte(midiFile);
+
+ // create and store our event
+ midiEvent = new MidiEvent(deltaTime, eventCode, data1, data2);
+
+ /*
+ std::cerr << "MIDI event for channel " << channel << " (track "
+ << trackNum << ")" << std::endl;
+ midiEvent->print();
+ */
+
+
+ m_midiComposition[trackNum].push_back(midiEvent);
+ break;
+
+ case MIDI_PITCH_BEND:
+ data2 = getMidiByte(midiFile);
+
+ // create and store our event
+ midiEvent = new MidiEvent(deltaTime, eventCode, data1, data2);
+ m_midiComposition[trackNum].push_back(midiEvent);
+ break;
+
+ case MIDI_PROG_CHANGE:
+ case MIDI_CHNL_AFTERTOUCH:
+ // create and store our event
+ std::cerr << "Program change or channel aftertouch: time " << deltaTime << ", code " << (int)eventCode << ", data " << (int) data1 << " going to track " << trackNum << std::endl;
+ midiEvent = new MidiEvent(deltaTime, eventCode, data1);
+ m_midiComposition[trackNum].push_back(midiEvent);
+ break;
+
+ case MIDI_SYSTEM_EXCLUSIVE:
+ messageLength = getNumberFromMidiBytes(midiFile, data1);
+
+#ifdef MIDI_DEBUG
+
+ std::cerr << "SysEx of " << messageLength << " bytes found" << std::endl;
+#endif
+
+ metaMessage = getMidiBytes(midiFile, messageLength);
+
+ if (MidiByte(metaMessage[metaMessage.length() - 1]) !=
+ MIDI_END_OF_EXCLUSIVE) {
+#ifdef MIDI_DEBUG
+ std::cerr << "MidiFile::parseTrack() - "
+ << "malformed or unsupported SysEx type"
+ << std::endl;
+#endif
+
+ continue;
+ }
+
+ // chop off the EOX
+ // length fixed by Pedro Lopez-Cabanillas (20030523)
+ //
+ metaMessage = metaMessage.substr(0, metaMessage.length() - 1);
+
+ midiEvent = new MidiEvent(deltaTime,
+ MIDI_SYSTEM_EXCLUSIVE,
+ metaMessage);
+ m_midiComposition[trackNum].push_back(midiEvent);
+ break;
+
+ case MIDI_END_OF_EXCLUSIVE:
+#ifdef MIDI_DEBUG
+
+ std::cerr << "MidiFile::parseTrack() - "
+ << "Found a stray MIDI_END_OF_EXCLUSIVE" << std::endl;
+#endif
+
+ break;
+
+ default:
+#ifdef MIDI_DEBUG
+
+ std::cerr << "MidiFile::parseTrack()"
+ << " - Unsupported MIDI Event Code: "
+ << (int)eventCode << endl;
+#endif
+
+ break;
+ }
+ }
+ }
+
+ return (true);
+}
+
+// borrowed from ALSA pcm_timer.c
+//
+static unsigned long gcd(unsigned long a, unsigned long b)
+{
+ unsigned long r;
+ if (a < b) {
+ r = a;
+ a = b;
+ b = r;
+ }
+ while ((r = a % b) != 0) {
+ a = b;
+ b = r;
+ }
+ return b;
+}
+
+// If we wanted to abstract the MidiFile class to make it more useful to
+// other applications (and formats) we'd make this method and its twin
+// pure virtual.
+//
+bool
+MidiFile::convertToRosegarden(Composition &composition, ConversionType type)
+{
+ Profiler profiler("MidiFile::convertToRosegarden");
+
+ MidiTrack::iterator midiEvent;
+ Segment *rosegardenSegment;
+ Segment *conductorSegment = 0;
+ Event *rosegardenEvent;
+ string trackName;
+
+ // Time conversions
+ //
+ timeT rosegardenTime = 0;
+ timeT rosegardenDuration = 0;
+ timeT maxTime = 0;
+
+ // To create rests
+ //
+ timeT endOfLastNote;
+
+ // Event specific vars
+ //
+ int numerator = 4;
+ int denominator = 4;
+ timeT segmentTime;
+
+ // keys
+ int accidentals;
+ bool isMinor;
+ bool isSharp;
+
+ if (type == CONVERT_REPLACE)
+ composition.clear();
+
+ timeT origin = 0;
+ if (type == CONVERT_APPEND && composition.getDuration() > 0) {
+ origin = composition.getBarEndForTime(composition.getDuration());
+ }
+
+ TrackId compTrack = 0;
+ for (Composition::iterator ci = composition.begin();
+ ci != composition.end(); ++ci) {
+ if ((*ci)->getTrack() >= compTrack)
+ compTrack = (*ci)->getTrack() + 1;
+ }
+
+ Track *track = 0;
+
+ // precalculate the timing factor
+ //
+ // [cc] -- attempt to avoid floating-point rounding errors
+ timeT crotchetTime = Note(Note::Crotchet).getDuration();
+ int divisor = m_timingDivision ? m_timingDivision : 96;
+
+ unsigned long multiplier = crotchetTime;
+ int g = (int)gcd(crotchetTime, divisor);
+ multiplier /= g;
+ divisor /= g;
+
+ timeT maxRawTime = LONG_MAX;
+ if (multiplier > divisor)
+ maxRawTime = (maxRawTime / multiplier) * divisor;
+
+ bool haveTimeSignatures = false;
+ InstrumentId compInstrument = MidiInstrumentBase;
+
+ // Clear down the assigned Instruments we already have
+ //
+ if (type == CONVERT_REPLACE) {
+ m_studio->unassignAllInstruments();
+ }
+
+ std::vector<Segment *> addedSegments;
+
+#ifdef MIDI_DEBUG
+
+ std::cerr << "NUMBER OF TRACKS = " << m_numberOfTracks << endl;
+ std::cerr << "MIDI COMP SIZE = " << m_midiComposition.size() << endl;
+#endif
+
+ for (TrackId i = 0; i < m_numberOfTracks; i++ ) {
+ segmentTime = 0;
+ trackName = string("Imported MIDI");
+
+ // progress - 20% total in file import itself and then 80%
+ // split over these tracks
+ emit setProgress(20 +
+ (int)((80.0 * double(i) / double(m_numberOfTracks))));
+ kapp->processEvents(50);
+
+ // Convert the deltaTime to an absolute time since
+ // the start of the segment. The addTime method
+ // returns the sum of the current Midi Event delta
+ // time plus the argument.
+ //
+ for (midiEvent = m_midiComposition[i].begin();
+ midiEvent != m_midiComposition[i].end();
+ ++midiEvent) {
+ segmentTime = (*midiEvent)->addTime(segmentTime);
+ }
+
+ // Consolidate NOTE ON and NOTE OFF events into a NOTE ON with
+ // a duration.
+ //
+ consolidateNoteOffEvents(i);
+
+ if (m_trackChannelMap.find(i) != m_trackChannelMap.end()) {
+ compInstrument = MidiInstrumentBase + m_trackChannelMap[i];
+ } else {
+ compInstrument = MidiInstrumentBase;
+ }
+
+ rosegardenSegment = new Segment;
+ rosegardenSegment->setTrack(compTrack);
+ rosegardenSegment->setStartTime(0);
+
+ track = new Track(compTrack, // id
+ compInstrument, // instrument
+ compTrack, // position
+ trackName, // name
+ false); // muted
+
+ std::cerr << "New Rosegarden track: id = " << compTrack << ", instrument = " << compInstrument << ", name = " << trackName << std::endl;
+
+ // rest creation token needs to be reset here
+ //
+ endOfLastNote = 0;
+
+ int msb = -1, lsb = -1; // for bank selects
+ Instrument *instrument = 0;
+
+ for (midiEvent = m_midiComposition[i].begin();
+ midiEvent != m_midiComposition[i].end();
+ midiEvent++) {
+ rosegardenEvent = 0;
+
+ // [cc] -- avoid floating-point where possible
+
+ timeT rawTime = (*midiEvent)->getTime();
+
+ if (rawTime < maxRawTime) {
+ rosegardenTime = origin +
+ timeT((rawTime * multiplier) / divisor);
+ } else {
+ rosegardenTime = origin +
+ timeT((double(rawTime) * multiplier) / double(divisor) + 0.01);
+ }
+
+ rosegardenDuration =
+ timeT(((*midiEvent)->getDuration() * multiplier) / divisor);
+
+#ifdef MIDI_DEBUG
+
+ std::cerr << "MIDI file import: origin " << origin
+ << ", event time " << rosegardenTime
+ << ", duration " << rosegardenDuration
+ << ", event type " << (int)(*midiEvent)->getMessageType()
+ << ", previous max time " << maxTime
+ << ", potential max time " << (rosegardenTime + rosegardenDuration)
+ << ", ev raw time " << (*midiEvent)->getTime()
+ << ", crotchet " << crotchetTime
+ << ", multiplier " << multiplier
+ << ", divisor " << divisor
+ << std::endl;
+#endif
+
+ if (rosegardenTime + rosegardenDuration > maxTime) {
+ maxTime = rosegardenTime + rosegardenDuration;
+ }
+
+ // timeT fillFromTime = rosegardenTime;
+ if (rosegardenSegment->empty()) {
+ // fillFromTime = composition.getBarStartForTime(rosegardenTime);
+ endOfLastNote = composition.getBarStartForTime(rosegardenTime);
+ }
+
+ if ((*midiEvent)->isMeta()) {
+
+ switch ((*midiEvent)->getMetaEventCode()) {
+
+ case MIDI_TEXT_EVENT: {
+ std::string text = (*midiEvent)->getMetaMessage();
+ rosegardenEvent =
+ Text(text).getAsEvent(rosegardenTime);
+ }
+ break;
+
+ case MIDI_LYRIC: {
+ std::string text = (*midiEvent)->getMetaMessage();
+// std::cerr << "lyric event: text=\""
+// << text << "\", time=" << rosegardenTime << std::endl;
+ rosegardenEvent =
+ Text(text, Text::Lyric).
+ getAsEvent(rosegardenTime);
+ }
+ break;
+
+ case MIDI_TEXT_MARKER: {
+ std::string text = (*midiEvent)->getMetaMessage();
+ composition.addMarker(new Marker
+ (rosegardenTime, text, ""));
+ }
+ break;
+
+ case MIDI_COPYRIGHT_NOTICE:
+ if (type == CONVERT_REPLACE) {
+ composition.setCopyrightNote((*midiEvent)->
+ getMetaMessage());
+ }
+ break;
+
+ case MIDI_TRACK_NAME:
+ track->setLabel((*midiEvent)->getMetaMessage());
+ break;
+
+ case MIDI_INSTRUMENT_NAME:
+ rosegardenSegment->setLabel((*midiEvent)->getMetaMessage());
+ break;
+
+ case MIDI_END_OF_TRACK: {
+ timeT trackEndTime = rosegardenTime;
+ if (trackEndTime <= 0) {
+ trackEndTime = crotchetTime * 4 * numerator / denominator;
+ }
+ if (endOfLastNote < trackEndTime) {
+ //If there's nothing in the segment yet, then we
+ //shouldn't fill with rests because we don't want
+ //to cause the otherwise empty segment to be created
+ if (rosegardenSegment->size() > 0) {
+ rosegardenSegment->fillWithRests(trackEndTime);
+ }
+ }
+ }
+ break;
+
+ case MIDI_SET_TEMPO: {
+ MidiByte m0 = (*midiEvent)->getMetaMessage()[0];
+ MidiByte m1 = (*midiEvent)->getMetaMessage()[1];
+ MidiByte m2 = (*midiEvent)->getMetaMessage()[2];
+
+ long tempo = (((m0 << 8) + m1) << 8) + m2;
+
+ if (tempo != 0) {
+ double qpm = 60000000.0 / double(tempo);
+ tempoT rgt(Composition::getTempoForQpm(qpm));
+ std::cout << "MidiFile: converted MIDI tempo " << tempo << " to Rosegarden tempo " << rgt << std::endl;
+ composition.addTempoAtTime(rosegardenTime, rgt);
+ }
+ }
+ break;
+
+ case MIDI_TIME_SIGNATURE:
+ numerator = (int) (*midiEvent)->getMetaMessage()[0];
+ denominator = 1 << ((int)(*midiEvent)->getMetaMessage()[1]);
+
+ // NB. a MIDI time signature also has
+ // metamessage[2] and [3], containing some timing data
+
+ if (numerator == 0)
+ numerator = 4;
+ if (denominator == 0)
+ denominator = 4;
+
+ composition.addTimeSignature
+ (rosegardenTime,
+ TimeSignature(numerator, denominator));
+ haveTimeSignatures = true;
+ break;
+
+ case MIDI_KEY_SIGNATURE:
+ // get the details
+ accidentals = (int) (*midiEvent)->getMetaMessage()[0];
+ isMinor = (int) (*midiEvent)->getMetaMessage()[1];
+ isSharp = accidentals < 0 ? false : true;
+ accidentals = accidentals < 0 ? -accidentals : accidentals;
+ // create the key event
+ //
+ try {
+ rosegardenEvent = Rosegarden::Key
+ (accidentals, isSharp, isMinor).
+ getAsEvent(rosegardenTime);
+ }
+ catch (...) {
+#ifdef MIDI_DEBUG
+ std::cerr << "MidiFile::convertToRosegarden - "
+ << " badly formed key signature"
+ << std::endl;
+#endif
+
+ break;
+ }
+ break;
+
+ case MIDI_SEQUENCE_NUMBER:
+ case MIDI_CHANNEL_PREFIX_OR_PORT:
+ case MIDI_CUE_POINT:
+ case MIDI_CHANNEL_PREFIX:
+ case MIDI_SEQUENCER_SPECIFIC:
+ case MIDI_SMPTE_OFFSET:
+ default:
+#ifdef MIDI_DEBUG
+
+ std::cerr << "MidiFile::convertToRosegarden - "
+ << "unsupported META event code "
+ << (int)((*midiEvent)->getMetaEventCode()) << endl;
+#endif
+
+ break;
+ }
+
+ } else
+ switch ((*midiEvent)->getMessageType()) {
+ case MIDI_NOTE_ON:
+
+ // A zero velocity here is a virtual "NOTE OFF"
+ // so we ignore this event
+ //
+ if ((*midiEvent)->getVelocity() == 0)
+ break;
+
+ endOfLastNote = rosegardenTime + rosegardenDuration;
+
+ //std::cerr << "MidiFile::convertToRosegarden: note at " << rosegardenTime << ", midi time " << (*midiEvent)->getTime() << std::endl;
+
+ // create and populate event
+ rosegardenEvent = new Event(Note::EventType,
+ rosegardenTime,
+ rosegardenDuration);
+ rosegardenEvent->set
+ <Int>(BaseProperties::PITCH,
+ (*midiEvent)->getPitch());
+ rosegardenEvent->set
+ <Int>(BaseProperties::VELOCITY,
+ (*midiEvent)->getVelocity());
+ break;
+
+ // We ignore any NOTE OFFs here as we've already
+ // converted NOTE ONs to have duration
+ //
+ case MIDI_NOTE_OFF:
+ continue;
+ break;
+
+ case MIDI_PROG_CHANGE:
+ // Attempt to turn the prog change we've found into an
+ // Instrument. Send the program number and whether or
+ // not we're on the percussion channel.
+ //
+ // Note that we make no attempt to do the right
+ // thing with program changes during a track -- we
+ // just save them as events. Only the first is
+ // used to select the instrument. If it's at time
+ // zero, it's not saved as an event.
+ //
+// std::cerr << "Program change found" << std::endl;
+
+ if (!instrument) {
+
+ bool percussion = (*midiEvent)->getChannelNumber() ==
+ MIDI_PERCUSSION_CHANNEL;
+ int program = (*midiEvent)->getData1();
+
+ if (type == CONVERT_REPLACE) {
+
+ instrument = m_studio->getInstrumentById(compInstrument);
+ if (instrument) {
+ instrument->setPercussion(percussion);
+ instrument->setSendProgramChange(true);
+ instrument->setProgramChange(program);
+ instrument->setSendBankSelect(msb >= 0 || lsb >= 0);
+ if (instrument->sendsBankSelect()) {
+ instrument->setMSB(msb >= 0 ? msb : 0);
+ instrument->setLSB(lsb >= 0 ? lsb : 0);
+ }
+ }
+ } else { // not CONVERT_REPLACE
+ instrument =
+ m_studio->assignMidiProgramToInstrument
+ (program, msb, lsb, percussion);
+ }
+ }
+
+ // assign it here
+ if (instrument) {
+ track->setInstrument(instrument->getId());
+ // We used to set the segment name from the instrument
+ // here, but now we do them all at the end only if the
+ // segment has no other name set (e.g. from instrument
+ // meta event)
+ if ((*midiEvent)->getTime() == 0) break; // no insert
+ }
+
+ // did we have a bank select? if so, insert that too
+
+ if (msb >= 0) {
+ rosegardenSegment->insert
+ (Controller(MIDI_CONTROLLER_BANK_MSB, msb).
+ getAsEvent(rosegardenTime));
+ }
+ if (lsb >= 0) {
+ rosegardenSegment->insert
+ (Controller(MIDI_CONTROLLER_BANK_LSB, msb).
+ getAsEvent(rosegardenTime));
+ }
+
+ rosegardenEvent =
+ ProgramChange((*midiEvent)->getData1()).
+ getAsEvent(rosegardenTime);
+ break;
+
+ case MIDI_CTRL_CHANGE:
+
+ // If it's a bank select, interpret it (or remember
+ // for later insertion) instead of just inserting it
+ // as a Rosegarden event
+
+ if ((*midiEvent)->getData1() == MIDI_CONTROLLER_BANK_MSB) {
+ msb = (*midiEvent)->getData2();
+ break;
+ }
+
+ if ((*midiEvent)->getData1() == MIDI_CONTROLLER_BANK_LSB) {
+ lsb = (*midiEvent)->getData2();
+ break;
+ }
+
+ // If it's something we can use as an instrument
+ // parameter, and it's at time zero, and we already
+ // have an instrument, then apply it to the instrument
+ // instead of inserting
+
+ if (instrument && (*midiEvent)->getTime() == 0) {
+ if ((*midiEvent)->getData1() == MIDI_CONTROLLER_VOLUME) {
+ instrument->setVolume((*midiEvent)->getData2());
+ break;
+ }
+ if ((*midiEvent)->getData1() == MIDI_CONTROLLER_PAN) {
+ instrument->setPan((*midiEvent)->getData2());
+ break;
+ }
+ if ((*midiEvent)->getData1() == MIDI_CONTROLLER_ATTACK) {
+ instrument->setControllerValue(MIDI_CONTROLLER_ATTACK, (*midiEvent)->getData2());
+ break;
+ }
+ if ((*midiEvent)->getData1() == MIDI_CONTROLLER_RELEASE) {
+ instrument->setControllerValue(MIDI_CONTROLLER_RELEASE, (*midiEvent)->getData2());
+ break;
+ }
+ if ((*midiEvent)->getData1() == MIDI_CONTROLLER_FILTER) {
+ instrument->setControllerValue(MIDI_CONTROLLER_FILTER, (*midiEvent)->getData2());
+ break;
+ }
+ if ((*midiEvent)->getData1() == MIDI_CONTROLLER_RESONANCE) {
+ instrument->setControllerValue(MIDI_CONTROLLER_RESONANCE, (*midiEvent)->getData2());
+ break;
+ }
+ if ((*midiEvent)->getData1() == MIDI_CONTROLLER_CHORUS) {
+ instrument->setControllerValue(MIDI_CONTROLLER_CHORUS, (*midiEvent)->getData2());
+ break;
+ }
+ if ((*midiEvent)->getData1() == MIDI_CONTROLLER_REVERB) {
+ instrument->setControllerValue(MIDI_CONTROLLER_REVERB, (*midiEvent)->getData2());
+ break;
+ }
+ }
+
+ rosegardenEvent =
+ Controller((*midiEvent)->getData1(),
+ (*midiEvent)->getData2()).
+ getAsEvent(rosegardenTime);
+ break;
+
+ case MIDI_PITCH_BEND:
+ rosegardenEvent =
+ PitchBend((*midiEvent)->getData2(),
+ (*midiEvent)->getData1()).
+ getAsEvent(rosegardenTime);
+ break;
+
+ case MIDI_SYSTEM_EXCLUSIVE:
+ rosegardenEvent =
+ SystemExclusive((*midiEvent)->getMetaMessage()).
+ getAsEvent(rosegardenTime);
+ break;
+
+ case MIDI_POLY_AFTERTOUCH:
+ rosegardenEvent =
+ KeyPressure((*midiEvent)->getData1(),
+ (*midiEvent)->getData2()).
+ getAsEvent(rosegardenTime);
+ break;
+
+ case MIDI_CHNL_AFTERTOUCH:
+ rosegardenEvent =
+ ChannelPressure((*midiEvent)->getData1()).
+ getAsEvent(rosegardenTime);
+ break;
+
+ default:
+#ifdef MIDI_DEBUG
+
+ std::cerr << "MidiFile::convertToRosegarden - "
+ << "Unsupported event code = "
+ << (int)(*midiEvent)->getMessageType() << std::endl;
+#endif
+
+ break;
+ }
+
+ if (rosegardenEvent) {
+ // if (fillFromTime < rosegardenTime) {
+ // rosegardenSegment->fillWithRests(fillFromTime, rosegardenTime);
+ // }
+ if (endOfLastNote < rosegardenTime) {
+ rosegardenSegment->fillWithRests(endOfLastNote, rosegardenTime);
+ }
+ rosegardenSegment->insert(rosegardenEvent);
+ }
+ }
+
+ if (rosegardenSegment->size() > 0) {
+
+ // if all we have is key signatures and rests, take this
+ // to be a conductor segment and don't insert it
+ //
+ bool keySigsOnly = true;
+ bool haveKeySig = false;
+ for (Segment::iterator i = rosegardenSegment->begin();
+ i != rosegardenSegment->end(); ++i) {
+ if (!(*i)->isa(Rosegarden::Key::EventType) &&
+ !(*i)->isa(Note::EventRestType)) {
+ keySigsOnly = false;
+ break;
+ } else if ((*i)->isa(Rosegarden::Key::EventType)) {
+ haveKeySig = true;
+ }
+ }
+
+ if (keySigsOnly) {
+ conductorSegment = rosegardenSegment;
+ continue;
+ } else if (!haveKeySig && conductorSegment) {
+ // copy across any key sigs from the conductor segment
+
+ timeT segmentStartTime = rosegardenSegment->getStartTime();
+ timeT earliestEventEndTime = segmentStartTime;
+
+ for (Segment::iterator i = conductorSegment->begin();
+ i != conductorSegment->end(); ++i) {
+ if ((*i)->getAbsoluteTime() + (*i)->getDuration() <
+ earliestEventEndTime) {
+ earliestEventEndTime =
+ (*i)->getAbsoluteTime() + (*i)->getDuration();
+ }
+ rosegardenSegment->insert(new Event(**i));
+ }
+
+ if (earliestEventEndTime < segmentStartTime) {
+ rosegardenSegment->fillWithRests(earliestEventEndTime,
+ segmentStartTime);
+ }
+ }
+
+#ifdef MIDI_DEBUG
+ std::cerr << "MIDI import: adding segment with start time " << rosegardenSegment->getStartTime() << " and end time " << rosegardenSegment->getEndTime() << std::endl;
+ if (rosegardenSegment->getEndTime() == 2880) {
+ std::cerr << "events:" << std::endl;
+ for (Segment::iterator i = rosegardenSegment->begin();
+ i != rosegardenSegment->end(); ++i) {
+ std::cerr << "type = " << (*i)->getType() << std::endl;
+ std::cerr << "time = " << (*i)->getAbsoluteTime() << std::endl;
+ std::cerr << "duration = " << (*i)->getDuration() << std::endl;
+ }
+ }
+#endif
+
+ // add the Segment to the Composition and increment the
+ // Rosegarden segment number
+ //
+ composition.addTrack(track);
+ composition.addSegment(rosegardenSegment);
+ addedSegments.push_back(rosegardenSegment);
+ compTrack++;
+
+ } else {
+ delete rosegardenSegment;
+ rosegardenSegment = 0;
+ delete track;
+ track = 0;
+ }
+ }
+
+ if (type == CONVERT_REPLACE || maxTime > composition.getEndMarker()) {
+ composition.setEndMarker(composition.getBarEndForTime(maxTime));
+ }
+
+ for (std::vector<Segment *>::iterator i = addedSegments.begin();
+ i != addedSegments.end(); ++i) {
+ Segment *s = *i;
+ if (s) {
+ timeT duration = s->getEndMarkerTime() - s->getStartTime();
+/*
+ std::cerr << "duration = " << duration << " (start "
+ << s->getStartTime() << ", end " << s->getEndTime()
+ << ", marker " << s->getEndMarkerTime() << ")" << std::endl;
+*/
+ if (duration == 0) {
+ s->setEndMarkerTime(s->getStartTime() +
+ Note(Note::Crotchet).getDuration());
+ }
+ Instrument *instr = m_studio->getInstrumentFor(s);
+ if (instr) {
+ if (s->getLabel() == "") {
+ s->setLabel(m_studio->getSegmentName(instr->getId()));
+ }
+ }
+ }
+ }
+
+ return true;
+}
+
+// Takes a Composition and turns it into internal MIDI representation
+// that can then be written out to file.
+//
+// For the moment we should watch to make sure that multiple Segment
+// (parts) don't equate to multiple segments in the MIDI Composition.
+//
+// This is a two pass operation - firstly convert the RG Composition
+// into MIDI events and insert anything extra we need (i.e. NOTE OFFs)
+// with absolute times before then processing all timings into delta
+// times.
+//
+//
+void
+MidiFile::convertToMidi(Composition &comp)
+{
+ MidiEvent *midiEvent;
+ int conductorTrack = 0;
+
+ timeT midiEventAbsoluteTime;
+ MidiByte midiVelocity;
+ MidiByte midiChannel = 0;
+
+ // [cc] int rather than floating point
+ //
+ m_timingDivision = 480; //!!! make this configurable
+ timeT crotchetDuration = Note(Note::Crotchet).getDuration();
+
+ // Export as this format only
+ //
+ m_format = MIDI_SIMULTANEOUS_TRACK_FILE;
+
+ // Clear out the MidiComposition internal store
+ //
+ clearMidiComposition();
+
+ // Insert the Rosegarden Signature Track here and any relevant
+ // file META information - this will get written out just like
+ // any other MIDI track.
+ //
+ midiEvent = new MidiEvent(0, MIDI_FILE_META_EVENT, MIDI_COPYRIGHT_NOTICE,
+ comp.getCopyrightNote());
+
+ m_midiComposition[conductorTrack].push_back(midiEvent);
+
+ midiEvent = new MidiEvent(0, MIDI_FILE_META_EVENT, MIDI_CUE_POINT,
+ "Created by Rosegarden");
+
+ m_midiComposition[conductorTrack].push_back(midiEvent);
+
+ midiEvent = new MidiEvent(0, MIDI_FILE_META_EVENT, MIDI_CUE_POINT,
+ "http://www.rosegardenmusic.com/");
+
+ m_midiComposition[conductorTrack].push_back(midiEvent);
+
+ // Insert tempo events
+ //
+ for (int i = 0; i < comp.getTempoChangeCount(); i++) // i=0 should be comp.getStart-something
+ {
+ std::pair<timeT, tempoT> tempo = comp.getTempoChange(i);
+
+ midiEventAbsoluteTime = tempo.first * m_timingDivision
+ / crotchetDuration;
+
+ double qpm = Composition::getTempoQpm(tempo.second);
+ long tempoValue = long(60000000.0 / qpm + 0.01);
+
+ string tempoString;
+ tempoString += (MidiByte) ( tempoValue >> 16 & 0xFF );
+ tempoString += (MidiByte) ( tempoValue >> 8 & 0xFF );
+ tempoString += (MidiByte) ( tempoValue & 0xFF );
+
+ midiEvent = new MidiEvent(midiEventAbsoluteTime,
+ MIDI_FILE_META_EVENT,
+ MIDI_SET_TEMPO,
+ tempoString);
+
+ m_midiComposition[conductorTrack].push_back(midiEvent);
+ }
+
+ // Insert time signatures (don't worry that the times might be out
+ // of order with those of the tempo events -- we sort the track later)
+ //
+ for (int i = 0; i < comp.getTimeSignatureCount(); i++) {
+ std::pair<timeT, TimeSignature> timeSig =
+ comp.getTimeSignatureChange(i);
+
+ midiEventAbsoluteTime = timeSig.first * m_timingDivision
+ / crotchetDuration;
+
+ string timeSigString;
+ timeSigString += (MidiByte) (timeSig.second.getNumerator());
+ int denominator = timeSig.second.getDenominator();
+ int denPowerOf2 = 0;
+
+ // Work out how many powers of two are in the denominator
+ //
+ while (denominator >>= 1)
+ denPowerOf2++;
+
+ timeSigString += (MidiByte) denPowerOf2;
+
+ // The third byte is the number of MIDI clocks per beat.
+ // There are 24 clocks per quarter-note (the MIDI clock
+ // is tempo-independent and is not related to the timebase).
+ //
+ int cpb = 24 * timeSig.second.getBeatDuration() / crotchetDuration;
+ timeSigString += (MidiByte) cpb;
+
+ // And the fourth byte is always 8, for us (it expresses
+ // the number of notated 32nd-notes in a MIDI quarter-note,
+ // for applications that may want to notate and perform
+ // in different units)
+ //
+ timeSigString += (MidiByte) 8;
+
+ midiEvent = new MidiEvent(midiEventAbsoluteTime,
+ MIDI_FILE_META_EVENT,
+ MIDI_TIME_SIGNATURE,
+ timeSigString);
+
+ m_midiComposition[conductorTrack].push_back(midiEvent);
+ }
+
+ // Insert markers
+ // fix for bug#
+ Composition::markercontainer marks = comp.getMarkers();
+
+ for (unsigned int i = 0; i < marks.size(); i++) {
+ midiEventAbsoluteTime = marks[i]->getTime() * m_timingDivision
+ / crotchetDuration;
+
+ midiEvent = new MidiEvent( midiEventAbsoluteTime,
+ MIDI_FILE_META_EVENT,
+ MIDI_TEXT_MARKER,
+ marks[i]->getName() );
+
+ m_midiComposition[conductorTrack].push_back(midiEvent);
+ }
+
+ m_numberOfTracks = 1;
+ std::map<int, int> trackPosMap; // RG track pos -> MIDI track no
+
+ // In pass one just insert all events including new NOTE OFFs at the right
+ // absolute times.
+ //
+ for (Composition::const_iterator segment = comp.begin();
+ segment != comp.end(); ++segment) {
+
+ // We use this later to get NOTE durations
+ //
+ SegmentPerformanceHelper helper(**segment);
+
+ Track *track = comp.getTrackById((*segment)->getTrack());
+
+ if (track->isMuted()) continue;
+
+ // Fix #1602023, map Rosegarden tracks to MIDI tracks, instead of
+ // putting each segment out on a new track
+
+ int trackPosition = track->getPosition();
+ bool firstSegmentThisTrack = false;
+
+ if (trackPosMap.find(trackPosition) == trackPosMap.end()) {
+ firstSegmentThisTrack = true;
+ trackPosMap[trackPosition] = m_numberOfTracks++;
+ }
+
+ int trackNumber = trackPosMap[trackPosition];
+
+ MidiTrack &mtrack = m_midiComposition[trackNumber];
+
+ midiEvent = new MidiEvent(0,
+ MIDI_FILE_META_EVENT,
+ MIDI_TRACK_NAME,
+ track->getLabel());
+
+ mtrack.push_back(midiEvent);
+
+ // Get the Instrument
+ //
+ Instrument *instr =
+ m_studio->getInstrumentById(track->getInstrument());
+
+ if (firstSegmentThisTrack) {
+
+ MidiByte program = 0;
+ midiChannel = 0;
+
+ bool useBank = false;
+ MidiByte lsb = 0;
+ MidiByte msb = 0;
+
+ if (instr) {
+ midiChannel = instr->getMidiChannel();
+ program = instr->getProgramChange();
+ if (instr->sendsBankSelect()) {
+ lsb = instr->getLSB();
+ msb = instr->getMSB();
+ useBank = true;
+ }
+ }
+
+ if (useBank) {
+
+ // insert a bank select
+
+ if (msb != 0) {
+ midiEvent = new MidiEvent(0,
+ MIDI_CTRL_CHANGE | midiChannel,
+ MIDI_CONTROLLER_BANK_MSB,
+ msb);
+ mtrack.push_back(midiEvent);
+ }
+
+ if (lsb != 0) {
+ midiEvent = new MidiEvent(0,
+ MIDI_CTRL_CHANGE | midiChannel,
+ MIDI_CONTROLLER_BANK_LSB,
+ lsb);
+ mtrack.push_back(midiEvent);
+ }
+ }
+
+ // insert a program change
+ midiEvent = new MidiEvent(0, // time
+ MIDI_PROG_CHANGE | midiChannel,
+ program);
+ mtrack.push_back(midiEvent);
+
+ if (instr) {
+
+ // MidiInstrument parameters: volume, pan, attack,
+ // release, filter, resonance, chorus, reverb. Always
+ // write these: the Instrument has an additional parameter
+ // to record whether they should be sent, but it isn't
+ // actually set anywhere so we have to ignore it.
+
+ static int controllers[] = {
+ MIDI_CONTROLLER_ATTACK,
+ MIDI_CONTROLLER_RELEASE,
+ MIDI_CONTROLLER_FILTER,
+ MIDI_CONTROLLER_RESONANCE,
+ MIDI_CONTROLLER_CHORUS,
+ MIDI_CONTROLLER_REVERB
+ };
+
+ mtrack.push_back
+ (new MidiEvent(0, MIDI_CTRL_CHANGE | midiChannel,
+ MIDI_CONTROLLER_VOLUME, instr->getVolume()));
+
+ mtrack.push_back
+ (new MidiEvent(0, MIDI_CTRL_CHANGE | midiChannel,
+ MIDI_CONTROLLER_PAN, instr->getPan()));
+
+ for (int i = 0; i < sizeof(controllers)/sizeof(controllers[0]); ++i) {
+ try {
+ mtrack.push_back
+ (new MidiEvent
+ (0, MIDI_CTRL_CHANGE | midiChannel, controllers[i],
+ instr->getControllerValue(controllers[i])));
+ } catch (...) {
+ /* do nothing */
+ }
+ }
+ } // if (instr)
+ } // if (firstSegmentThisTrack)
+
+ timeT segmentMidiDuration =
+ ((*segment)->getEndMarkerTime() -
+ (*segment)->getStartTime()) * m_timingDivision /
+ crotchetDuration;
+
+ for (Segment::iterator el = (*segment)->begin();
+ (*segment)->isBeforeEndMarker(el); ++el) {
+ midiEventAbsoluteTime =
+ (*el)->getAbsoluteTime() + (*segment)->getDelay();
+
+ timeT absoluteTimeLimit = midiEventAbsoluteTime;
+ if ((*segment)->isRepeating()) {
+ absoluteTimeLimit = ((*segment)->getRepeatEndTime() - 1) +
+ (*segment)->getDelay();
+ }
+
+ if ((*segment)->getRealTimeDelay() != RealTime::zeroTime) {
+ RealTime evRT = comp.getElapsedRealTime(midiEventAbsoluteTime);
+ timeT timeBeforeDelay = midiEventAbsoluteTime;
+ midiEventAbsoluteTime = comp.getElapsedTimeForRealTime
+ (evRT + (*segment)->getRealTimeDelay());
+ absoluteTimeLimit += (midiEventAbsoluteTime - timeBeforeDelay);
+ }
+
+ midiEventAbsoluteTime =
+ midiEventAbsoluteTime * m_timingDivision / crotchetDuration;
+ absoluteTimeLimit =
+ absoluteTimeLimit * m_timingDivision / crotchetDuration;
+
+ while (midiEventAbsoluteTime <= absoluteTimeLimit) {
+
+ try {
+
+ if ((*el)->isa(Note::EventType)) {
+ if ((*el)->has(BaseProperties::VELOCITY))
+ midiVelocity = (*el)->get
+ <Int>(BaseProperties::VELOCITY);
+ else
+ midiVelocity = 127;
+
+ // Get the sounding time for the matching NOTE_OFF.
+ // We use SegmentPerformanceHelper::getSoundingDuration()
+ // to work out the tied duration of the NOTE.
+ timeT soundingDuration = helper.getSoundingDuration(el);
+ if (soundingDuration > 0) {
+
+ timeT midiEventEndTime = midiEventAbsoluteTime +
+ soundingDuration * m_timingDivision /
+ crotchetDuration;
+
+ long pitch = 60;
+ (*el)->get
+ <Int>(BaseProperties::PITCH, pitch);
+ pitch += (*segment)->getTranspose();
+
+ // insert the NOTE_ON at the appropriate channel
+ //
+ midiEvent =
+ new MidiEvent(midiEventAbsoluteTime,
+ MIDI_NOTE_ON | midiChannel,
+ pitch,
+ midiVelocity);
+
+ mtrack.push_back(midiEvent);
+
+ // insert the matching NOTE OFF
+ //
+ midiEvent =
+ new MidiEvent(midiEventEndTime,
+ MIDI_NOTE_OFF | midiChannel,
+ pitch,
+ 127); // full volume silence
+
+ mtrack.push_back(midiEvent);
+ }
+ } else if ((*el)->isa(PitchBend::EventType)) {
+ PitchBend pb(**el);
+ midiEvent =
+ new MidiEvent(midiEventAbsoluteTime,
+ MIDI_PITCH_BEND | midiChannel,
+ pb.getLSB(), pb.getMSB());
+
+ mtrack.push_back(midiEvent);
+ } else if ((*el)->isa(Rosegarden::Key::EventType)) {
+ Rosegarden::Key key(**el);
+
+ int accidentals = key.getAccidentalCount();
+ if (!key.isSharp())
+ accidentals = -accidentals;
+
+ // stack out onto the meta string
+ //
+ std::string metaMessage;
+ metaMessage += MidiByte(accidentals);
+ metaMessage += MidiByte(key.isMinor());
+
+ midiEvent =
+ new MidiEvent(midiEventAbsoluteTime,
+ MIDI_FILE_META_EVENT,
+ MIDI_KEY_SIGNATURE,
+ metaMessage);
+
+ //mtrack.push_back(midiEvent);
+
+ } else if ((*el)->isa(Controller::EventType)) {
+ Controller c(**el);
+ midiEvent =
+ new MidiEvent(midiEventAbsoluteTime,
+ MIDI_CTRL_CHANGE | midiChannel,
+ c.getNumber(), c.getValue());
+
+ mtrack.push_back(midiEvent);
+ } else if ((*el)->isa(ProgramChange::EventType)) {
+ ProgramChange pc(**el);
+ midiEvent =
+ new MidiEvent(midiEventAbsoluteTime,
+ MIDI_PROG_CHANGE | midiChannel,
+ pc.getProgram());
+
+ mtrack.push_back(midiEvent);
+ } else if ((*el)->isa(SystemExclusive::EventType)) {
+ SystemExclusive s(**el);
+ std::string data = s.getRawData();
+
+ // check for closing EOX and add one if none found
+ //
+ if (MidiByte(data[data.length() - 1]) != MIDI_END_OF_EXCLUSIVE) {
+ data += MIDI_END_OF_EXCLUSIVE;
+ }
+
+ // construct plain SYSEX event
+ //
+ midiEvent = new MidiEvent(midiEventAbsoluteTime,
+ MIDI_SYSTEM_EXCLUSIVE,
+ data);
+
+ mtrack.push_back(midiEvent);
+
+ } else if ((*el)->isa(ChannelPressure::EventType)) {
+ ChannelPressure cp(**el);
+ midiEvent =
+ new MidiEvent(midiEventAbsoluteTime,
+ MIDI_CHNL_AFTERTOUCH | midiChannel,
+ cp.getPressure());
+
+ mtrack.push_back(midiEvent);
+ } else if ((*el)->isa(KeyPressure::EventType)) {
+ KeyPressure kp(**el);
+ midiEvent =
+ new MidiEvent(midiEventAbsoluteTime,
+ MIDI_POLY_AFTERTOUCH | midiChannel,
+ kp.getPitch(), kp.getPressure());
+
+ mtrack.push_back(midiEvent);
+ } else if ((*el)->isa(Text::EventType)) {
+ Text text(**el);
+ std::string metaMessage = text.getText();
+
+ MidiByte midiTextType = MIDI_TEXT_EVENT;
+
+ if (text.getTextType() == Text::Lyric) {
+ midiTextType = MIDI_LYRIC;
+ }
+
+ if (text.getTextType() != Text::Annotation) {
+ // (we don't write annotations)
+
+ midiEvent =
+ new MidiEvent(midiEventAbsoluteTime,
+ MIDI_FILE_META_EVENT,
+ midiTextType,
+ metaMessage);
+
+ mtrack.push_back(midiEvent);
+ }
+ } else if ((*el)->isa(Note::EventRestType)) {
+ // skip legitimately
+ } else {
+ /*
+ cerr << "MidiFile::convertToMidi - "
+ << "unsupported MidiType \""
+ << (*el)->getType()
+ << "\" at export"
+ << std::endl;
+ */
+ }
+
+ } catch (MIDIValueOutOfRange r) {
+#ifdef MIDI_DEBUG
+ std::cerr << "MIDI value out of range at "
+ << (*el)->getAbsoluteTime() << std::endl;
+#endif
+
+ } catch (Event::NoData d) {
+#ifdef MIDI_DEBUG
+ std::cerr << "Caught Event::NoData at "
+ << (*el)->getAbsoluteTime() << ", message is:"
+ << std::endl << d.getMessage() << std::endl;
+#endif
+
+ } catch (Event::BadType b) {
+#ifdef MIDI_DEBUG
+ std::cerr << "Caught Event::BadType at "
+ << (*el)->getAbsoluteTime() << ", message is:"
+ << std::endl << b.getMessage() << std::endl;
+#endif
+
+ } catch (SystemExclusive::BadEncoding e) {
+#ifdef MIDI_DEBUG
+ std::cerr << "Caught bad SysEx encoding at "
+ << (*el)->getAbsoluteTime() << std::endl;
+#endif
+
+ }
+
+ if (segmentMidiDuration > 0) {
+ midiEventAbsoluteTime += segmentMidiDuration;
+ } else
+ break;
+ }
+ }
+ }
+
+ // Now gnash through the MIDI events and turn the absolute times
+ // into delta times.
+ //
+ //
+ MidiTrack::iterator it;
+ timeT deltaTime, lastMidiTime;
+
+ for (TrackId i = 0; i < m_numberOfTracks; i++) {
+ lastMidiTime = 0;
+
+ // First sort the track with the MidiEvent comparator. Use
+ // stable_sort so that events with equal times are maintained
+ // in their current order (important for e.g. bank-program
+ // pairs, or the controllers at the start of the track which
+ // should follow the program so we can treat them correctly
+ // when re-reading).
+ //
+ std::stable_sort(m_midiComposition[i].begin(),
+ m_midiComposition[i].end(),
+ MidiEventCmp());
+
+ for (it = m_midiComposition[i].begin();
+ it != m_midiComposition[i].end();
+ it++) {
+ deltaTime = (*it)->getTime() - lastMidiTime;
+ lastMidiTime = (*it)->getTime();
+ (*it)->setTime(deltaTime);
+ }
+
+ // Insert end of track event (delta time = 0)
+ //
+ midiEvent = new MidiEvent(0, MIDI_FILE_META_EVENT,
+ MIDI_END_OF_TRACK, "");
+
+ m_midiComposition[i].push_back(midiEvent);
+
+ }
+
+ return ;
+}
+
+
+
+// Convert an integer into a two byte representation and
+// write out to the MidiFile.
+//
+void
+MidiFile::intToMidiBytes(std::ofstream* midiFile, int number)
+{
+ MidiByte upper;
+ MidiByte lower;
+
+ upper = (number & 0xFF00) >> 8;
+ lower = (number & 0x00FF);
+
+ *midiFile << (MidiByte) upper;
+ *midiFile << (MidiByte) lower;
+
+}
+
+void
+MidiFile::longToMidiBytes(std::ofstream* midiFile, unsigned long number)
+{
+ MidiByte upper1;
+ MidiByte lower1;
+ MidiByte upper2;
+ MidiByte lower2;
+
+ upper1 = (number & 0xff000000) >> 24;
+ lower1 = (number & 0x00ff0000) >> 16;
+ upper2 = (number & 0x0000ff00) >> 8;
+ lower2 = (number & 0x000000ff);
+
+ *midiFile << (MidiByte) upper1;
+ *midiFile << (MidiByte) lower1;
+ *midiFile << (MidiByte) upper2;
+ *midiFile << (MidiByte) lower2;
+
+}
+
+// Turn a delta time into a MIDI time - overlapping into
+// a maximum of four bytes using the MSB as the carry on
+// flag.
+//
+std::string
+MidiFile::longToVarBuffer(unsigned long number)
+{
+ std::string rS;
+
+ long inNumber = number;
+ long outNumber;
+
+ // get the lowest 7 bits of the number
+ outNumber = number & 0x7f;
+
+ // Shift and test and move the numbers
+ // on if we need them - setting the MSB
+ // as we go.
+ //
+ while ((inNumber >>= 7 ) > 0) {
+ outNumber <<= 8;
+ outNumber |= 0x80;
+ outNumber += (inNumber & 0x7f);
+ }
+
+ // Now move the converted number out onto the buffer
+ //
+ while (true) {
+ rS += (MidiByte)(outNumber & 0xff);
+ if (outNumber & 0x80)
+ outNumber >>= 8;
+ else
+ break;
+ }
+
+ return rS;
+}
+
+
+
+// Write out the MIDI file header
+//
+bool
+MidiFile::writeHeader(std::ofstream* midiFile)
+{
+ // Our identifying Header string
+ //
+ *midiFile << MIDI_FILE_HEADER.c_str();
+
+ // Write number of Bytes to follow
+ //
+ *midiFile << (MidiByte) 0x00;
+ *midiFile << (MidiByte) 0x00;
+ *midiFile << (MidiByte) 0x00;
+ *midiFile << (MidiByte) 0x06;
+
+ // Write File Format
+ //
+ *midiFile << (MidiByte) 0x00;
+ *midiFile << (MidiByte) m_format;
+
+ // Number of Tracks we're writing out
+ //
+ intToMidiBytes(midiFile, m_numberOfTracks);
+
+ // Timing Division
+ //
+ intToMidiBytes(midiFile, m_timingDivision);
+
+ return (true);
+}
+
+// Write a MIDI track to file
+//
+bool
+MidiFile::writeTrack(std::ofstream* midiFile, TrackId trackNumber)
+{
+ bool retOK = true;
+ MidiByte eventCode = 0;
+ MidiTrack::iterator midiEvent;
+
+ // First we write into the trackBuffer, then write it out to the
+ // file with it's accompanying length.
+ //
+ string trackBuffer;
+
+ long progressTotal = m_midiComposition[trackNumber].size();
+ long progressCount = 0;
+
+ for (midiEvent = m_midiComposition[trackNumber].begin();
+ midiEvent != m_midiComposition[trackNumber].end();
+ midiEvent++) {
+ // Write the time to the buffer in MIDI format
+ //
+ //
+ trackBuffer += longToVarBuffer((*midiEvent)->getTime());
+
+ if ((*midiEvent)->isMeta()) {
+ trackBuffer += MIDI_FILE_META_EVENT;
+ trackBuffer += (*midiEvent)->getMetaEventCode();
+
+ // Variable length number field
+ trackBuffer += longToVarBuffer((*midiEvent)->
+ getMetaMessage().length());
+
+ trackBuffer += (*midiEvent)->getMetaMessage();
+ } else {
+ // Send the normal event code (with encoded channel information)
+ //
+ // Fix for 674731 by Pedro Lopez-Cabanillas (20030531)
+ if (((*midiEvent)->getEventCode() != eventCode) ||
+ ((*midiEvent)->getEventCode() == MIDI_SYSTEM_EXCLUSIVE)) {
+ trackBuffer += (*midiEvent)->getEventCode();
+ eventCode = (*midiEvent)->getEventCode();
+ }
+
+ // Send the relevant data
+ //
+ switch ((*midiEvent)->getMessageType()) {
+ case MIDI_NOTE_ON:
+ case MIDI_NOTE_OFF:
+ case MIDI_POLY_AFTERTOUCH:
+ trackBuffer += (*midiEvent)->getData1();
+ trackBuffer += (*midiEvent)->getData2();
+ break;
+
+ case MIDI_CTRL_CHANGE:
+ trackBuffer += (*midiEvent)->getData1();
+ trackBuffer += (*midiEvent)->getData2();
+ break;
+
+ case MIDI_PROG_CHANGE:
+ trackBuffer += (*midiEvent)->getData1();
+ break;
+
+ case MIDI_CHNL_AFTERTOUCH:
+ trackBuffer += (*midiEvent)->getData1();
+ break;
+
+ case MIDI_PITCH_BEND:
+ trackBuffer += (*midiEvent)->getData1();
+ trackBuffer += (*midiEvent)->getData2();
+ break;
+
+ case MIDI_SYSTEM_EXCLUSIVE:
+
+ // write out message length
+ trackBuffer +=
+ longToVarBuffer((*midiEvent)->getMetaMessage().length());
+
+ // now the message
+ trackBuffer += (*midiEvent)->getMetaMessage();
+
+ break;
+
+ default:
+#ifdef MIDI_DEBUG
+
+ std::cerr << "MidiFile::writeTrack()"
+ << " - cannot write unsupported MIDI event"
+ << endl;
+#endif
+
+ break;
+ }
+ }
+
+ // For the moment just keep the app updating until we work
+ // out a good way of accounting for this write.
+ //
+ ++progressCount;
+
+ if (progressCount % 500 == 0) {
+ emit setProgress(progressCount * 100 / progressTotal);
+ kapp->processEvents(500);
+ }
+ }
+
+ // Now we write the track - First the standard header..
+ //
+ *midiFile << MIDI_TRACK_HEADER.c_str();
+
+ // ..now the length of the buffer..
+ //
+ longToMidiBytes(midiFile, (long)trackBuffer.length());
+
+ // ..then the buffer itself..
+ //
+ *midiFile << trackBuffer;
+
+ return (retOK);
+}
+
+// Writes out a MIDI file from the internal Midi representation
+//
+bool
+MidiFile::write()
+{
+ bool retOK = true;
+
+ std::ofstream *midiFile =
+ new std::ofstream(m_fileName.c_str(), ios::out | ios::binary);
+
+
+ if (!(*midiFile)) {
+#ifdef MIDI_DEBUG
+ std::cerr << "MidiFile::write() - can't write file" << endl;
+#endif
+
+ m_format = MIDI_FILE_NOT_LOADED;
+ return false;
+ }
+
+ // Write out the Header
+ //
+ writeHeader(midiFile);
+
+ // And now the tracks
+ //
+ for (TrackId i = 0; i < m_numberOfTracks; i++ )
+ if (!writeTrack(midiFile, i))
+ retOK = false;
+
+ midiFile->close();
+
+ if (!retOK)
+ m_format = MIDI_FILE_NOT_LOADED;
+
+ return (retOK);
+}
+
+// Delete dead NOTE OFF and NOTE ON/Zero Velocty Events after
+// reading them and modifying their relevant NOTE ONs
+//
+bool
+MidiFile::consolidateNoteOffEvents(TrackId track)
+{
+ MidiTrack::iterator nOE, mE = m_midiComposition[track].begin();
+ bool notesOnTrack = false;
+ bool noteOffFound;
+
+ for (;mE != m_midiComposition[track].end(); mE++) {
+ if ((*mE)->getMessageType() == MIDI_NOTE_ON && (*mE)->getVelocity() > 0) {
+ // We've found a note - flag it
+ //
+ if (!notesOnTrack)
+ notesOnTrack = true;
+
+ noteOffFound = false;
+
+ for (nOE = mE; nOE != m_midiComposition[track].end(); nOE++) {
+ if (((*nOE)->getChannelNumber() == (*mE)->getChannelNumber()) &&
+ ((*nOE)->getPitch() == (*mE)->getPitch()) &&
+ ((*nOE)->getMessageType() == MIDI_NOTE_OFF ||
+ ((*nOE)->getMessageType() == MIDI_NOTE_ON &&
+ (*nOE)->getVelocity() == 0x00))) {
+ (*mE)->setDuration((*nOE)->getTime() - (*mE)->getTime());
+
+ delete *nOE;
+ m_midiComposition[track].erase(nOE);
+
+ noteOffFound = true;
+ break;
+ }
+ }
+
+ // If no matching NOTE OFF has been found then set
+ // Event duration to length of Segment
+ //
+ if (noteOffFound == false) {
+ --nOE; // avoid crash due to nOE == track.end()
+ (*mE)->setDuration((*nOE)->getTime() - (*mE)->getTime());
+ }
+ }
+ }
+
+ return notesOnTrack;
+}
+
+// Clear down the MidiFile Composition
+//
+void
+MidiFile::clearMidiComposition()
+{
+ for (MidiComposition::iterator ci = m_midiComposition.begin();
+ ci != m_midiComposition.end(); ++ci) {
+
+ //std::cerr << "MidiFile::clearMidiComposition: track " << ci->first << std::endl;
+
+ for (MidiTrack::iterator ti = ci->second.begin();
+ ti != ci->second.end(); ++ti) {
+ delete *ti;
+ }
+
+ ci->second.clear();
+ }
+
+ m_midiComposition.clear();
+ m_trackChannelMap.clear();
+}
+
+// Doesn't do anything yet - doesn't need to. We need to satisfy
+// the pure virtual function in the base class.
+//
+void
+MidiFile::close()
+{}
+
+
+
+}
+
+#include "MidiFile.moc"
diff --git a/src/sound/MidiFile.h b/src/sound/MidiFile.h
new file mode 100644
index 0000000..da97374
--- /dev/null
+++ b/src/sound/MidiFile.h
@@ -0,0 +1,173 @@
+// -*- c-indentation-style:"stroustrup" c-basic-offset: 4 -*-
+/*
+ Rosegarden
+ A sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <bownie@bownie.com>
+
+ 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_MIDI_FILE_H_
+#define _ROSEGARDEN_MIDI_FILE_H_
+
+#include <fstream>
+#include <string>
+#include <list>
+#include <map>
+
+#include <qobject.h>
+
+#include "Midi.h"
+#include "MidiEvent.h"
+#include "Composition.h"
+#include "SoundFile.h"
+
+// Conversion class for Composition to and
+// from MIDI Files. Despite the fact you can reuse this
+// object it's probably safer just to create it for a
+// single way conversion and then throw it away (MIDI
+// to Composition conversion invalidates the internal
+// MIDI model).
+//
+// Derived from SoundFile but still had some features
+// in common with it which could theoretically be moved
+// up into the base for use in other derived classes.
+//
+// [rwb]
+//
+//
+
+namespace Rosegarden
+{
+
+// Our internal MIDI structure is just a list of MidiEvents.
+// We use a list and not a set because we want the order of
+// the events to be arbitrary until we explicitly sort them
+// (necessary when converting Composition absolute times to
+// MIDI delta times).
+//
+typedef std::vector<MidiEvent *> MidiTrack;
+typedef std::map<unsigned int, MidiTrack> MidiComposition;
+
+class Studio;
+
+class MidiFile : public QObject, public SoundFile
+{
+ Q_OBJECT
+public:
+
+ typedef enum
+ {
+ MIDI_SINGLE_TRACK_FILE = 0x00,
+ MIDI_SIMULTANEOUS_TRACK_FILE = 0x01,
+ MIDI_SEQUENTIAL_TRACK_FILE = 0x02,
+ MIDI_CONVERTED_TO_APPLICATION = 0xFE,
+ MIDI_FILE_NOT_LOADED = 0xFF
+ } MIDIFileFormatType;
+
+ typedef enum
+ {
+ CONVERT_REPLACE,
+ CONVERT_AUGMENT,
+ CONVERT_APPEND
+ } ConversionType;
+
+ MidiFile(Studio *studio);
+ MidiFile (const std::string &fn, Studio *studio);
+ ~MidiFile();
+
+ // Declare our virtuals
+ //
+ virtual bool open();
+ virtual bool write();
+ virtual void close();
+
+ int timingDivision() { return m_timingDivision; }
+ MIDIFileFormatType format() { return m_format; }
+ unsigned int numberOfTracks() { return m_numberOfTracks; }
+ bool hasTimeChanges() { return m_containsTimeChanges; }
+
+ // If a file open or save failed
+ std::string getError() { return m_error; }
+
+ /**
+ * Convert a MIDI file to a Rosegarden composition. Return true
+ * for success.
+ */
+ bool convertToRosegarden(Composition &c, ConversionType type);
+
+ /**
+ * Convert a Rosegarden composition to MIDI format, storing the
+ * result internally for later writing.
+ */
+ void convertToMidi(Composition &comp);
+
+signals:
+ void setProgress(int);
+ void incrementProgress(int);
+
+private:
+
+ int m_timingDivision; // pulses per quarter note
+ MIDIFileFormatType m_format;
+ unsigned int m_numberOfTracks;
+ bool m_containsTimeChanges;
+
+ // Internal counters
+ //
+ long m_trackByteCount;
+ bool m_decrementCount;
+
+ // Internal MidiComposition
+ //
+ MidiComposition m_midiComposition;
+ std::map<int, int> m_trackChannelMap;
+
+ // Clear the m_midiComposition
+ //
+ void clearMidiComposition();
+
+ // Split the tasks up with these top level private methods
+ //
+ bool parseHeader(const std::string& midiHeader);
+ bool parseTrack(std::ifstream* midiFile, unsigned int &trackNum);
+ bool writeHeader(std::ofstream* midiFile);
+ bool writeTrack(std::ofstream* midiFile, unsigned int trackNum);
+
+ bool consolidateNoteOffEvents(TrackId track);
+
+ // Internal convenience functions
+ //
+ int midiBytesToInt(const std::string& bytes);
+ long midiBytesToLong(const std::string& bytes);
+ long getNumberFromMidiBytes(std::ifstream* midiFile, int firstByte = -1);
+ MidiByte getMidiByte(std::ifstream* midiFile);
+ std::string getMidiBytes(std::ifstream* midiFile,
+ unsigned long bytes);
+ bool skipToNextTrack(std::ifstream *midiFile);
+ void intToMidiBytes(std::ofstream* midiFile, int number);
+ void longToMidiBytes(std::ofstream* midiFile, unsigned long number);
+ std::string longToVarBuffer(unsigned long number);
+
+ // The pointer to the Studio for Instrument stuff
+ //
+ Studio *m_studio;
+
+ std::string m_error;
+};
+
+}
+
+#endif // _ROSEGARDEN_MIDI_FILE_H_
diff --git a/src/sound/MidiMapping.xml b/src/sound/MidiMapping.xml
new file mode 100644
index 0000000..13b7138
--- /dev/null
+++ b/src/sound/MidiMapping.xml
@@ -0,0 +1,133 @@
+
+<midi>
+ <bank id="0" name="General MIDI">
+ <program id="0" name="Acoustic Grand Piano"/>
+ <program id="1" name="Bright Acoustic Piano"/>
+ <program id="2" name="Electric Grand Piano"/>
+ <program id="3" name="Honky-tonk Piano"/>
+ <program id="4" name="Electric Piano 1"/>
+ <program id="5" name="Electric Piano 2"/>
+ <program id="6" name="Harpsichord"/>
+ <program id="7" name="Clavi"/>
+ <program id="8" name="Celesta"/>
+ <program id="9" name="Glockenspiel"/>
+ <program id="10" name="Music Box"/>
+ <program id="11" name="Vibraphone"/>
+ <program id="12" name="Marimba"/>
+ <program id="13" name="Xylophone"/>
+ <program id="14" name="Tubular Bells"/>
+ <program id="15" name="Dulcimer"/>
+ <program id="16" name="Drawbar Organ"/>
+ <program id="17" name="Percussive Organ"/>
+ <program id="18" name="Rock Organ"/>
+ <program id="19" name="Church Organ"/>
+ <program id="20" name="Reed Organ"/>
+ <program id="21" name="Accordion"/>
+ <program id="22" name="Harmonica"/>
+ <program id="23" name="Tango Accordion"/>
+ <program id="24" name="Acoustic Guitar (nylon)"/>
+ <program id="25" name="Acoustic Guitar (steel)"/>
+ <program id="26" name="Electric Guitar (jazz)"/>
+ <program id="27" name="Electric Guitar (clean)"/>
+ <program id="28" name="Electric Guitar (muted)"/>
+ <program id="29" name="Overdriven Guitar"/>
+ <program id="30" name="Distortion Guitar"/>
+ <program id="31" name="Guitar harmonics"/>
+ <program id="32" name="Acoustic Bass"/>
+ <program id="33" name="Fingered Bass"/>
+ <program id="34" name="Picked Bass"/>
+ <program id="35" name="Fretless Bass"/>
+ <program id="36" name="Slap Bass 1"/>
+ <program id="37" name="Slap Bass 2"/>
+ <program id="38" name="Synth Bass 1"/>
+ <program id="39" name="Synth Bass 2"/>
+ <program id="40" name="Violin"/>
+ <program id="41" name="Viola"/>
+ <program id="42" name="Cello"/>
+ <program id="43" name="Contrabass"/>
+ <program id="44" name="Tremolo Strings"/>
+ <program id="45" name="Pizzicato Strings"/>
+ <program id="46" name="Orchestral Harp"/>
+ <program id="47" name="Timpani"/>
+ <program id="48" name="String Ensemble 1"/>
+ <program id="49" name="String Ensemble 2"/>
+ <program id="50" name="SynthStrings 1"/>
+ <program id="51" name="SynthStrings 2"/>
+ <program id="52" name="Choir Aahs"/>
+ <program id="53" name="Voice Oohs"/>
+ <program id="54" name="Synth Voice"/>
+ <program id="55" name="Orchestra Hit"/>
+ <program id="56" name="Trumpet"/>
+ <program id="57" name="Trombone"/>
+ <program id="58" name="Tuba"/>
+ <program id="59" name="Muted Trumpet"/>
+ <program id="60" name="French Horn"/>
+ <program id="61" name="Brass Section"/>
+ <program id="62" name="SynthBrass 1"/>
+ <program id="63" name="SynthBrass 2"/>
+ <program id="64" name="Soprano Sax"/>
+ <program id="65" name="Alto Sax"/>
+ <program id="66" name="Tenor Sax"/>
+ <program id="67" name="Baritone Sax"/>
+ <program id="68" name="Oboe"/>
+ <program id="69" name="English Horn"/>
+ <program id="70" name="Bassoon"/>
+ <program id="71" name="Clarinet"/>
+ <program id="72" name="Piccolo"/>
+ <program id="73" name="Flute"/>
+ <program id="74" name="Recorder"/>
+ <program id="75" name="Pan Flute"/>
+ <program id="76" name="Blown Bottle"/>
+ <program id="77" name="Shakuhachi"/>
+ <program id="78" name="Whistle"/>
+ <program id="79" name="Ocarina"/>
+ <program id="80" name="Lead 1 (square)"/>
+ <program id="81" name="Lead 2 (sawtooth)"/>
+ <program id="82" name="Lead 3 (calliope)"/>
+ <program id="83" name="Lead 4 (chiff)"/>
+ <program id="84" name="Lead 5 (charang)"/>
+ <program id="85" name="Lead 6 (voice)"/>
+ <program id="86" name="Lead 7 (fifths)"/>
+ <program id="87" name="Lead 8 (bass + lead)"/>
+ <program id="88" name="Pad 1 (new age)"/>
+ <program id="89" name="Pad 2 (warm)"/>
+ <program id="90" name="Pad 3 (polysynth)"/>
+ <program id="91" name="Pad 4 (choir)"/>
+ <program id="92" name="Pad 5 (bowed)"/>
+ <program id="93" name="Pad 6 (metallic)"/>
+ <program id="94" name="Pad 7 (halo)"/>
+ <program id="95" name="Pad 8 (sweep)"/>
+ <program id="96" name="FX 1 (rain)"/>
+ <program id="97" name="FX 2 (soundtrack)"/>
+ <program id="98" name="FX 3 (crystal)"/>
+ <program id="99" name="FX 4 (atmosphere)"/>
+ <program id="100" name="FX 5 (brightness)"/>
+ <program id="101" name="FX 6 (goblins)"/>
+ <program id="102" name="FX 7 (echoes)"/>
+ <program id="103" name="FX 8 (sci-fi)"/>
+ <program id="104" name="Sitar"/>
+ <program id="105" name="Banjo"/>
+ <program id="106" name="Shamisen"/>
+ <program id="107" name="Koto"/>
+ <program id="108" name="Kalimba"/>
+ <program id="109" name="Bag pipe"/>
+ <program id="110" name="Fiddle"/>
+ <program id="111" name="Shanai"/>
+ <program id="112" name="Tinkle Bell"/>
+ <program id="113" name="Agogo"/>
+ <program id="114" name="Steel Drums"/>
+ <program id="115" name="Woodblock"/>
+ <program id="116" name="Taiko Drum"/>
+ <program id="117" name="Melodic Tom"/>
+ <program id="118" name="Synth Drum"/>
+ <program id="119" name="Reverse Cymbal"/>
+ <program id="120" name="Guitar Fret Noise"/>
+ <program id="121" name="Breath Noise"/>
+ <program id="122" name="Seashore"/>
+ <program id="123" name="Bird Tweet"/>
+ <program id="124" name="Telephone Ring"/>
+ <program id="125" name="Helicopter"/>
+ <program id="126" name="Applause"/>
+ <program id="127" name="Gunshot"/>
+ </bank>
+</midi>
diff --git a/src/sound/PeakFile.cpp b/src/sound/PeakFile.cpp
new file mode 100644
index 0000000..8881114
--- /dev/null
+++ b/src/sound/PeakFile.cpp
@@ -0,0 +1,1033 @@
+// -*- c-indentation-style:"stroustrup" c-basic-offset: 4 -*- /*
+/*
+ Rosegarden
+ A sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <bownie@bownie.com>
+
+ 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 <cmath>
+#include <cstdlib>
+#include <kapplication.h>
+
+#include <qdatetime.h>
+#include <qstringlist.h>
+#include <qpalette.h>
+#include <kapp.h>
+
+#include "PeakFile.h"
+#include "AudioFile.h"
+#include "Profiler.h"
+
+using std::cout;
+using std::cerr;
+using std::endl;
+
+//#define DEBUG_PEAKFILE 1
+//#define DEBUG_PEAKFILE_BRIEF 1
+//#define DEBUG_PEAKFILE_CACHE 1
+
+#ifdef DEBUG_PEAKFILE
+#define DEBUG_PEAKFILE_BRIEF 1
+#endif
+
+namespace Rosegarden
+{
+
+PeakFile::PeakFile(AudioFile *audioFile):
+ SoundFile(audioFile->getPeakFilename()),
+ m_audioFile(audioFile),
+ m_version( -1), // -1 defines new file - start at 0
+ m_format(1), // default is 8-bit peak format
+ m_pointsPerValue(0),
+ m_blockSize(256), // default block size is 256 samples
+ m_channels(0),
+ m_numberOfPeaks(0),
+ m_positionPeakOfPeaks(0),
+ m_offsetToPeaks(0),
+ m_modificationTime(QDate(1970, 1, 1), QTime(0, 0, 0)),
+ m_chunkStartPosition(0),
+ m_lastPreviewStartTime(0, 0),
+ m_lastPreviewEndTime(0, 0),
+ m_lastPreviewWidth( -1),
+ m_lastPreviewShowMinima(false)
+{}
+
+PeakFile::~PeakFile()
+{}
+
+bool
+PeakFile::open()
+{
+ // Set the file size
+ //
+ QFileInfo info(QString(m_fileName.c_str()));
+ m_fileSize = info.size();
+
+ // If we're already open then don't open again
+ //
+ if (m_inFile && m_inFile->is_open())
+ return true;
+
+ // Open
+ //
+ m_inFile = new std::ifstream(m_fileName.c_str(),
+ std::ios::in | std::ios::binary);
+ // Check we're open
+ //
+ if (!(*m_inFile))
+ return false;
+
+ try {
+ parseHeader();
+ } catch (BadSoundFileException s) {
+
+#ifdef DEBUG_PEAKFILE
+ cerr << "PeakFile::open - EXCEPTION \"" << s.getMessage() << "\""
+ << endl;
+#endif
+
+ return false;
+ }
+
+ return true;
+}
+
+void
+PeakFile::parseHeader()
+{
+ if (!(*m_inFile))
+ return ;
+
+ m_inFile->seekg(0, std::ios::beg);
+
+ // get full header length
+ //
+ std::string header = getBytes(128);
+
+#if (__GNUC__ < 3)
+
+ if (header.compare(AUDIO_BWF_PEAK_ID, 0, 4) != 0)
+#else
+
+ if (header.compare(0, 4, AUDIO_BWF_PEAK_ID) != 0)
+#endif
+
+ {
+ throw(BadSoundFileException(m_fileName, "PeakFile::parseHeader - can't find LEVL identifier"));
+ }
+
+ int length = getIntegerFromLittleEndian(header.substr(4, 4));
+
+ // Get the length of the header minus the first 8 bytes
+ //
+ if (length == 0)
+ throw(BadSoundFileException(m_fileName, "PeakFile::parseHeader - can't get header length"));
+
+ // Get the file information
+ //
+ m_version = getIntegerFromLittleEndian(header.substr(8, 4));
+ m_format = getIntegerFromLittleEndian(header.substr(12, 4));
+ m_pointsPerValue = getIntegerFromLittleEndian(header.substr(16, 4));
+ m_blockSize = getIntegerFromLittleEndian(header.substr(20, 4));
+ m_channels = getIntegerFromLittleEndian(header.substr(24, 4));
+ m_numberOfPeaks = getIntegerFromLittleEndian(header.substr(28, 4));
+ m_positionPeakOfPeaks = getIntegerFromLittleEndian(header.substr(32, 4));
+
+ // Read in date string and convert it up to QDateTime
+ //
+ QString dateString = QString(header.substr(40, 28).c_str());
+
+ QStringList dateTime = QStringList::split(":", dateString);
+
+ m_modificationTime.setDate(QDate(dateTime[0].toInt(),
+ dateTime[1].toInt(),
+ dateTime[2].toInt()));
+
+ m_modificationTime.setTime(QTime(dateTime[3].toInt(),
+ dateTime[4].toInt(),
+ dateTime[5].toInt(),
+ dateTime[6].toInt()));
+
+ //printStats();
+
+}
+
+void
+PeakFile::printStats()
+{
+ cout << endl;
+ cout << "STATS for PeakFile \"" << m_fileName << "\"" << endl
+ << "-----" << endl << endl;
+
+ cout << " VERSION = " << m_version << endl
+ << " FORMAT = " << m_format << endl
+ << " BYTES/VALUE = " << m_pointsPerValue << endl
+ << " BLOCKSIZE = " << m_blockSize << endl
+ << " CHANNELS = " << m_channels << endl
+ << " PEAK FRAMES = " << m_numberOfPeaks << endl
+ << " PEAK OF PKS = " << m_positionPeakOfPeaks << endl
+ << endl;
+
+ cout << "DATE" << endl
+ << "----" << endl << endl
+ << " YEAR = " << m_modificationTime.date().year() << endl
+ << " MONTH = " << m_modificationTime.date().month() << endl
+ << " DAY = " << m_modificationTime.date().day() << endl
+ << " HOUR = " << m_modificationTime.time().hour() << endl
+ << " MINUTE = " << m_modificationTime.time().minute()
+ << endl
+ << " SECOND = " << m_modificationTime.time().second()
+ << endl
+ << " MSEC = " << m_modificationTime.time().msec()
+ << endl << endl;
+}
+
+bool
+PeakFile::write()
+{
+ return write(5); // default update every 5%
+}
+
+bool
+PeakFile::write(unsigned short updatePercentage)
+{
+ if (m_outFile) {
+ m_outFile->close();
+ delete m_outFile;
+ }
+
+ // Attempt to open AudioFile so that we can extract sample data
+ // for preview file generation
+ //
+ try {
+ if (!m_audioFile->open())
+ return false;
+ } catch (BadSoundFileException e) {
+#ifdef DEBUG_PEAKFILE
+ std::cerr << "PeakFile::write - \"" << e.getMessage() << "\"" << std::endl;
+#endif
+
+ return false;
+ }
+
+ // create and test that we've made it
+ m_outFile = new std::ofstream(m_fileName.c_str(),
+ std::ios::out | std::ios::binary);
+ if (!(*m_outFile))
+ return false;
+
+ // write out the header
+ writeHeader(m_outFile);
+
+ // and now the peak values
+ writePeaks(updatePercentage, m_outFile);
+
+ return true;
+}
+
+// Close the peak file and tidy up
+//
+void
+PeakFile::close()
+{
+ // Close any input file handle
+ //
+ if (m_inFile && m_inFile->is_open()) {
+ m_inFile->close();
+ delete m_inFile;
+ m_inFile = 0;
+ }
+
+ if (m_outFile == 0)
+ return ;
+
+ // Seek to start of chunk
+ //
+ m_outFile->seekp(m_chunkStartPosition, std::ios::beg);
+
+ // Seek to size field at set it
+ //
+ m_outFile->seekp(4, std::ios::cur);
+ putBytes(m_outFile, getLittleEndianFromInteger(m_bodyBytes + 120, 4));
+
+ // Seek to format and set it (m_format is only set at the
+ // end of writePeaks()
+ //
+ m_outFile->seekp(4, std::ios::cur);
+ putBytes(m_outFile, getLittleEndianFromInteger(m_format, 4));
+
+ // Seek to number of peak frames and write value
+ //
+ m_outFile->seekp(12, std::ios::cur);
+ putBytes(m_outFile,
+ getLittleEndianFromInteger(m_numberOfPeaks, 4));
+
+ // Peak of peaks
+ //
+ putBytes(m_outFile,
+ getLittleEndianFromInteger(m_positionPeakOfPeaks, 4));
+
+ // Seek to date field
+ //
+ m_outFile->seekp(4, std::ios::cur);
+
+ // Set modification time to now
+ //
+ m_modificationTime = m_modificationTime.currentDateTime();
+
+ QString fDate;
+ fDate.sprintf("%04d:%02d:%02d:%02d:%02d:%02d:%03d",
+ m_modificationTime.date().year(),
+ m_modificationTime.date().month(),
+ m_modificationTime.date().day(),
+ m_modificationTime.time().hour(),
+ m_modificationTime.time().minute(),
+ m_modificationTime.time().second(),
+ m_modificationTime.time().msec());
+
+ std::string dateString(fDate.data());
+
+ // Pad with spaces to make up to 28 bytes long and output
+ //
+ dateString += " ";
+ putBytes(m_outFile, dateString);
+
+ // Ok, now close and tidy up
+ //
+ m_outFile->close();
+ delete m_outFile;
+ m_outFile = 0;
+}
+
+// If the audio file is more recently modified that the modification time
+// on this peak file then we're invalid. The action to rectify this is
+// usually to regenerate the peak data.
+//
+bool
+PeakFile::isValid()
+{
+ if (m_audioFile->getModificationDateTime() > m_modificationTime)
+ return false;
+
+ return true;
+}
+
+bool
+PeakFile::writeToHandle(std::ofstream *file,
+ unsigned short /*updatePercentage*/)
+{
+ // Remember the position where we pass in the ofstream pointer
+ // so we can return there to write close() information.
+ //
+ m_chunkStartPosition = file->tellp();
+
+ return false;
+}
+
+// Build up a header string and then pump it out to the file handle
+//
+void
+PeakFile::writeHeader(std::ofstream *file)
+{
+ if (!file || !(*file))
+ return ;
+
+ std::string header;
+
+ // The "levl" identifer for this chunk
+ //
+ header += AUDIO_BWF_PEAK_ID;
+
+ // Add a four byte version of the size of the header chunk (120
+ // bytes from this point onwards)
+ //
+ header += getLittleEndianFromInteger(120, 4);
+
+ // A four byte version number (incremented every time)
+ //
+ header += getLittleEndianFromInteger(++m_version, 4);
+
+ // Format of the peak points - 1 = unsigned char
+ // 2 = unsigned short
+ //
+ header += getLittleEndianFromInteger(m_format, 4);
+
+ // Points per value - 1 = 1 peak and has vertical about x-axis
+ // 2 = 2 peaks so differs above and below x-axis
+ //
+ // .. hardcode to 2 for the mo
+ m_pointsPerValue = 2;
+ header += getLittleEndianFromInteger(m_pointsPerValue, 4);
+
+ // Block size - default and recommended is 256
+ //
+ header += getLittleEndianFromInteger(m_blockSize, 4);
+
+ // Set channels up if they're currently empty
+ //
+ if (m_channels == 0 && m_audioFile)
+ m_channels = m_audioFile->getChannels();
+
+ // Peak channels - same as AudioFile channels
+ //
+ header += getLittleEndianFromInteger(m_channels, 4);
+
+ // Number of peak frames - we write this at close() and so
+ // for the moment put spacing 0's in.
+ header += getLittleEndianFromInteger(0, 4);
+
+ // Position of peak of peaks - written at close()
+ //
+ header += getLittleEndianFromInteger(0, 4);
+
+ // Offset to start of peaks - usually the total size of this header
+ //
+ header += getLittleEndianFromInteger(128, 4);
+
+ // Creation timestamp - fill in on close() so just use spacing
+ // of 28 bytes for the moment.
+ //
+ header += getLittleEndianFromInteger(0, 28);
+
+ // reserved space - 60 bytes
+ header += getLittleEndianFromInteger(0, 60);
+
+ //cout << "HEADER LENGTH = " << header.length() << endl;
+
+ // write out the header
+ //
+ putBytes(file, header);
+}
+
+bool
+PeakFile::scanToPeak(int peak)
+{
+ if (!m_inFile)
+ return false;
+
+ if (!m_inFile->is_open())
+ return false;
+
+ // Scan to start of chunk and then seek to peak number
+ //
+ ssize_t pos = (ssize_t)m_chunkStartPosition + 128 +
+ peak * m_format * m_channels * m_pointsPerValue;
+
+ ssize_t off = pos - m_inFile->tellg();
+
+ if (off == 0) {
+ return true;
+ } else if (off < 0) {
+ // std::cerr << "PeakFile::scanToPeak: warning: seeking backwards for peak " << peak << " (" << m_inFile->tellg() << " -> " << pos << ")" << std::endl;
+ m_inFile->seekg(pos);
+ } else {
+ m_inFile->seekg(off, std::ios::cur);
+ }
+
+ // Ensure we re-read the input buffer if we're
+ // doing buffered reads as it's now meaningless
+ //
+ m_loseBuffer = true;
+
+ if (m_inFile->eof()) {
+ m_inFile->clear();
+ return false;
+ }
+
+ return true;
+}
+
+bool
+PeakFile::scanForward(int numberOfPeaks)
+{
+ if (!m_inFile)
+ return false;
+
+ if (!m_inFile->is_open())
+ return false;
+
+ // Seek forward and number of peaks
+ //
+ m_inFile->seekg(numberOfPeaks * m_format * m_channels * m_pointsPerValue,
+ std::ios::cur);
+
+ // Ensure we re-read the input buffer
+ m_loseBuffer = true;
+
+ if (m_inFile->eof()) {
+ m_inFile->clear();
+ return false;
+ }
+
+ return true;
+}
+
+
+void
+PeakFile::writePeaks(unsigned short /*updatePercentage*/,
+ std::ofstream *file)
+{
+ if (!file || !(*file))
+ return ;
+ m_keepProcessing = true;
+
+#ifdef DEBUG_PEAKFILE
+
+ cout << "PeakFile::writePeaks - calculating peaks" << endl;
+#endif
+
+ // Scan to beginning of audio data
+ m_audioFile->scanTo(RealTime(0, 0));
+
+ // Store our samples
+ //
+ std::vector<std::pair<int, int> > channelPeaks;
+ std::string samples;
+ unsigned char *samplePtr;
+
+ int sampleValue;
+ int sampleMax = 0 ;
+ int sampleFrameCount = 0;
+
+ int channels = m_audioFile->getChannels();
+ int bytes = m_audioFile->getBitsPerSample() / 8;
+
+ m_format = bytes;
+ if (bytes == 3 || bytes == 4) // 24-bit PCM or 32-bit float
+ m_format = 2; // write 16-bit PCM instead
+
+ // for the progress dialog
+ unsigned int apprxTotalBytes = m_audioFile->getSize();
+ unsigned int byteCount = 0;
+
+ for (int i = 0; i < channels; i++)
+ channelPeaks.push_back(std::pair<int, int>());
+
+ // clear down info
+ m_numberOfPeaks = 0;
+ m_bodyBytes = 0;
+ m_positionPeakOfPeaks = 0;
+
+ while (m_keepProcessing) {
+ try {
+ samples = m_audioFile->
+ getBytes(m_blockSize * channels * bytes);
+ } catch (BadSoundFileException e) {
+ std::cerr << "PeakFile::writePeaks: " << e.getMessage()
+ << std::endl;
+ break;
+ }
+
+ // If no bytes or less than the total number of bytes are returned
+ // then break out
+ //
+ if (samples.length() == 0 ||
+ samples.length() < (m_blockSize * m_audioFile->getChannels()
+ * bytes))
+ break;
+
+ byteCount += samples.length();
+
+ emit setProgress((int)(double(byteCount) /
+ double(apprxTotalBytes) * 100.0));
+ kapp->processEvents();
+
+ samplePtr = (unsigned char *)samples.c_str();
+
+ for (int i = 0; i < m_blockSize; i++) {
+ for (unsigned int ch = 0; ch < m_audioFile->getChannels(); ch++) {
+ // Single byte format values range from 0-255 and then
+ // shifted down about the x-axis. Double byte and above
+ // are already centred about x-axis.
+ //
+ if (bytes == 1) {
+ // get value
+ sampleValue = int(*samplePtr) - 128;
+ samplePtr++;
+ } else if (bytes == 2) {
+ unsigned char b2 = samplePtr[0];
+ unsigned char b1 = samplePtr[1];
+ unsigned int bits = (b1 << 8) + b2;
+ sampleValue = (short)bits;
+ samplePtr += 2;
+ } else if (bytes == 3) {
+ unsigned char b3 = samplePtr[0];
+ unsigned char b2 = samplePtr[1];
+ unsigned char b1 = samplePtr[2];
+ unsigned int bits = (b1 << 24) + (b2 << 16) + (b3 << 8);
+
+ // write out as 16-bit (m_format == 2)
+ sampleValue = int(bits) / 65536;
+
+ samplePtr += 3;
+ } else if (bytes == 4) // IEEE float (enforced by RIFFAudioFile)
+ {
+ // write out as 16-bit (m_format == 2)
+ float val = *(float *)samplePtr;
+ sampleValue = (int)(32767.0 * val);
+ samplePtr += 4;
+ } else {
+ throw(BadSoundFileException(m_fileName, "PeakFile::writePeaks - unsupported bit depth"));
+ }
+
+ // First time for each channel
+ //
+ if (i == 0) {
+ channelPeaks[ch].first = sampleValue;
+ channelPeaks[ch].second = sampleValue;
+ } else {
+ // Compare and store
+ //
+ if (sampleValue > channelPeaks[ch].first)
+ channelPeaks[ch].first = sampleValue;
+
+ if (sampleValue < channelPeaks[ch].second)
+ channelPeaks[ch].second = sampleValue;
+ }
+
+ // Store peak of peaks if it fits
+ //
+ if (abs(sampleValue) > sampleMax) {
+ sampleMax = abs(sampleValue);
+ m_positionPeakOfPeaks = sampleFrameCount;
+ }
+ }
+
+ // for peak of peaks as well as frame count
+ sampleFrameCount++;
+ }
+
+ // Write absolute peak data in channel order
+ //
+ for (unsigned int i = 0; i < m_audioFile->getChannels(); i++) {
+ putBytes(file, getLittleEndianFromInteger(channelPeaks[i].first,
+ m_format));
+ putBytes(file, getLittleEndianFromInteger(channelPeaks[i].second,
+ m_format));
+ m_bodyBytes += m_format * 2;
+ }
+
+ // increment number of peak frames
+ m_numberOfPeaks++;
+ }
+
+#ifdef DEBUG_PEAKFILE
+ cout << "PeakFile::writePeaks - "
+ << "completed peaks" << endl;
+#endif
+
+}
+
+// Get a normalised vector for the preview at a given horizontal resolution.
+// We return a value for each channel and if returnLow is set we also return
+// an interleaved low value for each channel.
+//
+//
+std::vector<float>
+PeakFile::getPreview(const RealTime &startTime,
+ const RealTime &endTime,
+ int width,
+ bool showMinima)
+{
+#ifdef DEBUG_PEAKFILE_BRIEF
+ std::cout << "PeakFile::getPreview - "
+ << "startTime = " << startTime
+ << ", endTime = " << endTime
+ << ", width = " << width
+ << ", showMinima = " << showMinima << std::endl;
+#endif
+
+ if (getSize() == 0) {
+ std::cout << "PeakFile::getPreview - PeakFile size == 0" << std::endl;
+ return std::vector<float>();
+ }
+
+ // Regenerate cache on these conditions
+ //
+ if (!m_peakCache.length()) {
+#ifdef DEBUG_PEAKFILE_CACHE
+ std::cerr << "PeakFile::getPreview - no peak cache" << std::endl;
+#endif
+
+ if (getSize() < (256 *1024)) // if less than 256K PeakFile
+ {
+ // Scan to start of peak data
+ scanToPeak(0);
+ try
+ {
+ m_peakCache = getBytes(m_inFile, getSize() - 128);
+ } catch (BadSoundFileException e)
+ {
+ std::cerr << "PeakFile::getPreview: " << e.getMessage()
+ << std::endl;
+ }
+
+#ifdef DEBUG_PEAKFILE_CACHE
+ std::cout << "PeakFile::getPreview - generated peak cache - "
+ << "size = " << m_peakCache.length() << std::endl;
+#endif
+
+ } else {
+#ifdef DEBUG_PEAKFILE_CACHE
+ std::cout << "PeakFile::getPreview - file size = " << getSize()
+ << ", not generating cache" << std::endl;
+#endif
+
+ }
+ }
+
+ // Check to see if we hit the "lastPreview" cache by comparing the last
+ // query parameters we used.
+ //
+ if (startTime == m_lastPreviewStartTime && endTime == m_lastPreviewEndTime
+ && width == m_lastPreviewWidth && showMinima == m_lastPreviewShowMinima) {
+#ifdef DEBUG_PEAKFILE_CACHE
+ std::cout << "PeakFile::getPreview - hit last preview cache" << std::endl;
+#endif
+
+ return m_lastPreviewCache;
+ } else {
+#ifdef DEBUG_PEAKFILE_CACHE
+ std::cout << "PeakFile::getPreview - last preview " << m_lastPreviewStartTime
+ << " -> " << m_lastPreviewEndTime << ", w " << m_lastPreviewWidth << "; this " << startTime << " -> " << endTime << ", w " << width << std::endl;
+#endif
+
+ }
+
+ // Clear the cache - we need to regenerate it
+ //
+ m_lastPreviewCache.clear();
+
+ int startPeak = getPeak(startTime);
+ int endPeak = getPeak(endTime);
+
+ // Sanity check
+ if (startPeak > endPeak)
+ return m_lastPreviewCache;
+
+ // Actual possible sample length in RealTime
+ //
+ double step = double(endPeak - startPeak) / double(width);
+ std::string peakData;
+ int peakNumber;
+
+#ifdef DEBUG_PEAKFILE_BRIEF
+
+ std::cout << "PeakFile::getPreview - getting preview for \""
+ << m_audioFile->getFilename() << "\"" << endl;
+#endif
+
+ // Get a divisor
+ //
+ float divisor = 0.0f;
+ switch (m_format) {
+ case 1:
+ divisor = SAMPLE_MAX_8BIT;
+ break;
+
+ case 2:
+ divisor = SAMPLE_MAX_16BIT;
+ break;
+
+ default:
+#ifdef DEBUG_PEAKFILE_BRIEF
+
+ std::cout << "PeakFile::getPreview - "
+ << "unsupported peak length format (" << m_format << ")"
+ << endl;
+#endif
+
+ return m_lastPreviewCache;
+ }
+
+ float *hiValues = new float[m_channels];
+ float *loValues = new float[m_channels];
+
+ for (int i = 0; i < width; i++) {
+
+ peakNumber = startPeak + int(double(i) * step);
+ int nextPeakNumber = startPeak + int(double(i + 1) * step);
+
+ // Seek to value
+ //
+ if (!m_peakCache.length()) {
+
+ if (scanToPeak(peakNumber) == false) {
+#ifdef DEBUG_PEAKFILE
+ std::cout << "PeakFile::getPreview: scanToPeak(" << peakNumber << ") failed" << std::endl;
+#endif
+
+ m_lastPreviewCache.push_back(0.0f);
+ }
+ }
+#ifdef DEBUG_PEAKFILE
+ std::cout << "PeakFile::getPreview: step is " << step << ", format * pointsPerValue * chans is " << (m_format * m_pointsPerValue * m_channels) << std::endl;
+ std::cout << "i = " << i << ", peakNumber = " << peakNumber << ", nextPeakNumber = " << nextPeakNumber << std::endl;
+#endif
+
+ for (int ch = 0; ch < m_channels; ch++) {
+ hiValues[ch] = 0.0f;
+ loValues[ch] = 0.0f;
+ }
+
+ // Get peak value over channels
+ //
+ for (int k = 0; peakNumber < nextPeakNumber; ++k) {
+
+ for (int ch = 0; ch < m_channels; ch++) {
+
+ if (!m_peakCache.length()) {
+
+ try {
+ peakData = getBytes(m_inFile, m_format * m_pointsPerValue);
+ } catch (BadSoundFileException e) {
+ // Problem with the get - probably an EOF
+ // return the results so far.
+ //
+#ifdef DEBUG_PEAKFILE
+ std::cout << "PeakFile::getPreview - \"" << e.getMessage() << "\"\n"
+ << endl;
+#endif
+
+ goto done;
+ }
+#ifdef DEBUG_PEAKFILE
+ std::cout << "PeakFile::getPreview - "
+ << "read from file" << std::endl;
+#endif
+
+ } else {
+
+ int valueNum = peakNumber * m_channels + ch;
+ int charNum = valueNum * m_format * m_pointsPerValue;
+ int charLength = m_format * m_pointsPerValue;
+
+ // Get peak value from the cached string if
+ // the value is valid.
+ //
+ if (charNum + charLength <= m_peakCache.length()) {
+ peakData = m_peakCache.substr(charNum, charLength);
+#ifdef DEBUG_PEAKFILE
+
+ std::cout << "PeakFile::getPreview - "
+ << "hit peakCache" << std::endl;
+#endif
+
+ }
+ }
+
+
+ if (peakData.length() != (unsigned int)(m_format *
+ m_pointsPerValue)) {
+ // We didn't get the whole peak block - return what
+ // we've got so far
+ //
+#ifdef DEBUG_PEAKFILE
+ std::cout << "PeakFile::getPreview - "
+ << "failed to get complete peak block"
+ << endl;
+#endif
+
+ goto done;
+ }
+
+ int intDivisor = int(divisor);
+ int inValue =
+ getIntegerFromLittleEndian(peakData.substr(0, m_format));
+
+ while (inValue > intDivisor) {
+ inValue -= (1 << (m_format * 8));
+ }
+
+#ifdef DEBUG_PEAKFILE
+ std::cout << "found potential hivalue " << inValue << std::endl;
+#endif
+
+ if (k == 0 || inValue > hiValues[ch]) {
+ hiValues[ch] = float(inValue);
+ }
+
+ if (m_pointsPerValue == 2) {
+
+ inValue =
+ getIntegerFromLittleEndian(
+ peakData.substr(m_format, m_format));
+
+ while (inValue > intDivisor) {
+ inValue -= (1 << (m_format * 8));
+ }
+
+ if (k == 0 || inValue < loValues[ch]) {
+ loValues[ch] = inValue;
+ }
+ }
+ }
+
+ ++peakNumber;
+ }
+
+ for (int ch = 0; ch < m_channels; ++ch) {
+
+ float value = hiValues[ch] / divisor;
+
+#ifdef DEBUG_PEAKFILE_BRIEF
+
+ std::cout << "VALUE = " << hiValues[ch] / divisor << std::endl;
+#endif
+
+ if (showMinima) {
+ m_lastPreviewCache.push_back(loValues[ch] / divisor);
+ } else {
+ value = fabs(value);
+ if (m_pointsPerValue == 2) {
+ value = std::max(value, fabsf(loValues[ch] / divisor));
+ }
+ m_lastPreviewCache.push_back(value);
+ }
+ }
+ }
+
+done:
+ resetStream();
+ delete[] hiValues;
+ delete[] loValues;
+
+ // We have a good preview in the cache so store our parameters
+ //
+ m_lastPreviewStartTime = startTime;
+ m_lastPreviewEndTime = endTime;
+ m_lastPreviewWidth = width;
+ m_lastPreviewShowMinima = showMinima;
+
+#ifdef DEBUG_PEAKFILE_BRIEF
+
+ std::cout << "Returning " << m_lastPreviewCache.size() << " items" << std::endl;
+#endif
+
+ return m_lastPreviewCache;
+}
+
+int
+PeakFile::getPeak(const RealTime &time)
+{
+ double frames = ((time.sec * 1000000.0) + time.usec()) *
+ m_audioFile->getSampleRate() / 1000000.0;
+ return int(frames / double(m_blockSize));
+}
+
+RealTime
+PeakFile::getTime(int peak)
+{
+ int usecs = int((double)peak * (double)m_blockSize *
+ double(1000000.0) / double(m_audioFile->getSampleRate()));
+ return RealTime(usecs / 1000000, (usecs % 1000000) * 1000);
+}
+
+// Get pairs of split points for areas that exceed a percentage
+// threshold
+//
+std::vector<SplitPointPair>
+PeakFile::getSplitPoints(const RealTime &startTime,
+ const RealTime &endTime,
+ int threshold,
+ const RealTime &minLength)
+{
+ std::vector<SplitPointPair> points;
+ std::string peakData;
+
+ int startPeak = getPeak(startTime);
+ int endPeak = getPeak(endTime);
+
+ if (endPeak < startPeak)
+ return std::vector<SplitPointPair>();
+
+ scanToPeak(startPeak);
+
+ float divisor = 0.0f;
+ switch (m_format) {
+ case 1:
+ divisor = SAMPLE_MAX_8BIT;
+ break;
+
+ case 2:
+ divisor = SAMPLE_MAX_16BIT;
+ break;
+
+ default:
+ return points;
+ }
+
+ float value;
+ float fThreshold = float(threshold) / 100.0;
+ bool belowThreshold = true;
+ RealTime startSplit = RealTime::zeroTime;
+ bool inSplit = false;
+
+ for (int i = startPeak; i < endPeak; i++) {
+ value = 0.0;
+
+ for (int ch = 0; ch < m_channels; ch++) {
+ try {
+ peakData = getBytes(m_inFile, m_format * m_pointsPerValue);
+ } catch (BadSoundFileException e) {
+ std::cerr << "PeakFile::getSplitPoints: "
+ << e.getMessage() << std::endl;
+ break;
+ }
+
+ if (peakData.length() == (unsigned int)(m_format *
+ m_pointsPerValue)) {
+ int peakValue =
+ getIntegerFromLittleEndian(peakData.substr(0, m_format));
+
+ value += fabs(float(peakValue) / divisor);
+ }
+ }
+
+ value /= float(m_channels);
+
+ if (belowThreshold) {
+ if (value > fThreshold) {
+ startSplit = getTime(i);
+ inSplit = true;
+ belowThreshold = false;
+ }
+ } else {
+ if (value < fThreshold && getTime(i) - startSplit > minLength) {
+ // insert values
+ if (inSplit) {
+ points.push_back(SplitPointPair(startSplit, getTime(i)));
+ }
+ inSplit = false;
+ belowThreshold = true;
+ }
+ }
+ }
+
+ // if we've got a split point open the close it
+ if (inSplit) {
+ points.push_back(SplitPointPair(startSplit,
+ getTime(endPeak)));
+ }
+
+ return points;
+}
+
+
+}
+
+
+#include "PeakFile.moc"
diff --git a/src/sound/PeakFile.h b/src/sound/PeakFile.h
new file mode 100644
index 0000000..26ef71c
--- /dev/null
+++ b/src/sound/PeakFile.h
@@ -0,0 +1,196 @@
+// -*- c-indentation-style:"stroustrup" c-basic-offset: 4 -*- /*
+/*
+ Rosegarden
+ A sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <bownie@bownie.com>
+
+ 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 <vector>
+
+#include <qobject.h>
+#include <qdatetime.h>
+
+#include "SoundFile.h"
+#include "RealTime.h"
+
+#ifndef _PEAKFILE_H_
+#define _PEAKFILE_H_
+
+// A PeakFile is generated to the BWF Supplement 3 Peak Envelope Chunk
+// format as defined here:
+//
+// http://www.ebu.ch/pmc_bwf.html
+//
+// To comply with BWF format files this chunk can be embedded into
+// the sample file itself (writeToHandle()) or used to generate an
+// external peak file (write()). At the moment the only type of file
+// with an embedded peak chunk is the BWF file itself.
+//
+//
+
+
+
+namespace Rosegarden
+{
+
+class AudioFile;
+
+
+typedef std::pair<RealTime, RealTime> SplitPointPair;
+
+class PeakFile : public QObject, public SoundFile
+{
+ Q_OBJECT
+
+public:
+ PeakFile(AudioFile *audioFile);
+ virtual ~PeakFile();
+
+ // Copy constructor
+ //
+ PeakFile(const PeakFile &);
+
+ // Standard file methods
+ //
+ virtual bool open();
+ virtual void close();
+
+ // Write to standard peak file
+ //
+ virtual bool write();
+
+ // Write the file, emit progress signal and process app events
+ //
+ virtual bool write(unsigned short updatePercentage);
+
+ // Write peak chunk to file handle (BWF)
+ //
+ bool writeToHandle(std::ofstream *file, unsigned short updatePercentage);
+
+ // Is the peak file valid and up to date?
+ //
+ bool isValid();
+
+ // Vital file stats
+ //
+ void printStats();
+
+ // Get a preview of a section of the audio file where that section
+ // is "width" pixels.
+ //
+ std::vector<float> getPreview(const RealTime &startTime,
+ const RealTime &endTime,
+ int width,
+ bool showMinima);
+
+ AudioFile* getAudioFile() { return m_audioFile; }
+ const AudioFile* getAudioFile() const { return m_audioFile; }
+
+ // Scan to a peak and scan forward a number of peaks
+ //
+ bool scanToPeak(int peak);
+ bool scanForward(int numberOfPeaks);
+
+ // Find threshold crossing points
+ //
+ std::vector<SplitPointPair> getSplitPoints(const RealTime &startTime,
+ const RealTime &endTime,
+ int threshold,
+ const RealTime &minLength);
+ // Accessors
+ //
+ int getVersion() const { return m_version; }
+ int getFormat() const { return m_format; }
+ int getPointsPerValue() const { return m_pointsPerValue; }
+ int getBlockSize() const { return m_blockSize; }
+ int getChannels() const { return m_channels; }
+ int getNumberOfPeaks() const { return m_numberOfPeaks; }
+ int getPositionPeakOfPeaks() const { return m_positionPeakOfPeaks; }
+ int getOffsetToPeaks() const { return m_offsetToPeaks; }
+ int getBodyBytes() const { return m_bodyBytes; }
+ QDateTime getModificationTime() const { return m_modificationTime; }
+ std::streampos getChunkStartPosition() const
+ { return m_chunkStartPosition; }
+
+ bool isProcessingPeaks() const { return m_keepProcessing; }
+ void setProcessingPeaks(bool value) { m_keepProcessing = value; }
+
+signals:
+ void setProgress(int);
+
+protected:
+ // Write the peak header and the peaks themselves
+ //
+ void writeHeader(std::ofstream *file);
+ void writePeaks(unsigned short updatePercentage,
+ std::ofstream *file);
+
+ // Get the position of a peak for a given time
+ //
+ int getPeak(const RealTime &time);
+
+ // And the time of a peak
+ //
+ RealTime getTime(int peak);
+
+ // Parse the header
+ //
+ void parseHeader();
+
+ AudioFile *m_audioFile;
+
+ // Some Peak Envelope Chunk parameters
+ //
+ int m_version;
+ int m_format; // bytes in peak value (1 or 2)
+ int m_pointsPerValue;
+ int m_blockSize;
+ int m_channels;
+ int m_numberOfPeaks;
+ int m_positionPeakOfPeaks;
+ int m_offsetToPeaks;
+ int m_bodyBytes;
+
+ // Peak timestamp
+ //
+ QDateTime m_modificationTime;
+
+ std::streampos m_chunkStartPosition;
+
+ // For cacheing of peak information in memory we use the last query
+ // parameters as our key to the cached data.
+ //
+ RealTime m_lastPreviewStartTime;
+ RealTime m_lastPreviewEndTime;
+ int m_lastPreviewWidth;
+ bool m_lastPreviewShowMinima;
+ std::vector<float> m_lastPreviewCache;
+
+ // Do we actually want to keep processing this peakfile?
+ // In case we get a cancel.
+ //
+ bool m_keepProcessing;
+
+ std::string m_peakCache;
+
+};
+
+}
+
+
+#endif // _PEAKFILE_H_
+
+
diff --git a/src/sound/PeakFileManager.cpp b/src/sound/PeakFileManager.cpp
new file mode 100644
index 0000000..10293e6
--- /dev/null
+++ b/src/sound/PeakFileManager.cpp
@@ -0,0 +1,327 @@
+// -*- c-basic-offset: 4 -*-
+
+/*
+ Rosegarden
+ A sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <bownie@bownie.com>
+
+ 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.
+*/
+
+
+// Accepts a file handle positioned somewhere in sample data (could
+// be at the start) along with the necessary meta information for
+// decoding (channels, bits per sample) and turns the sample data
+// into peak data and generates a BWF format peak chunk file. This
+// file can exist by itself (in the case this is being generated
+// by a WAV) or be accomodated inside a BWF format file.
+//
+//
+
+#include <string>
+#include <vector>
+
+#include <qobject.h>
+
+#include "PeakFileManager.h"
+#include "AudioFile.h"
+#include "RealTime.h"
+#include "PeakFile.h"
+
+namespace Rosegarden
+{
+
+
+PeakFileManager::PeakFileManager():
+ m_updatePercentage(0),
+ m_currentPeakFile(0)
+{}
+
+PeakFileManager::~PeakFileManager()
+{}
+
+// Inserts PeakFile based on AudioFile if it doesn't already exist
+bool
+PeakFileManager::insertAudioFile(AudioFile *audioFile)
+{
+ std::vector<PeakFile*>::iterator it;
+
+ for (it = m_peakFiles.begin(); it != m_peakFiles.end(); it++) {
+ if ((*it)->getAudioFile()->getId() == audioFile->getId())
+ return false;
+ }
+
+ /*
+ std::cout << "PeakFileManager::insertAudioFile - creating peak file "
+ << m_peakFiles.size() + 1
+ << " for \"" << audioFile->getFilename()
+ << "\"" << std::endl;
+ */
+
+ // Insert
+ m_peakFiles.push_back(new PeakFile(audioFile));
+
+ return true;
+}
+
+// Removes peak file from PeakFileManager - doesn't affect audioFile
+//
+bool
+PeakFileManager::removeAudioFile(AudioFile *audioFile)
+{
+ std::vector<PeakFile*>::iterator it;
+
+ for (it = m_peakFiles.begin(); it != m_peakFiles.end(); it++) {
+ if ((*it)->getAudioFile()->getId() == audioFile->getId()) {
+ if (m_currentPeakFile == *it)
+ m_currentPeakFile = 0;
+ delete *it;
+ m_peakFiles.erase(it);
+ return true;
+ }
+ }
+
+ return false;
+}
+
+// Auto-insert PeakFile into manager if it doesn't already exist
+//
+PeakFile*
+PeakFileManager::getPeakFile(AudioFile *audioFile)
+{
+ std::vector<PeakFile*>::iterator it;
+ PeakFile *ptr = 0;
+
+ while (ptr == 0) {
+ for (it = m_peakFiles.begin(); it != m_peakFiles.end(); it++)
+ if ((*it)->getAudioFile()->getId() == audioFile->getId())
+ ptr = *it;
+
+ // If nothing is found then insert and retry
+ //
+ if (ptr == 0) {
+ // Insert - if we fail we return as empty
+ //
+ if (insertAudioFile(audioFile) == false)
+ return 0;
+ }
+ }
+
+ return ptr;
+}
+
+
+// Does a given AudioFile have a valid peak file or peak chunk?
+//
+bool
+PeakFileManager::hasValidPeaks(AudioFile *audioFile)
+{
+ if (audioFile->getType() == WAV) {
+ // Check external peak file
+ PeakFile *peakFile = getPeakFile(audioFile);
+
+ if (peakFile == 0) {
+#ifdef DEBUG_PEAKFILEMANAGER
+ std::cerr << "PeakFileManager::hasValidPeaks - no peak file found"
+ << std::endl;
+#endif
+
+ return false;
+ }
+ // If it doesn't open and parse correctly
+ if (peakFile->open() == false)
+ return false;
+
+ // or if the data is old or invalid
+ if (peakFile->isValid() == false)
+ return false;
+
+ } else if (audioFile->getType() == BWF) {
+ // check internal peak chunk
+ } else {
+#ifdef DEBUG_PEAKFILEMANAGER
+ std::cout << "PeakFileManager::hasValidPeaks - unsupported file type"
+ << std::endl;
+#endif
+
+ return false;
+ }
+
+ return true;
+
+}
+
+// Generate the peak file. Checks to see if peak file exists
+// already and if so if it's up to date. If it isn't then we
+// regenerate.
+//
+void
+PeakFileManager::generatePeaks(AudioFile *audioFile,
+ unsigned short updatePercentage)
+{
+#ifdef DEBUG_PEAKFILEMANAGER
+ std::cout << "PeakFileManager::generatePeaks - generating peaks for \""
+ << audioFile->getFilename() << "\"" << std::endl;
+#endif
+
+ if (audioFile->getType() == WAV) {
+ m_currentPeakFile = getPeakFile(audioFile);
+
+ QObject::connect(m_currentPeakFile, SIGNAL(setProgress(int)),
+ this, SIGNAL(setProgress(int)));
+
+ // Just write out a peak file
+ //
+ if (m_currentPeakFile->write(updatePercentage) == false) {
+ std::cerr << "Can't write peak file for " << audioFile->getFilename() << " - no preview generated" << std::endl;
+ throw BadPeakFileException
+ (audioFile->getFilename(), __FILE__, __LINE__);
+ }
+
+ // The m_currentPeakFile might have been cancelled (see stopPreview())
+ //
+ if (m_currentPeakFile) {
+ // close writes out important things
+ m_currentPeakFile->close();
+ m_currentPeakFile->disconnect();
+ }
+ } else if (audioFile->getType() == BWF) {
+ // write the file out and incorporate the peak chunk
+ } else {
+#ifdef DEBUG_PEAKFILEMANAGER
+ std::cerr << "PeakFileManager::generatePeaks - unsupported file type"
+ << std::endl;
+#endif
+
+ return ;
+ }
+
+ m_currentPeakFile = 0;
+
+}
+
+std::vector<float>
+PeakFileManager::getPreview(AudioFile *audioFile,
+ const RealTime &startTime,
+ const RealTime &endTime,
+ int width,
+ bool showMinima)
+{
+ std::vector<float> rV;
+
+ // If we've got no channels then the audio file hasn't
+ // completed (recording) - so don't generate a preview
+ //
+ if (audioFile->getChannels() == 0)
+ return rV;
+
+ if (audioFile->getType() == WAV) {
+ PeakFile *peakFile = getPeakFile(audioFile);
+
+ // just write out a peak file
+ try {
+ peakFile->open();
+ rV = peakFile->getPreview(startTime,
+ endTime,
+ width,
+ showMinima);
+ } catch (SoundFile::BadSoundFileException e) {
+#ifdef DEBUG_PEAKFILEMANAGER
+ std::cout << "PeakFileManager::getPreview "
+ << "\"" << e << "\"" << std::endl;
+#else
+
+ ;
+#endif
+
+ throw BadPeakFileException(e);
+ }
+ } else if (audioFile->getType() == BWF) {
+ // write the file out and incorporate the peak chunk
+ }
+#ifdef DEBUG_PEAKFILEMANAGER
+ else {
+ std::cerr << "PeakFileManager::getPreview - unsupported file type"
+ << std::endl;
+ }
+#endif
+
+ return rV;
+}
+
+void
+PeakFileManager::clear()
+{
+ std::vector<PeakFile*>::iterator it;
+
+ for (it = m_peakFiles.begin(); it != m_peakFiles.end(); it++)
+ delete (*it);
+
+ m_peakFiles.erase(m_peakFiles.begin(), m_peakFiles.end());
+
+ m_currentPeakFile = 0;
+}
+
+
+std::vector<SplitPointPair>
+PeakFileManager::getSplitPoints(AudioFile *audioFile,
+ const RealTime &startTime,
+ const RealTime &endTime,
+ int threshold,
+ const RealTime &minTime)
+{
+ PeakFile *peakFile = getPeakFile(audioFile);
+
+ if (peakFile == 0)
+ return std::vector<SplitPointPair>();
+
+ return peakFile->getSplitPoints(startTime,
+ endTime,
+ threshold,
+ minTime);
+
+}
+
+void
+PeakFileManager::stopPreview()
+{
+ if (m_currentPeakFile) {
+ // Stop processing
+ //
+ QString fileName = QString(m_currentPeakFile->getFilename().data());
+ m_currentPeakFile->setProcessingPeaks(false);
+ m_currentPeakFile->disconnect();
+
+ QFile file(fileName);
+ bool removed = file.remove();
+
+#ifdef DEBUG_PEAKFILEMANAGER
+
+ if (removed) {
+ std::cout << "PeakFileManager::stopPreview() - removed preview"
+ << std::endl;
+ }
+#endif
+ //delete m_currentPeakFile;
+ m_currentPeakFile = 0;
+ }
+}
+
+
+
+
+}
+
+
+#include "PeakFileManager.moc"
diff --git a/src/sound/PeakFileManager.h b/src/sound/PeakFileManager.h
new file mode 100644
index 0000000..07ff704
--- /dev/null
+++ b/src/sound/PeakFileManager.h
@@ -0,0 +1,162 @@
+// -*- c-basic-offset: 4 -*-
+
+/*
+ Rosegarden
+ A sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <bownie@bownie.com>
+
+ 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.
+*/
+
+
+// Accepts an AudioFIle and turns the sample data into peak data for
+// storage in a peak file or a BWF format peak chunk. Pixmaps or
+// sample data is returned to callers on demand using these cached
+// values.
+//
+//
+
+#ifndef _PEAKFILEMANAGER_H_
+#define _PEAKFILEMANAGER_H_
+
+#include <string>
+#include <iostream>
+#include <fstream>
+#include <vector>
+
+#include <qobject.h>
+
+
+#include "PeakFile.h"
+
+namespace Rosegarden
+{
+
+class AudioFile;
+class RealTime;
+
+class PeakFileManager : public QObject
+{
+ Q_OBJECT
+public:
+ // updatePercentage tells this object how often to throw a
+ // percentage complete message - active between 0-100 only
+ // if it's set to 5 then we send an update exception every
+ // five percent. The percentage complete is sent with
+ // each exception.
+ //
+ PeakFileManager();
+ virtual ~PeakFileManager();
+
+ class BadPeakFileException : public Exception
+ {
+ public:
+ BadPeakFileException(std::string path) :
+ Exception("Bad peak file " + path), m_path(path) { }
+ BadPeakFileException(std::string path, std::string file, int line) :
+ Exception("Bad peak file " + path, file, line), m_path(path) { }
+ BadPeakFileException(const SoundFile::BadSoundFileException &e) :
+ Exception("Bad peak file (malformed audio?) " + e.getPath()), m_path(e.getPath()) { }
+
+ ~BadPeakFileException() throw() { }
+
+ std::string getPath() const { return m_path; }
+
+ private:
+ std::string m_path;
+ };
+
+private:
+ PeakFileManager(const PeakFileManager &pFM);
+ PeakFileManager& operator=(const PeakFileManager &);
+
+public:
+ // Check that a given audio file has a valid and up to date
+ // peak file or peak chunk.
+ //
+ bool hasValidPeaks(AudioFile *audioFile);
+ // throw BadSoundFileException, BadPeakFileException
+
+ // Generate a peak file from file details - if the peak file already
+ // exists _and_ it's up to date then we don't do anything. For BWF
+ // files we generate an internal peak chunk.
+ //
+ //
+ void generatePeaks(AudioFile *audioFile,
+ unsigned short updatePercentage);
+ // throw BadSoundFileException, BadPeakFileException
+
+ // Get a vector of floats as the preview
+ //
+ std::vector<float> getPreview(AudioFile *audioFile,
+ const RealTime &startTime,
+ const RealTime &endTime,
+ int width,
+ bool showMinima);
+ // throw BadSoundFileException, BadPeakFileException
+
+ // Remove cache for a single audio file (if audio file to be deleted etc)
+ //
+ bool removeAudioFile(AudioFile *audioFile);
+
+ // Clear down
+ //
+ void clear();
+
+ // Get split points for a peak file
+ //
+ std::vector<SplitPointPair>
+ getSplitPoints(AudioFile *audioFile,
+ const RealTime &startTime,
+ const RealTime &endTime,
+ int threshold,
+ const RealTime &minTime);
+
+ std::vector<PeakFile*>::const_iterator begin() const
+ { return m_peakFiles.begin(); }
+
+ std::vector<PeakFile*>::const_iterator end() const
+ { return m_peakFiles.end(); }
+
+ // Stop a preview during its build
+ //
+ void stopPreview();
+
+signals:
+ void setProgress(int);
+
+protected:
+
+ // Add and remove from our PeakFile cache
+ //
+ bool insertAudioFile(AudioFile *audioFile);
+ PeakFile* getPeakFile(AudioFile *audioFile);
+
+ std::vector<PeakFile*> m_peakFiles;
+ unsigned short m_updatePercentage; // how often we send updates
+
+ // Whilst processing - the current PeakFile
+ //
+ PeakFile *m_currentPeakFile;
+
+
+};
+
+
+}
+
+
+#endif // _PEAKFILEMANAGER_H_
+
+
diff --git a/src/sound/PlayableAudioFile.cpp b/src/sound/PlayableAudioFile.cpp
new file mode 100644
index 0000000..b5ddcf7
--- /dev/null
+++ b/src/sound/PlayableAudioFile.cpp
@@ -0,0 +1,1086 @@
+// -*- c-basic-offset: 4 -*-
+
+/*
+ Rosegarden
+ A sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <bownie@bownie.com>
+
+ 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 "PlayableAudioFile.h"
+#include <cassert>
+
+namespace Rosegarden
+{
+
+//#define DEBUG_RING_BUFFER_POOL 1
+//#define DEBUG_PLAYABLE 1
+//#define DEBUG_PLAYABLE_READ 1
+
+class RingBufferPool
+{
+public:
+ typedef float sample_t;
+
+ RingBufferPool(size_t bufferSize);
+ virtual ~RingBufferPool();
+
+ /**
+ * Set the default size for buffers. Buffers currently allocated
+ * will not be resized until they are returned.
+ */
+ void setBufferSize(size_t n);
+
+ size_t getBufferSize() const
+ {
+ return m_bufferSize;
+ }
+
+ /**
+ * Discard or create buffers as necessary so as to have n buffers
+ * in the pool. This will not discard any buffers that are
+ * currently allocated, so if more than n are allocated, more than
+ * n will remain.
+ */
+ void setPoolSize(size_t n);
+
+ size_t getPoolSize() const
+ {
+ return m_buffers.size();
+ }
+
+ /**
+ * Return true if n buffers available, false otherwise.
+ */
+ bool getBuffers(size_t n, RingBuffer<sample_t> **buffers);
+
+ /**
+ * Return a buffer to the pool.
+ */
+ void returnBuffer(RingBuffer<sample_t> *buffer);
+
+protected:
+ // Want to avoid memory allocation if possible when marking a buffer
+ // unallocated or allocated, so we use a single container for all
+
+ typedef std::pair<RingBuffer<sample_t> *, bool> AllocPair;
+ typedef std::vector<AllocPair> AllocList;
+ AllocList m_buffers;
+
+ size_t m_bufferSize;
+ size_t m_available;
+
+ pthread_mutex_t m_lock;
+};
+
+
+RingBufferPool::RingBufferPool(size_t bufferSize) :
+ m_bufferSize(bufferSize),
+ m_available(0)
+{
+ pthread_mutex_t initialisingMutex = PTHREAD_MUTEX_INITIALIZER;
+ memcpy(&m_lock, &initialisingMutex, sizeof(pthread_mutex_t));
+}
+
+RingBufferPool::~RingBufferPool()
+{
+ size_t allocatedCount = 0;
+ for (AllocList::iterator i = m_buffers.begin(); i != m_buffers.end(); ++i) {
+ if (i->second)
+ ++allocatedCount;
+ }
+
+ if (allocatedCount > 0) {
+ std::cerr << "WARNING: RingBufferPool::~RingBufferPool: deleting pool with " << allocatedCount << " allocated buffers" << std::endl;
+ }
+
+ for (AllocList::iterator i = m_buffers.begin(); i != m_buffers.end(); ++i) {
+ delete i->first;
+ }
+
+ m_buffers.clear();
+
+ pthread_mutex_destroy(&m_lock);
+}
+
+void
+RingBufferPool::setBufferSize(size_t n)
+{
+ if (m_bufferSize == n)
+ return ;
+
+ pthread_mutex_lock(&m_lock);
+
+#ifdef DEBUG_RING_BUFFER_POOL
+
+ std::cerr << "RingBufferPool::setBufferSize: from " << m_bufferSize
+ << " to " << n << std::endl;
+ int c = 0;
+#endif
+
+ for (AllocList::iterator i = m_buffers.begin(); i != m_buffers.end(); ++i) {
+ if (!i->second) {
+ delete i->first;
+ i->first = new RingBuffer<sample_t>(n);
+#ifdef DEBUG_RING_BUFFER_POOL
+
+ std::cerr << "Resized buffer " << c++ << std::endl;
+#endif
+
+ } else {
+#ifdef DEBUG_RING_BUFFER_POOL
+ std::cerr << "Buffer " << c++ << " is already in use, resizing in place" << std::endl;
+#endif
+
+ i->first->resize(n);
+ }
+ }
+
+ m_bufferSize = n;
+ pthread_mutex_unlock(&m_lock);
+}
+
+void
+RingBufferPool::setPoolSize(size_t n)
+{
+ pthread_mutex_lock(&m_lock);
+
+#ifdef DEBUG_RING_BUFFER_POOL
+
+ std::cerr << "RingBufferPool::setPoolSize: from " << m_buffers.size()
+ << " to " << n << std::endl;
+#endif
+
+ size_t allocatedCount = 0, count = 0;
+
+ for (AllocList::iterator i = m_buffers.begin(); i != m_buffers.end(); ++i) {
+ if (i->second)
+ ++allocatedCount;
+ ++count;
+ }
+
+ if (count > n) {
+ for (AllocList::iterator i = m_buffers.begin(); i != m_buffers.end(); ) {
+ if (!i->second) {
+ delete i->first;
+ m_buffers.erase(i);
+ if (--count == n)
+ break;
+ } else {
+ ++i;
+ }
+ }
+ }
+
+ while (count < n) {
+ m_buffers.push_back(AllocPair(new RingBuffer<sample_t>(m_bufferSize),
+ false));
+ ++count;
+ }
+
+ m_available = std::max(allocatedCount, n) - allocatedCount;
+
+#ifdef DEBUG_RING_BUFFER_POOL
+
+ std::cerr << "RingBufferPool::setPoolSize: have " << m_buffers.size()
+ << " buffers (" << allocatedCount << " allocated, " << m_available << " available)" << std::endl;
+#endif
+
+ pthread_mutex_unlock(&m_lock);
+}
+
+bool
+RingBufferPool::getBuffers(size_t n, RingBuffer<sample_t> **buffers)
+{
+ pthread_mutex_lock(&m_lock);
+
+ size_t count = 0;
+
+ for (AllocList::iterator i = m_buffers.begin(); i != m_buffers.end(); ++i) {
+ if (!i->second && ++count == n)
+ break;
+ }
+
+ if (count < n) {
+#ifdef DEBUG_RING_BUFFER_POOL
+ std::cerr << "RingBufferPool::getBuffers(" << n << "): not available (in pool of " << m_buffers.size() << "), resizing" << std::endl;
+#endif
+
+ AllocList newBuffers;
+
+ while (count < n) {
+ for (size_t i = 0; i < m_buffers.size(); ++i) {
+ newBuffers.push_back(m_buffers[i]);
+ }
+ for (size_t i = 0; i < m_buffers.size(); ++i) {
+ newBuffers.push_back(AllocPair(new RingBuffer<sample_t>(m_bufferSize),
+ false));
+ }
+ count += m_buffers.size();
+ m_available += m_buffers.size();
+ }
+
+ m_buffers = newBuffers;
+ }
+
+ count = 0;
+
+#ifdef DEBUG_RING_BUFFER_POOL
+
+ std::cerr << "RingBufferPool::getBuffers(" << n << "): available" << std::endl;
+#endif
+
+ for (AllocList::iterator i = m_buffers.begin(); i != m_buffers.end(); ++i) {
+ if (!i->second) {
+ i->second = true;
+ i->first->reset();
+ i->first->mlock();
+ buffers[count] = i->first;
+ --m_available;
+ if (++count == n)
+ break;
+ }
+ }
+
+#ifdef DEBUG_RING_BUFFER_POOL
+ std::cerr << "RingBufferPool::getBuffers: " << m_available << " remain in pool of " << m_buffers.size() << std::endl;
+#endif
+
+ pthread_mutex_unlock(&m_lock);
+ return true;
+}
+
+void
+RingBufferPool::returnBuffer(RingBuffer<sample_t> *buffer)
+{
+ pthread_mutex_lock(&m_lock);
+
+#ifdef DEBUG_RING_BUFFER_POOL
+
+ std::cerr << "RingBufferPool::returnBuffer" << std::endl;
+#endif
+
+ buffer->munlock();
+
+ for (AllocList::iterator i = m_buffers.begin(); i != m_buffers.end(); ++i) {
+ if (i->first == buffer) {
+ i->second = false;
+ ++m_available;
+ if (buffer->getSize() != m_bufferSize) {
+ delete buffer;
+ i->first = new RingBuffer<sample_t>(m_bufferSize);
+ }
+ }
+ }
+
+#ifdef DEBUG_RING_BUFFER_POOL
+ std::cerr << "RingBufferPool::returnBuffer: " << m_available << " remain in pool of " << m_buffers.size() << std::endl;
+#endif
+
+ pthread_mutex_unlock(&m_lock);
+}
+
+
+AudioCache PlayableAudioFile::m_smallFileCache;
+
+std::vector<PlayableAudioFile::sample_t *> PlayableAudioFile::m_workBuffers;
+size_t PlayableAudioFile::m_workBufferSize = 0;
+
+char *PlayableAudioFile::m_rawFileBuffer;
+size_t PlayableAudioFile::m_rawFileBufferSize = 0;
+
+RingBufferPool *PlayableAudioFile::m_ringBufferPool = 0;
+
+size_t PlayableAudioFile::m_xfadeFrames = 30;
+
+PlayableAudioFile::PlayableAudioFile(InstrumentId instrumentId,
+ AudioFile *audioFile,
+ const RealTime &startTime,
+ const RealTime &startIndex,
+ const RealTime &duration,
+ size_t bufferSize,
+ size_t smallFileSize,
+ int targetChannels,
+ int targetSampleRate) :
+ m_startTime(startTime),
+ m_startIndex(startIndex),
+ m_duration(duration),
+ m_file(0),
+ m_audioFile(audioFile),
+ m_instrumentId(instrumentId),
+ m_targetChannels(targetChannels),
+ m_targetSampleRate(targetSampleRate),
+ m_fileEnded(false),
+ m_firstRead(true),
+ m_runtimeSegmentId( -1),
+ m_isSmallFile(false),
+ m_currentScanPoint(RealTime::zeroTime),
+ m_smallFileScanFrame(0),
+ m_autoFade(false),
+ m_fadeInTime(RealTime::zeroTime),
+ m_fadeOutTime(RealTime::zeroTime)
+{
+#ifdef DEBUG_PLAYABLE
+ std::cerr << "PlayableAudioFile::PlayableAudioFile - creating " << this << " for instrument " << instrumentId << " with file " << (m_audioFile ? m_audioFile->getShortFilename() : "(none)") << std::endl;
+#endif
+
+ if (!m_ringBufferPool) {
+ //!!! Problematic -- how do we deal with different playable audio
+ // files requiring different buffer sizes? That shouldn't be the
+ // usual case, but it's not unthinkable.
+ m_ringBufferPool = new RingBufferPool(bufferSize);
+ } else {
+ m_ringBufferPool->setBufferSize
+ (std::max(bufferSize, m_ringBufferPool->getBufferSize()));
+ }
+
+ initialise(bufferSize, smallFileSize);
+}
+
+
+void
+PlayableAudioFile::setRingBufferPoolSizes(size_t n, size_t nframes)
+{
+ if (!m_ringBufferPool) {
+ m_ringBufferPool = new RingBufferPool(nframes);
+ } else {
+ m_ringBufferPool->setBufferSize
+ (std::max(nframes, m_ringBufferPool->getBufferSize()));
+ }
+ m_ringBufferPool->setPoolSize(n);
+}
+
+
+void
+PlayableAudioFile::initialise(size_t bufferSize, size_t smallFileSize)
+{
+#ifdef DEBUG_PLAYABLE
+ std::cerr << "PlayableAudioFile::initialise() " << this << std::endl;
+#endif
+
+ checkSmallFileCache(smallFileSize);
+
+ if (!m_isSmallFile) {
+
+ m_file = new std::ifstream(m_audioFile->getFilename().c_str(),
+ std::ios::in | std::ios::binary);
+
+ if (!*m_file) {
+ std::cerr << "ERROR: PlayableAudioFile::initialise: Failed to open audio file " << m_audioFile->getFilename() << std::endl;
+ delete m_file;
+ m_file = 0;
+ }
+ }
+
+ // Scan to the beginning of the data chunk we need
+ //
+#ifdef DEBUG_PLAYABLE
+ std::cerr << "PlayableAudioFile::initialise - scanning to " << m_startIndex << std::endl;
+#endif
+
+ if (m_file) {
+ scanTo(m_startIndex);
+ } else {
+ m_fileEnded = false;
+ m_currentScanPoint = m_startIndex;
+ m_smallFileScanFrame = RealTime::realTime2Frame
+ (m_currentScanPoint, m_audioFile->getSampleRate());
+ }
+
+#ifdef DEBUG_PLAYABLE
+ std::cerr << "PlayableAudioFile::initialise: buffer size is " << bufferSize << " frames, file size is " << m_audioFile->getSize() << std::endl;
+#endif
+
+ if (m_targetChannels <= 0)
+ m_targetChannels = m_audioFile->getChannels();
+ if (m_targetSampleRate <= 0)
+ m_targetSampleRate = m_audioFile->getSampleRate();
+
+ m_ringBuffers = new RingBuffer<sample_t> *[m_targetChannels];
+ for (int ch = 0; ch < m_targetChannels; ++ch) {
+ m_ringBuffers[ch] = 0;
+ }
+}
+
+PlayableAudioFile::~PlayableAudioFile()
+{
+ if (m_file) {
+ m_file->close();
+ delete m_file;
+ }
+
+ returnRingBuffers();
+ delete[] m_ringBuffers;
+ m_ringBuffers = 0;
+
+ if (m_isSmallFile) {
+ m_smallFileCache.decrementReference(m_audioFile);
+ }
+
+#ifdef DEBUG_PLAYABLE
+ // std::cerr << "PlayableAudioFile::~PlayableAudioFile - destroying - " << this << std::endl;
+#endif
+}
+
+void
+PlayableAudioFile::returnRingBuffers()
+{
+ for (int i = 0; i < m_targetChannels; ++i) {
+ if (m_ringBuffers[i]) {
+ m_ringBufferPool->returnBuffer(m_ringBuffers[i]);
+ m_ringBuffers[i] = 0;
+ }
+ }
+}
+
+bool
+PlayableAudioFile::scanTo(const RealTime &time)
+{
+#ifdef DEBUG_PLAYABLE_READ
+ std::cerr << "PlayableAudioFile::scanTo(" << time << ")" << std::endl;
+#endif
+
+ m_fileEnded = false; // until we know otherwise -- this flag is an
+ // optimisation, not a reliable record
+
+ bool ok = false;
+
+ if (m_isSmallFile) {
+
+ m_currentScanPoint = time;
+ m_smallFileScanFrame = RealTime::realTime2Frame
+ (time, m_audioFile->getSampleRate());
+#ifdef DEBUG_PLAYABLE_READ
+ std::cerr << "... maps to frame " << m_smallFileScanFrame << std::endl;
+#endif
+ ok = true;
+
+ } else {
+
+ ok = m_audioFile->scanTo(m_file, time);
+ if (ok) {
+ m_currentScanPoint = time;
+ }
+ }
+
+#ifdef DEBUG_PLAYABLE_READ
+ std::cerr << "PlayableAudioFile::scanTo(" << time << "): set m_currentScanPoint to " << m_currentScanPoint << std::endl;
+#endif
+
+ m_firstRead = true; // so we know to xfade in
+
+ return ok;
+}
+
+
+size_t
+PlayableAudioFile::getSampleFramesAvailable()
+{
+ size_t actual = 0;
+
+ if (m_isSmallFile) {
+ size_t cchannels;
+ size_t cframes;
+ (void)m_smallFileCache.getData(m_audioFile, cchannels, cframes);
+ if (cframes > m_smallFileScanFrame)
+ return cframes - m_smallFileScanFrame;
+ else
+ return 0;
+ }
+
+ for (int ch = 0; ch < m_targetChannels; ++ch) {
+ if (!m_ringBuffers[ch])
+ return 0;
+ size_t thisChannel = m_ringBuffers[ch]->getReadSpace();
+ if (ch == 0 || thisChannel < actual)
+ actual = thisChannel;
+ }
+
+#ifdef DEBUG_PLAYABLE
+ std::cerr << "PlayableAudioFile(" << (m_audioFile ? m_audioFile->getShortFilename() : "(none)") << " " << this << ")::getSampleFramesAvailable: have " << actual << std::endl;
+#endif
+
+ return actual;
+}
+
+size_t
+PlayableAudioFile::addSamples(std::vector<sample_t *> &destination,
+ size_t channels, size_t nframes, size_t offset)
+{
+#ifdef DEBUG_PLAYABLE_READ
+ std::cerr << "PlayableAudioFile::addSamples(" << nframes << "): channels " << channels << ", my target channels " << m_targetChannels << std::endl;
+#endif
+
+ if (!m_isSmallFile) {
+
+ size_t qty = 0;
+ bool done = m_fileEnded;
+
+ for (int ch = 0; ch < int(channels) && ch < m_targetChannels; ++ch) {
+ if (!m_ringBuffers[ch])
+ return 0; //!!! fatal
+ size_t here = m_ringBuffers[ch]->readAdding(destination[ch] + offset, nframes);
+ if (ch == 0 || here < qty)
+ qty = here;
+ if (done && (m_ringBuffers[ch]->getReadSpace() > 0))
+ done = false;
+ }
+
+ for (int ch = channels; ch < m_targetChannels; ++ch) {
+ m_ringBuffers[ch]->skip(nframes);
+ }
+
+ if (done) {
+#ifdef DEBUG_PLAYABLE_READ
+ std::cerr << "PlayableAudioFile::addSamples(" << nframes << "): reached end, returning buffers" << std::endl;
+#endif
+
+ returnRingBuffers();
+ }
+
+#ifdef DEBUG_PLAYABLE_READ
+ std::cerr << "PlayableAudioFile::addSamples(" << nframes << "): returning " << qty << " frames (at least " << (m_ringBuffers[0] ? m_ringBuffers[0]->getReadSpace() : 0) << " remaining)" << std::endl;
+#endif
+
+ return qty;
+
+ } else {
+
+ size_t cchannels;
+ size_t cframes;
+ float **cached = m_smallFileCache.getData(m_audioFile, cchannels, cframes);
+
+ if (!cached) {
+ std::cerr << "WARNING: PlayableAudioFile::addSamples: Failed to find small file in cache" << std::endl;
+ m_isSmallFile = false;
+ } else {
+
+ size_t scanFrame = m_smallFileScanFrame;
+
+ if (scanFrame >= cframes) {
+ m_fileEnded = true;
+ return 0;
+ }
+
+ size_t endFrame = scanFrame + nframes;
+ size_t n = nframes;
+
+ if (endFrame >= cframes) {
+ m_fileEnded = true;
+ n = cframes - scanFrame;
+ }
+
+#ifdef DEBUG_PLAYABLE_READ
+ std::cerr << "PlayableAudioFile::addSamples: it's a small file: want frames " << scanFrame << " to " << endFrame << " of " << cframes << std::endl;
+#endif
+
+ size_t xfadeIn = (m_firstRead ? m_xfadeFrames : 0);
+ size_t xfadeOut = (m_fileEnded ? m_xfadeFrames : 0);
+
+ // all this could be neater!
+
+ if (channels == 1 && cchannels == 2) { // mix
+ for (size_t i = 0; i < n; ++i) {
+ sample_t v =
+ cached[0][scanFrame + i] +
+ cached[1][scanFrame + i];
+ // if ((i + 1) < xfadeIn)
+ // v = (v * (i + 1)) / xfadeIn;
+ //if ((n - i) < xfadeOut)
+ // v = (v * (n - i)) / xfadeOut;
+ destination[0][i + offset] += v;
+ }
+ } else {
+ for (size_t ch = 0; ch < channels; ++ch) {
+ int sch = ch;
+ if (ch >= cchannels) {
+ if (channels == 2 && cchannels == 1)
+ sch = 0;
+ else
+ break;
+ } else {
+ for (size_t i = 0; i < n; ++i) {
+ sample_t v = cached[sch][scanFrame + i];
+ // if ((i + 1) < xfadeIn)
+ // v = (v * (i + 1)) / xfadeIn;
+ //if ((n - i) < xfadeOut)
+ // v = (v * (n - i)) / xfadeOut;
+ destination[ch][i + offset] += v;
+ }
+ }
+ }
+ }
+
+ m_smallFileScanFrame += nframes;
+ m_currentScanPoint = m_currentScanPoint +
+ RealTime::frame2RealTime(nframes, m_targetSampleRate);
+ return nframes;
+ }
+ }
+
+ return 0;
+}
+
+void
+PlayableAudioFile::checkSmallFileCache(size_t smallFileSize)
+{
+ if (m_smallFileCache.has(m_audioFile)) {
+
+#ifdef DEBUG_PLAYABLE
+ std::cerr << "PlayableAudioFile::checkSmallFileCache: Found file in small file cache" << std::endl;
+#endif
+
+ m_smallFileCache.incrementReference(m_audioFile);
+ m_isSmallFile = true;
+
+ } else if (m_audioFile->getSize() <= smallFileSize) {
+
+ std::ifstream file(m_audioFile->getFilename().c_str(),
+ std::ios::in | std::ios::binary);
+
+ if (!file) {
+ std::cerr << "ERROR: PlayableAudioFile::checkSmallFileCache: Failed to open audio file " << m_audioFile->getFilename() << std::endl;
+ return ;
+ }
+
+#ifdef DEBUG_PLAYABLE
+ std::cerr << "PlayableAudioFile::checkSmallFileCache: Adding file to small file cache" << std::endl;
+#endif
+
+ // We always encache files with their original number of
+ // channels (because they might be called for in any channel
+ // configuration subsequently) but with the current sample
+ // rate, not their original one.
+
+ m_audioFile->scanTo(&file, RealTime::zeroTime);
+
+ size_t reqd = m_audioFile->getSize() / m_audioFile->getBytesPerFrame();
+ unsigned char *buffer = new unsigned char[m_audioFile->getSize()];
+ size_t obtained = m_audioFile->getSampleFrames(&file, (char *)buffer, reqd);
+
+// std::cerr <<"obtained=" << obtained << std::endl;
+
+ size_t nch = getSourceChannels();
+ size_t nframes = obtained;
+ if (int(getSourceSampleRate()) != m_targetSampleRate) {
+#ifdef DEBUG_PLAYABLE
+ std::cerr << "PlayableAudioFile::checkSmallFileCache: Resampling badly from " << getSourceSampleRate() << " to " << m_targetSampleRate << std::endl;
+#endif
+ nframes = size_t(float(nframes) * float(m_targetSampleRate) /
+ float(getSourceSampleRate()));
+ }
+
+ std::vector<sample_t *> samples;
+ for (size_t ch = 0; ch < nch; ++ch) {
+ samples.push_back(new sample_t[nframes]);
+ }
+
+ if (!m_audioFile->decode(buffer,
+ obtained * m_audioFile->getBytesPerFrame(),
+ m_targetSampleRate,
+ nch,
+ nframes,
+ samples)) {
+ std::cerr << "PlayableAudioFile::checkSmallFileCache: failed to decode file" << std::endl;
+ } else {
+ sample_t **toCache = new sample_t * [nch];
+ for (size_t ch = 0; ch < nch; ++ch) {
+ toCache[ch] = samples[ch];
+ }
+ m_smallFileCache.addData(m_audioFile, nch, nframes, toCache);
+ m_isSmallFile = true;
+ }
+
+ delete[] buffer;
+
+ file.close();
+ }
+
+ if (m_isSmallFile) {
+ if (m_file) {
+ m_file->close();
+ delete m_file;
+ m_file = 0;
+ }
+ }
+}
+
+
+void
+PlayableAudioFile::fillBuffers()
+{
+#ifdef DEBUG_PLAYABLE
+ if (m_audioFile) {
+ std::cerr << "PlayableAudioFile(" << m_audioFile->getShortFilename() << ")::fillBuffers() [async] -- scanning to " << m_startIndex << std::endl;
+ } else {
+ std::cerr << "PlayableAudioFile::fillBuffers() [async] -- scanning to " << m_startIndex << std::endl;
+ }
+#endif
+
+ if (!m_isSmallFile && (!m_file || !*m_file)) {
+ m_file = new std::ifstream(m_audioFile->getFilename().c_str(),
+ std::ios::in | std::ios::binary);
+ if (!*m_file) {
+ std::cerr << "ERROR: PlayableAudioFile::fillBuffers: Failed to open audio file " << m_audioFile->getFilename() << std::endl;
+ delete m_file;
+ m_file = 0;
+ return ;
+ }
+ }
+
+ scanTo(m_startIndex);
+ updateBuffers();
+}
+
+void
+PlayableAudioFile::clearBuffers()
+{
+ returnRingBuffers();
+}
+
+bool
+PlayableAudioFile::fillBuffers(const RealTime &currentTime)
+{
+#ifdef DEBUG_PLAYABLE
+ if (!m_isSmallFile) {
+ if (m_audioFile) {
+ std::cerr << "PlayableAudioFile(" << m_audioFile->getShortFilename() << " " << this << ")::fillBuffers(" << currentTime << "):\n my start time " << m_startTime << ", start index " << m_startIndex << ", duration " << m_duration << std::endl;
+ } else {
+ std::cerr << "PlayableAudioFile::fillBuffers(" << currentTime << "): my start time " << m_startTime << ", start index " << m_startIndex << ", duration " << m_duration << std::endl;
+ }
+ }
+#endif
+
+ if (currentTime > m_startTime + m_duration) {
+
+#ifdef DEBUG_PLAYABLE
+ std::cerr << "PlayableAudioFile::fillBuffers: seeking past end, returning buffers" << std::endl;
+#endif
+
+ returnRingBuffers();
+ return true;
+ }
+
+ if (!m_isSmallFile && (!m_file || !*m_file)) {
+ m_file = new std::ifstream(m_audioFile->getFilename().c_str(),
+ std::ios::in | std::ios::binary);
+ if (!*m_file) {
+ std::cerr << "ERROR: PlayableAudioFile::fillBuffers: Failed to open audio file " << m_audioFile->getFilename() << std::endl;
+ delete m_file;
+ m_file = 0;
+ return false;
+ }
+ scanTo(m_startIndex);
+ }
+
+ RealTime scanTime = m_startIndex;
+
+ if (currentTime > m_startTime) {
+ scanTime = m_startIndex + currentTime - m_startTime;
+ }
+
+ // size_t scanFrames = RealTime::realTime2Frame
+ // (scanTime,
+ // m_isSmallFile ? m_targetSampleRate : m_audioFile->getSampleRate());
+
+ if (scanTime != m_currentScanPoint) {
+ scanTo(scanTime);
+ }
+
+ if (!m_isSmallFile) {
+ for (int i = 0; i < m_targetChannels; ++i) {
+ if (m_ringBuffers[i])
+ m_ringBuffers[i]->reset();
+ }
+ updateBuffers();
+ }
+
+ return true;
+}
+
+bool
+PlayableAudioFile::updateBuffers()
+{
+ if (m_isSmallFile)
+ return false;
+ if (!m_file)
+ return false;
+
+ if (m_fileEnded) {
+#ifdef DEBUG_PLAYABLE_READ
+ std::cerr << "PlayableAudioFile::updateBuffers: at end of file already" << std::endl;
+#endif
+
+ return false;
+ }
+
+ if (!m_ringBuffers[0]) {
+
+ if (m_targetChannels < 0) {
+ std::cerr << "WARNING: PlayableAudioFile::updateBuffers: m_targetChannels < 0, can't allocate ring buffers" << std::endl;
+ return false;
+ }
+
+ // need a buffer: can we get one?
+ if (!m_ringBufferPool->getBuffers(m_targetChannels, m_ringBuffers)) {
+ std::cerr << "WARNING: PlayableAudioFile::updateBuffers: no ring buffers available" << std::endl;
+ return false;
+ }
+ }
+
+ size_t nframes = 0;
+
+ for (int ch = 0; ch < m_targetChannels; ++ch) {
+ if (!m_ringBuffers[ch])
+ continue;
+ size_t writeSpace = m_ringBuffers[ch]->getWriteSpace();
+ if (ch == 0 || writeSpace < nframes)
+ nframes = writeSpace;
+ }
+
+ if (nframes == 0) {
+#ifdef DEBUG_PLAYABLE_READ
+ std::cerr << "PlayableAudioFile::updateBuffers: frames == 0, ignoring" << std::endl;
+#endif
+
+ return false;
+ }
+
+#ifdef DEBUG_PLAYABLE_READ
+ std::cerr << "PlayableAudioFile::updateBuffers: want " << nframes << " frames" << std::endl;
+#endif
+
+
+ RealTime block = RealTime::frame2RealTime(nframes, m_targetSampleRate);
+ if (m_currentScanPoint + block >= m_startIndex + m_duration) {
+ block = m_startIndex + m_duration - m_currentScanPoint;
+ if (block <= RealTime::zeroTime)
+ nframes = 0;
+ else
+ nframes = RealTime::realTime2Frame(block, m_targetSampleRate);
+ m_fileEnded = true;
+ }
+
+ size_t fileFrames = nframes;
+ if (m_targetSampleRate != int(getSourceSampleRate())) {
+ fileFrames = size_t(float(fileFrames) * float(getSourceSampleRate()) /
+ float(m_targetSampleRate));
+ }
+
+#ifdef DEBUG_PLAYABLE_READ
+ std::cerr << "Want " << fileFrames << " (" << block << ") from file (" << (m_duration + m_startIndex - m_currentScanPoint - block) << " to go)" << std::endl;
+#endif
+
+ //!!! need to be doing this in initialise, want to avoid allocations here
+ if ((getBytesPerFrame() * fileFrames) > m_rawFileBufferSize) {
+ delete[] m_rawFileBuffer;
+ m_rawFileBufferSize = getBytesPerFrame() * fileFrames;
+#ifdef DEBUG_PLAYABLE_READ
+
+ std::cerr << "Expanding raw file buffer to " << m_rawFileBufferSize << " chars" << std::endl;
+#endif
+
+ m_rawFileBuffer = new char[m_rawFileBufferSize];
+ }
+
+ size_t obtained =
+ m_audioFile->getSampleFrames(m_file, m_rawFileBuffer, fileFrames);
+
+ if (obtained < fileFrames || m_file->eof()) {
+ m_fileEnded = true;
+ }
+
+#ifdef DEBUG_PLAYABLE
+ std::cerr << "requested " << fileFrames << " frames from file for " << nframes << " frames, got " << obtained << " frames" << std::endl;
+#endif
+
+ if (nframes > m_workBufferSize) {
+
+ for (size_t i = 0; i < m_workBuffers.size(); ++i) {
+ delete[] m_workBuffers[i];
+ }
+
+ m_workBuffers.clear();
+ m_workBufferSize = nframes;
+#ifdef DEBUG_PLAYABLE_READ
+
+ std::cerr << "Expanding work buffer to " << m_workBufferSize << " frames" << std::endl;
+#endif
+
+ for (int i = 0; i < m_targetChannels; ++i) {
+ m_workBuffers.push_back(new sample_t[m_workBufferSize]);
+ }
+
+ } else {
+
+ while (m_targetChannels > m_workBuffers.size()) {
+ m_workBuffers.push_back(new sample_t[m_workBufferSize]);
+ }
+ }
+
+ if (m_audioFile->decode((const unsigned char *)m_rawFileBuffer,
+ obtained * getBytesPerFrame(),
+ m_targetSampleRate,
+ m_targetChannels,
+ nframes,
+ m_workBuffers,
+ false)) {
+
+ /*!!! No -- GUI and notification side of things isn't up to this yet,
+ so comment it out just in case
+
+ if (m_autoFade) {
+
+ if (m_currentScanPoint < m_startIndex + m_fadeInTime) {
+
+ size_t fadeSamples =
+ RealTime::realTime2Frame(m_fadeInTime, getTargetSampleRate());
+ size_t originSamples =
+ RealTime::realTime2Frame(m_currentScanPoint - m_startIndex,
+ getTargetSampleRate());
+
+ for (size_t i = 0; i < nframes; ++i) {
+ if (i + originSamples > fadeSamples) {
+ break;
+ }
+ float gain = float(i + originSamples) / float(fadeSamples);
+ for (int ch = 0; ch < m_targetChannels; ++ch) {
+ m_workBuffers[ch][i] *= gain;
+ }
+ }
+ }
+
+ if (m_currentScanPoint + block >
+ m_startIndex + m_duration - m_fadeOutTime) {
+
+ size_t fadeSamples =
+ RealTime::realTime2Frame(m_fadeOutTime, getTargetSampleRate());
+ size_t originSamples = // counting from end
+ RealTime::realTime2Frame
+ (m_startIndex + m_duration - m_currentScanPoint,
+ getTargetSampleRate());
+
+ for (size_t i = 0; i < nframes; ++i) {
+ float gain = 1.0;
+ if (originSamples < i) gain = 0.0;
+ else {
+ size_t fromEnd = originSamples - i;
+ if (fromEnd < fadeSamples) {
+ gain = float(fromEnd) / float(fadeSamples);
+ }
+ }
+ for (int ch = 0; ch < m_targetChannels; ++ch) {
+ m_workBuffers[ch][i] *= gain;
+ }
+ }
+ }
+ }
+ */
+
+ m_currentScanPoint = m_currentScanPoint + block;
+
+ for (int ch = 0; ch < m_targetChannels; ++ch) {
+
+ if (m_firstRead || m_fileEnded) {
+ float xfade = std::min(m_xfadeFrames, nframes);
+ if (m_firstRead) {
+ for (size_t i = 0; i < xfade; ++i) {
+ m_workBuffers[ch][i] *= float(i + 1) / xfade;
+ }
+ }
+ if (m_fileEnded) {
+ for (size_t i = 0; i < xfade; ++i) {
+ m_workBuffers[ch][nframes - i - 1] *=
+ float(i + 1) / xfade;
+ }
+ }
+ }
+
+ if (m_ringBuffers[ch]) {
+ m_ringBuffers[ch]->write(m_workBuffers[ch], nframes);
+ }
+ }
+ }
+
+ m_firstRead = false;
+
+ if (obtained < fileFrames) {
+ if (m_file) {
+ m_file->close();
+ delete m_file;
+ m_file = 0;
+ }
+ }
+
+ return true;
+}
+
+
+// How many channels in the base AudioFile?
+//
+unsigned int
+PlayableAudioFile::getSourceChannels()
+{
+ if (m_audioFile) {
+ return m_audioFile->getChannels();
+ }
+ return 0;
+}
+
+unsigned int
+PlayableAudioFile::getTargetChannels()
+{
+ return m_targetChannels;
+}
+
+unsigned int
+PlayableAudioFile::getBytesPerFrame()
+{
+ if (m_audioFile) {
+ return m_audioFile->getBytesPerFrame();
+ }
+ return 0;
+}
+
+unsigned int
+PlayableAudioFile::getSourceSampleRate()
+{
+ if (m_audioFile) {
+ return m_audioFile->getSampleRate();
+ }
+ return 0;
+}
+
+unsigned int
+PlayableAudioFile::getTargetSampleRate()
+{
+ return m_targetSampleRate;
+}
+
+
+// How many bits per sample in the base AudioFile?
+//
+unsigned int
+PlayableAudioFile::getBitsPerSample()
+{
+ if (m_audioFile) {
+ return m_audioFile->getBitsPerSample();
+ }
+ return 0;
+}
+
+
+}
+
diff --git a/src/sound/PlayableAudioFile.h b/src/sound/PlayableAudioFile.h
new file mode 100644
index 0000000..648ad4c
--- /dev/null
+++ b/src/sound/PlayableAudioFile.h
@@ -0,0 +1,219 @@
+// -*- c-basic-offset: 4 -*-
+
+/*
+ Rosegarden
+ A sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <bownie@bownie.com>
+
+ 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 _PLAYABLE_AUDIO_FILE_H_
+#define _PLAYABLE_AUDIO_FILE_H_
+
+#include "Instrument.h"
+#include "RingBuffer.h"
+#include "AudioFile.h"
+#include "AudioCache.h"
+
+#include <string>
+#include <map>
+
+namespace Rosegarden
+{
+
+class RingBufferPool;
+
+
+class PlayableAudioFile
+{
+public:
+ typedef float sample_t;
+
+ PlayableAudioFile(InstrumentId instrumentId,
+ AudioFile *audioFile,
+ const RealTime &startTime,
+ const RealTime &startIndex,
+ const RealTime &duration,
+ size_t bufferSize = 4096,
+ size_t smallFileSize = 131072,
+ int targetChannels = -1, // default same as file
+ int targetSampleRate = -1); // default same as file
+ ~PlayableAudioFile();
+
+ static void setRingBufferPoolSizes(size_t n, size_t nframes);
+
+ void setStartTime(const RealTime &time) { m_startTime = time; }
+ RealTime getStartTime() const { return m_startTime; }
+
+ void setDuration(const RealTime &time) { m_duration = time; }
+ RealTime getDuration() const { return m_duration; }
+ RealTime getEndTime() const { return m_startTime + m_duration; }
+
+ void setStartIndex(const RealTime &time) { m_startIndex = time; }
+ RealTime getStartIndex() const { return m_startIndex; }
+
+ bool isSmallFile() const { return m_isSmallFile; }
+
+ // Get audio file for interrogation
+ //
+ AudioFile* getAudioFile() const { return m_audioFile; }
+
+ // Get instrument ID - we need to be able to map back
+ // at the GUI.
+ //
+ InstrumentId getInstrument() const { return m_instrumentId; }
+
+ // Return the number of frames currently buffered. The next call
+ // to getSamples on any channel is guaranteed to return at least
+ // this many samples.
+ //
+ size_t getSampleFramesAvailable();
+
+ // Read samples from the given channel on the file and add them
+ // into the destination.
+ //
+ // If insufficient frames are available, this will leave the
+ // excess samples unchanged.
+ //
+ // Returns the actual number of samples written.
+ //
+ // If offset is non-zero, the samples will be written starting at
+ // offset frames from the start of the target block.
+ //
+ size_t addSamples(std::vector<sample_t *> &target,
+ size_t channels, size_t nframes, size_t offset = 0);
+
+ unsigned int getSourceChannels();
+ unsigned int getTargetChannels();
+ unsigned int getSourceSampleRate();
+ unsigned int getTargetSampleRate();
+
+ unsigned int getBitsPerSample();
+ unsigned int getBytesPerFrame();
+
+ // Clear out and refill the ring buffer for immediate
+ // (asynchronous) play.
+ //
+ void fillBuffers();
+
+ // Clear out and refill the ring buffer (in preparation for
+ // playback) according to the proposed play time.
+ //
+ // This call and updateBuffers are not thread-safe (for
+ // performance reasons). They should be called for all files
+ // sequentially within a single thread.
+ //
+ bool fillBuffers(const RealTime &currentTime);
+
+ void clearBuffers();
+
+ // Update the buffer during playback.
+ //
+ // This call and fillBuffers are not thread-safe (for performance
+ // reasons). They should be called for all files sequentially
+ // within a single thread.
+ //
+ bool updateBuffers();
+
+ // Has fillBuffers been called and completed yet?
+ //
+ bool isBuffered() const { return m_currentScanPoint > m_startIndex; }
+
+ // Has all the data in this file now been read into the buffers?
+ //
+ bool isFullyBuffered() const { return m_isSmallFile || m_fileEnded; }
+
+ // Stop playing this file.
+ //
+ void cancel() { m_fileEnded = true; }
+
+ // Segment id that allows us to crosscheck against playing audio
+ // segments.
+ //
+ int getRuntimeSegmentId() const { return m_runtimeSegmentId; }
+ void setRuntimeSegmentId(int id) { m_runtimeSegmentId = id; }
+
+ // Auto fading of a playable audio file
+ //
+ bool isAutoFading() const { return m_autoFade; }
+ void setAutoFade(bool value) { m_autoFade = value; }
+
+ RealTime getFadeInTime() const { return m_fadeInTime; }
+ void setFadeInTime(const RealTime &time)
+ { m_fadeInTime = time; }
+
+ RealTime getFadeOutTime() const { return m_fadeOutTime; }
+ void setFadeOutTime(const RealTime &time)
+ { m_fadeOutTime = time; }
+
+
+protected:
+ void initialise(size_t bufferSize, size_t smallFileSize);
+ void checkSmallFileCache(size_t smallFileSize);
+ bool scanTo(const RealTime &time);
+ void returnRingBuffers();
+
+ RealTime m_startTime;
+ RealTime m_startIndex;
+ RealTime m_duration;
+
+ // Performance file handle - must open non-blocking to
+ // allow other potential PlayableAudioFiles access to
+ // the same file.
+ //
+ std::ifstream *m_file;
+
+ // AudioFile handle
+ //
+ AudioFile *m_audioFile;
+
+ // Originating Instrument Id
+ //
+ InstrumentId m_instrumentId;
+
+ int m_targetChannels;
+ int m_targetSampleRate;
+
+ bool m_fileEnded;
+ bool m_firstRead;
+ static size_t m_xfadeFrames;
+ int m_runtimeSegmentId;
+
+ static AudioCache m_smallFileCache;
+ bool m_isSmallFile;
+
+ static std::vector<sample_t *> m_workBuffers;
+ static size_t m_workBufferSize;
+
+ static char *m_rawFileBuffer;
+ static size_t m_rawFileBufferSize;
+
+ RingBuffer<sample_t> **m_ringBuffers;
+ static RingBufferPool *m_ringBufferPool;
+
+ RealTime m_currentScanPoint;
+ size_t m_smallFileScanFrame;
+
+ bool m_autoFade;
+ RealTime m_fadeInTime;
+ RealTime m_fadeOutTime;
+
+private:
+ PlayableAudioFile(const PlayableAudioFile &pAF); // not provided
+};
+
+}
+
+#endif
diff --git a/src/sound/PluginFactory.cpp b/src/sound/PluginFactory.cpp
new file mode 100644
index 0000000..49c1014
--- /dev/null
+++ b/src/sound/PluginFactory.cpp
@@ -0,0 +1,120 @@
+// -*- c-basic-offset: 4 -*-
+
+/*
+ Rosegarden
+ A sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <bownie@bownie.com>
+
+ 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 "PluginFactory.h"
+#include "PluginIdentifier.h"
+
+#ifdef HAVE_LADSPA
+#include "LADSPAPluginFactory.h"
+#endif
+
+#ifdef HAVE_DSSI
+#include "DSSIPluginFactory.h"
+#endif
+
+#include <iostream>
+
+namespace Rosegarden
+{
+
+int PluginFactory::m_sampleRate = 48000;
+
+#ifdef HAVE_LADSPA
+static LADSPAPluginFactory *_ladspaInstance = 0;
+#endif
+
+#ifdef HAVE_DSSI
+static LADSPAPluginFactory *_dssiInstance = 0;
+#endif
+
+PluginFactory *
+PluginFactory::instance(QString pluginType)
+{
+ if (pluginType == "ladspa") {
+#ifdef HAVE_LADSPA
+ if (!_ladspaInstance) {
+ std::cerr << "PluginFactory::instance(" << pluginType
+ << "): creating new LADSPAPluginFactory" << std::endl;
+ _ladspaInstance = new LADSPAPluginFactory();
+ _ladspaInstance->discoverPlugins();
+ }
+ return _ladspaInstance;
+#else
+
+ return 0;
+#endif
+
+ } else if (pluginType == "dssi") {
+#ifdef HAVE_DSSI
+ if (!_dssiInstance) {
+ std::cerr << "PluginFactory::instance(" << pluginType
+ << "): creating new DSSIPluginFactory" << std::endl;
+ _dssiInstance = new DSSIPluginFactory();
+ _dssiInstance->discoverPlugins();
+ }
+ return _dssiInstance;
+#else
+
+ return 0;
+#endif
+
+ }
+ else
+ return 0;
+}
+
+PluginFactory *
+PluginFactory::instanceFor(QString identifier)
+{
+ QString type, soName, label;
+ PluginIdentifier::parseIdentifier(identifier, type, soName, label);
+ return instance(type);
+}
+
+void
+PluginFactory::enumerateAllPlugins(MappedObjectPropertyList &list)
+{
+ PluginFactory *factory;
+
+ // Plugins can change the locale, store it for reverting afterwards
+ char *loc = setlocale(LC_ALL, 0);
+
+ // Query DSSI plugins before LADSPA ones.
+ // This is to provide for the interesting possibility of plugins
+ // providing either DSSI or LADSPA versions of themselves,
+ // returning both versions if the LADSPA identifiers are queried
+ // first but only the DSSI version if the DSSI identifiers are
+ // queried first.
+
+ factory = instance("dssi");
+ if (factory)
+ factory->enumeratePlugins(list);
+
+ factory = instance("ladspa");
+ if (factory)
+ factory->enumeratePlugins(list);
+
+ setlocale(LC_ALL, loc);
+}
+
+
+}
+
diff --git a/src/sound/PluginFactory.h b/src/sound/PluginFactory.h
new file mode 100644
index 0000000..820b233
--- /dev/null
+++ b/src/sound/PluginFactory.h
@@ -0,0 +1,97 @@
+// -*- c-basic-offset: 4 -*-
+
+/*
+ Rosegarden
+ A sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <bownie@bownie.com>
+
+ 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 _PLUGIN_FACTORY_H_
+#define _PLUGIN_FACTORY_H_
+
+#include <qstring.h>
+#include <vector>
+
+#include "MappedCommon.h"
+
+namespace Rosegarden
+{
+
+class RunnablePluginInstance;
+class MappedPluginSlot;
+
+class PluginFactory
+{
+public:
+ static PluginFactory *instance(QString pluginType);
+ static PluginFactory *instanceFor(QString identifier);
+ static void enumerateAllPlugins(MappedObjectPropertyList &);
+
+ static void setSampleRate(int sampleRate) { m_sampleRate = sampleRate; }
+
+ /**
+ * Look up the plugin path and find the plugins in it. Called
+ * automatically after construction of a factory.
+ */
+ virtual void discoverPlugins() = 0;
+
+ /**
+ * Return a reference to a list of all plugin identifiers that can
+ * be created by this factory.
+ */
+ virtual const std::vector<QString> &getPluginIdentifiers() const = 0;
+
+ /**
+ * Append to the given list descriptions of all the available
+ * plugins and their ports. This is in a standard format, see
+ * the LADSPA implementation for details.
+ */
+ virtual void enumeratePlugins(MappedObjectPropertyList &list) = 0;
+
+ /**
+ * Populate the given plugin slot with information about its
+ * plugin. This is called from the plugin slot's set method
+ * when it's been asked to set its plugin identifier. This
+ * method should also destroy and recreate the plugin slot's
+ * port child objects.
+ */
+ virtual void populatePluginSlot(QString identifier,
+ MappedPluginSlot &slot) = 0;
+
+ /**
+ * Instantiate a plugin.
+ */
+ virtual RunnablePluginInstance *instantiatePlugin(QString identifier,
+ int instrumentId,
+ int position,
+ unsigned int sampleRate,
+ unsigned int blockSize,
+ unsigned int channels) = 0;
+
+protected:
+ PluginFactory() { }
+
+ // for call by RunnablePluginInstance dtor
+ virtual void releasePlugin(RunnablePluginInstance *, QString identifier) = 0;
+ friend class RunnablePluginInstance;
+
+ static int m_sampleRate;
+};
+
+
+}
+
+#endif
diff --git a/src/sound/PluginIdentifier.cpp b/src/sound/PluginIdentifier.cpp
new file mode 100644
index 0000000..25b3317
--- /dev/null
+++ b/src/sound/PluginIdentifier.cpp
@@ -0,0 +1,72 @@
+// -*- c-basic-offset: 4 -*-
+
+/*
+ Rosegarden
+ A sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <bownie@bownie.com>
+
+ 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 "PluginIdentifier.h"
+#include <iostream>
+
+namespace Rosegarden
+{
+
+QString
+PluginIdentifier::createIdentifier(QString type,
+ QString soName,
+ QString label)
+{
+ QString identifier = type + ":" + soName + ":" + label;
+ return identifier;
+}
+
+void
+PluginIdentifier::parseIdentifier(QString identifier,
+ QString &type,
+ QString &soName,
+ QString &label)
+{
+ type = identifier.section(':', 0, 0);
+ soName = identifier.section(':', 1, 1);
+ label = identifier.section(':', 2);
+}
+
+bool
+PluginIdentifier::areIdentifiersSimilar(QString id1, QString id2)
+{
+ QString type1, type2, soName1, soName2, label1, label2;
+
+ parseIdentifier(id1, type1, soName1, label1);
+ parseIdentifier(id2, type2, soName2, label2);
+
+ if (type1 != type2 || label1 != label2)
+ return false;
+
+ bool similar = (soName1.section('/', -1).section('.', 0, 0) ==
+ soName2.section('/', -1).section('.', 0, 0));
+
+ return similar;
+}
+
+// The prefix of this key is also used as a literal in base/AudioPluginInstance.C.
+// If you change one, change the other.
+// Better still, don't change one.
+QString
+PluginIdentifier::RESERVED_PROJECT_DIRECTORY_KEY = "__ROSEGARDEN__:__RESERVED__:ProjectDirectoryKey";
+
+}
+
diff --git a/src/sound/PluginIdentifier.h b/src/sound/PluginIdentifier.h
new file mode 100644
index 0000000..e8519ad
--- /dev/null
+++ b/src/sound/PluginIdentifier.h
@@ -0,0 +1,50 @@
+// -*- c-basic-offset: 4 -*-
+
+/*
+ Rosegarden
+ A sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <bownie@bownie.com>
+
+ 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 _PLUGIN_IDENTIFIER_H_
+#define _PLUGIN_IDENTIFIER_H_
+
+#include <qstring.h>
+
+
+// A plugin identifier is simply a string; this class provides methods
+// to parse it into its constituent bits (plugin type, DLL path and label).
+
+namespace Rosegarden {
+
+class PluginIdentifier {
+
+public:
+
+ static QString createIdentifier(QString type, QString soName, QString label);
+
+ static void parseIdentifier(QString identifier,
+ QString &type, QString &soName, QString &label);
+
+ static bool areIdentifiersSimilar(QString id1, QString id2);
+
+ // Not strictly related to identifiers
+ static QString RESERVED_PROJECT_DIRECTORY_KEY;
+};
+
+}
+
+#endif
diff --git a/src/sound/RIFFAudioFile.cpp b/src/sound/RIFFAudioFile.cpp
new file mode 100644
index 0000000..c34435f
--- /dev/null
+++ b/src/sound/RIFFAudioFile.cpp
@@ -0,0 +1,686 @@
+// -*- c-basic-offset: 4 -*-
+
+/*
+ Rosegarden
+ A sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <bownie@bownie.com>
+
+ 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 "RIFFAudioFile.h"
+#include "RealTime.h"
+#include "Profiler.h"
+
+using std::cout;
+using std::cerr;
+using std::endl;
+
+//#define DEBUG_RIFF
+
+
+namespace Rosegarden
+{
+
+RIFFAudioFile::RIFFAudioFile(unsigned int id,
+ const std::string &name,
+ const std::string &fileName):
+ AudioFile(id, name, fileName),
+ m_subFormat(PCM),
+ m_bytesPerSecond(0),
+ m_bytesPerFrame(0)
+{}
+
+RIFFAudioFile::RIFFAudioFile(const std::string &fileName,
+ unsigned int channels = 1,
+ unsigned int sampleRate = 48000,
+ unsigned int bytesPerSecond = 6000,
+ unsigned int bytesPerFrame = 2,
+ unsigned int bitsPerSample = 16):
+ AudioFile(0, "", fileName)
+{
+ m_bitsPerSample = bitsPerSample;
+ m_sampleRate = sampleRate;
+ m_bytesPerSecond = bytesPerSecond;
+ m_bytesPerFrame = bytesPerFrame;
+ m_channels = channels;
+
+ if (bitsPerSample == 16)
+ m_subFormat = PCM;
+ else if (bitsPerSample == 32)
+ m_subFormat = FLOAT;
+ else
+ throw(BadSoundFileException(m_fileName, "Rosegarden currently only supports 16 or 32-bit PCM or IEEE floating-point RIFF files for writing"));
+
+}
+
+RIFFAudioFile::~RIFFAudioFile()
+{}
+
+
+// Show some stats on this file
+//
+void
+RIFFAudioFile::printStats()
+{
+ cout << "filename : " << m_fileName << endl
+ << "channels : " << m_channels << endl
+ << "sample rate : " << m_sampleRate << endl
+ << "bytes per second : " << m_bytesPerSecond << endl
+ << "bits per sample : " << m_bitsPerSample << endl
+ << "bytes per frame : " << m_bytesPerFrame << endl
+ << "file length : " << m_fileSize << " bytes" << endl
+ << endl;
+}
+
+bool
+RIFFAudioFile::appendSamples(const std::string &buffer)
+{
+ /*
+ if (m_outFile == 0 || m_type != WAV)
+ return false;
+ */
+
+ // write out
+ putBytes(m_outFile, buffer);
+
+ return true;
+}
+
+bool
+RIFFAudioFile::appendSamples(const char *buf, unsigned int frames)
+{
+ putBytes(m_outFile, buf, frames * m_bytesPerFrame);
+ return true;
+}
+
+// scan on from a descriptor position
+bool
+RIFFAudioFile::scanForward(std::ifstream *file, const RealTime &time)
+{
+ // sanity
+ if (file == 0)
+ return false;
+
+ unsigned int totalSamples = m_sampleRate * time.sec +
+ ( ( m_sampleRate * time.usec() ) / 1000000 );
+ unsigned int totalBytes = totalSamples * m_bytesPerFrame;
+
+ m_loseBuffer = true;
+
+ // do the seek
+ file->seekg(totalBytes, std::ios::cur);
+
+ if (file->eof())
+ return false;
+
+ return true;
+}
+
+bool
+RIFFAudioFile::scanForward(const RealTime &time)
+{
+ if (*m_inFile)
+ return scanForward(m_inFile, time);
+ else
+ return false;
+}
+
+bool
+RIFFAudioFile::scanTo(const RealTime &time)
+{
+ if (*m_inFile)
+ return scanTo(m_inFile, time);
+ else
+ return false;
+
+}
+
+bool
+RIFFAudioFile::scanTo(std::ifstream *file, const RealTime &time)
+{
+ // sanity
+ if (file == 0)
+ return false;
+
+ // whatever we do here we invalidate the read buffer
+ //
+ m_loseBuffer = true;
+
+ file->clear();
+
+ // seek past header - don't hardcode this - use the file format
+ // spec to get header length and then scoot to that.
+ //
+ file->seekg(16, std::ios::beg);
+
+ unsigned int lengthOfFormat = 0;
+
+ try {
+ lengthOfFormat = getIntegerFromLittleEndian(getBytes(file, 4));
+ file->seekg(lengthOfFormat, std::ios::cur);
+
+ // check we've got data chunk start
+ std::string chunkName;
+ int chunkLength = 0;
+
+ while ((chunkName = getBytes(file, 4)) != "data") {
+ if (file->eof()) {
+ std::cerr << "RIFFAudioFile::scanTo(): failed to find data "
+ << std::endl;
+ return false;
+ }
+//#ifdef DEBUG_RIFF
+ std::cerr << "RIFFAudioFile::scanTo(): skipping chunk: "
+ << chunkName << std::endl;
+//#endif
+ chunkLength = getIntegerFromLittleEndian(getBytes(file, 4));
+ if (chunkLength < 0) {
+ std::cerr << "RIFFAudioFile::scanTo(): negative chunk length "
+ << chunkLength << " for chunk " << chunkName << std::endl;
+ return false;
+ }
+ file->seekg(chunkLength, std::ios::cur);
+ }
+
+ // get the length of the data chunk, and scan past it as a side-effect
+ chunkLength = getIntegerFromLittleEndian(getBytes(file, 4));
+#ifdef DEBUG_RIFF
+
+ std::cout << "RIFFAudioFile::scanTo() - data chunk size = "
+ << chunkLength << std::endl;
+#endif
+
+ } catch (BadSoundFileException s) {
+#ifdef DEBUG_RIFF
+ std::cerr << "RIFFAudioFile::scanTo - EXCEPTION - \""
+ << s.getMessage() << "\"" << std::endl;
+#endif
+
+ return false;
+ }
+
+ // Ok, we're past all the header information in the data chunk.
+ // Now, how much do we scan forward?
+ //
+ size_t totalFrames = RealTime::realTime2Frame(time, m_sampleRate);
+
+ unsigned int totalBytes = totalFrames * m_bytesPerFrame;
+
+ // When using seekg we have to keep an eye on the boundaries ourselves
+ //
+ if (totalBytes > m_fileSize - (lengthOfFormat + 16 + 8)) {
+#ifdef DEBUG_RIFF
+ std::cerr << "RIFFAudioFile::scanTo() - attempting to move past end of "
+ << "data block" << std::endl;
+#endif
+
+ return false;
+ }
+
+#ifdef DEBUG_RIFF
+ std::cout << "RIFFAudioFile::scanTo - seeking to " << time
+ << " (" << totalBytes << " bytes from current " << file->tellg()
+ << ")" << std::endl;
+#endif
+
+ file->seekg(totalBytes, std::ios::cur);
+
+ return true;
+}
+
+// Get a certain number of sample frames - a frame is a set
+// of samples (all channels) for a given sample quanta.
+//
+// For example, getting one frame of 16-bit stereo will return
+// four bytes of data (two per channel).
+//
+//
+std::string
+RIFFAudioFile::getSampleFrames(std::ifstream *file, unsigned int frames)
+{
+ // sanity
+ if (file == 0)
+ return std::string("");
+
+ // Bytes per sample already takes into account the number
+ // of channels we're using
+ //
+ long totalBytes = frames * m_bytesPerFrame;
+
+ try {
+ return getBytes(file, totalBytes);
+ } catch (BadSoundFileException s) {
+ return "";
+ }
+}
+
+unsigned int
+RIFFAudioFile::getSampleFrames(std::ifstream *file, char *buf,
+ unsigned int frames)
+{
+ if (file == 0)
+ return 0;
+ try {
+ return getBytes(file, buf, frames * m_bytesPerFrame) / m_bytesPerFrame;
+ } catch (BadSoundFileException s) {
+ return 0;
+ }
+}
+
+std::string
+RIFFAudioFile::getSampleFrames(unsigned int frames)
+{
+ if (*m_inFile) {
+ return getSampleFrames(m_inFile, frames);
+ } else {
+ return std::string("");
+ }
+}
+
+// Return a slice of frames over a time period
+//
+std::string
+RIFFAudioFile::getSampleFrameSlice(std::ifstream *file, const RealTime &time)
+{
+ // sanity
+ if (file == 0)
+ return std::string("");
+
+ long totalFrames = RealTime::realTime2Frame(time, m_sampleRate);
+ long totalBytes = totalFrames * m_bytesPerFrame;
+
+ try {
+ return getBytes(file, totalBytes);
+ } catch (BadSoundFileException s) {
+ return "";
+ }
+}
+
+std::string
+RIFFAudioFile::getSampleFrameSlice(const RealTime &time)
+{
+ if (*m_inFile) {
+ return getSampleFrameSlice(m_inFile, time);
+ } else {
+ return std::string("");
+ }
+}
+
+RealTime
+RIFFAudioFile::getLength()
+{
+ // Fixed header size = 44 but prove by getting it from the file too
+ //
+ unsigned int headerLength = 44;
+
+ if (m_inFile) {
+ m_inFile->seekg(16, std::ios::beg);
+ headerLength = getIntegerFromLittleEndian(getBytes(m_inFile, 4));
+ m_inFile->seekg(headerLength, std::ios::cur);
+ headerLength += (16 + 8);
+ }
+
+ if (!m_bytesPerFrame || !m_sampleRate) return RealTime::zeroTime;
+
+ double frames = (m_fileSize - headerLength) / m_bytesPerFrame;
+ double seconds = frames / ((double)m_sampleRate);
+
+ int secs = int(seconds);
+ int nsecs = int((seconds - secs) * 1000000000.0);
+
+ return RealTime(secs, nsecs);
+}
+
+
+// The RIFF file format chunk defines our internal meta data.
+//
+// Courtesy of:
+// http://www.technology.niagarac.on.ca/courses/comp630/WavFileFormat.html
+//
+// 'The WAV file itself consists of three "chunks" of information:
+// The RIFF chunk which identifies the file as a WAV file, The FORMAT
+// chunk which identifies parameters such as sample rate and the DATA
+// chunk which contains the actual data (samples).'
+//
+//
+void
+RIFFAudioFile::readFormatChunk()
+{
+ if (m_inFile == 0)
+ return ;
+
+ m_loseBuffer = true;
+
+ // seek to beginning
+ m_inFile->seekg(0, std::ios::beg);
+
+ // get the header string
+ //
+ std::string hS = getBytes(36);
+
+ // Look for the RIFF identifier and bomb out if we don't find it
+ //
+#if (__GNUC__ < 3)
+
+ if (hS.compare(AUDIO_RIFF_ID, 0, 4) != 0)
+#else
+
+ if (hS.compare(0, 4, AUDIO_RIFF_ID) != 0)
+#endif
+
+ {
+#ifdef DEBUG_RIFF
+ std::cerr << "RIFFAudioFile::readFormatChunk - "
+ << "can't find RIFF identifier\n";
+#endif
+
+ throw(BadSoundFileException(m_fileName, "RIFFAudioFile::readFormatChunk - can't find RIFF identifier"));
+ }
+
+ // Look for the WAV identifier
+ //
+#if (__GNUC__ < 3)
+ if (hS.compare(AUDIO_WAVE_ID, 8, 4) != 0)
+#else
+
+ if (hS.compare(8, 4, AUDIO_WAVE_ID) != 0)
+#endif
+
+ {
+#ifdef DEBUG_RIFF
+ std::cerr << "Can't find WAV identifier\n";
+#endif
+
+ throw(BadSoundFileException(m_fileName, "Can't find WAV identifier"));
+ }
+
+ // Look for the FORMAT identifier - note that this doesn't actually
+ // have to be in the first chunk we come across, but for the moment
+ // this is the only place we check for it because I'm lazy.
+ //
+ //
+#if (__GNUC__ < 3)
+ if (hS.compare(AUDIO_FORMAT_ID, 12, 4) != 0)
+#else
+
+ if (hS.compare(12, 4, AUDIO_FORMAT_ID) != 0)
+#endif
+
+ {
+#ifdef DEBUG_RIFF
+ std::cerr << "Can't find FORMAT identifier\n";
+#endif
+
+ throw(BadSoundFileException(m_fileName, "Can't find FORMAT identifier"));
+ }
+
+ // Little endian conversion of length bytes into file length
+ // (add on eight for RIFF id and length field and compare to
+ // real file size).
+ //
+ unsigned int length = getIntegerFromLittleEndian(hS.substr(4, 4)) + 8;
+
+ if (length != m_fileSize) {
+ std::cerr << "WARNING: RIFFAudioFile: incorrect length ("
+ << length << ", file size is " << m_fileSize << "), ignoring"
+ << std::endl;
+ length = m_fileSize;
+ }
+
+ // Check the format length
+ //
+ unsigned int lengthOfFormat = getIntegerFromLittleEndian(hS.substr(16, 4));
+
+ // Make sure we step to the end of the format chunk ignoring the
+ // tail if it exists
+ //
+ if (lengthOfFormat > 0x10) {
+#ifdef DEBUG_RIFF
+ std::cerr << "RIFFAudioFile::readFormatChunk - "
+ << "extended Format Chunk (" << lengthOfFormat << ")"
+ << std::endl;
+#endif
+
+ // ignore any overlapping bytes
+ m_inFile->seekg(lengthOfFormat - 0x10, std::ios::cur);
+ } else if (lengthOfFormat < 0x10) {
+#ifdef DEBUG_RIFF
+ std::cerr << "RIFFAudioFile::readFormatChunk - "
+ << "truncated Format Chunk (" << lengthOfFormat << ")"
+ << std::endl;
+#endif
+
+ m_inFile->seekg(lengthOfFormat - 0x10, std::ios::cur);
+ //throw(BadSoundFileException(m_fileName, "Format chunk too short"));
+ }
+
+
+ // Check sub format - we support PCM or IEEE floating point.
+ //
+ unsigned int subFormat = getIntegerFromLittleEndian(hS.substr(20, 2));
+
+ if (subFormat == 0x01) {
+ m_subFormat = PCM;
+ } else if (subFormat == 0x03) {
+ m_subFormat = FLOAT;
+ } else {
+ throw(BadSoundFileException(m_fileName, "Rosegarden currently only supports PCM or IEEE floating-point RIFF files"));
+ }
+
+ // We seem to have a good looking .WAV file - extract the
+ // sample information and populate this locally
+ //
+ unsigned int channelNumbers = getIntegerFromLittleEndian(hS.substr(22, 2));
+
+ switch (channelNumbers) {
+ case 0x01:
+ case 0x02:
+ m_channels = channelNumbers;
+ break;
+
+ default: {
+ throw(BadSoundFileException(m_fileName, "Unsupported number of channels"));
+ }
+ break;
+ }
+
+ // Now the rest of the information
+ //
+ m_sampleRate = getIntegerFromLittleEndian(hS.substr(24, 4));
+ m_bytesPerSecond = getIntegerFromLittleEndian(hS.substr(28, 4));
+ m_bytesPerFrame = getIntegerFromLittleEndian(hS.substr(32, 2));
+ m_bitsPerSample = getIntegerFromLittleEndian(hS.substr(34, 2));
+
+ if (m_subFormat == PCM) {
+ if (m_bitsPerSample != 8 && m_bitsPerSample != 16 && m_bitsPerSample != 24) {
+ throw BadSoundFileException("Rosegarden currently only supports 8-, 16- or 24-bit PCM in RIFF files");
+ }
+ } else if (m_subFormat == FLOAT) {
+ if (m_bitsPerSample != 32) {
+ throw BadSoundFileException("Rosegarden currently only supports 32-bit floating-point in RIFF files");
+ }
+ }
+
+ // printStats();
+
+}
+
+// Write out the format chunk from our internal data
+//
+void
+RIFFAudioFile::writeFormatChunk()
+{
+ if (m_outFile == 0 || m_type != WAV)
+ return ;
+
+ std::string outString;
+
+ // RIFF type is all we support for the moment
+ outString += AUDIO_RIFF_ID;
+
+ // Now write the total length of the file minus these first 8 bytes.
+ // We won't know this until we've finished recording the file.
+ //
+ outString += "0000";
+
+ // WAV file is all we support
+ //
+ outString += AUDIO_WAVE_ID;
+
+ // Begin the format chunk
+ outString += AUDIO_FORMAT_ID;
+
+ // length
+ //cout << "LENGTH = " << getLittleEndianFromInteger(0x10, 4) << endl;
+ outString += getLittleEndianFromInteger(0x10, 4);
+
+ // 1 for PCM, 3 for float
+ if (m_subFormat == PCM) {
+ outString += getLittleEndianFromInteger(0x01, 2);
+ } else {
+ outString += getLittleEndianFromInteger(0x03, 2);
+ }
+
+ // channel
+ outString += getLittleEndianFromInteger(m_channels, 2);
+
+ // sample rate
+ outString += getLittleEndianFromInteger(m_sampleRate, 4);
+
+ // bytes per second
+ outString += getLittleEndianFromInteger(m_bytesPerSecond, 4);
+
+ // bytes per sample
+ outString += getLittleEndianFromInteger(m_bytesPerFrame, 2);
+
+ // bits per sample
+ outString += getLittleEndianFromInteger(m_bitsPerSample, 2);
+
+ // Now mark the beginning of the "data" chunk and leave the file
+ // open for writing.
+ outString += "data";
+
+ // length of data to follow - again needs to be written after
+ // we've completed the file.
+ //
+ outString += "0000";
+
+ // write out
+ //
+ putBytes(m_outFile, outString);
+}
+
+
+AudioFileType
+RIFFAudioFile::identifySubType(const std::string &filename)
+{
+ std::ifstream *testFile =
+ new std::ifstream(filename.c_str(), std::ios::in | std::ios::binary);
+
+ if (!(*testFile))
+ return UNKNOWN;
+
+ std::string hS;
+ unsigned int numberOfBytes = 36;
+ char *bytes = new char[numberOfBytes];
+
+ testFile->read(bytes, numberOfBytes);
+ for (unsigned int i = 0; i < numberOfBytes; i++)
+ hS += (unsigned char)bytes[i];
+
+ AudioFileType type = UNKNOWN;
+
+ // Test for BWF first because it's an extension of a plain WAV
+ //
+#if (__GNUC__ < 3)
+
+ if (hS.compare(AUDIO_RIFF_ID, 0, 4) == 0 &&
+ hS.compare(AUDIO_WAVE_ID, 8, 4) == 0 &&
+ hS.compare(AUDIO_BWF_ID, 12, 4) == 0)
+#else
+
+ if (hS.compare(0, 4, AUDIO_RIFF_ID) == 0 &&
+ hS.compare(8, 4, AUDIO_WAVE_ID) == 0 &&
+ hS.compare(12, 4, AUDIO_BWF_ID) == 0)
+#endif
+
+ {
+ type = BWF;
+ }
+ // Now for a WAV
+#if (__GNUC__ < 3)
+ else if (hS.compare(AUDIO_RIFF_ID, 0, 4) == 0 &&
+ hS.compare(AUDIO_WAVE_ID, 8, 4) == 0)
+#else
+
+ else if (hS.compare(0, 4, AUDIO_RIFF_ID) == 0 &&
+ hS.compare(8, 4, AUDIO_WAVE_ID) == 0)
+#endif
+
+ {
+ type = WAV;
+ } else
+ type = UNKNOWN;
+
+ testFile->close();
+ delete [] bytes;
+
+ return type;
+}
+
+float
+RIFFAudioFile::convertBytesToSample(const unsigned char *ubuf)
+{
+ switch (getBitsPerSample()) {
+
+ case 8: {
+ // WAV stores 8-bit samples unsigned, other sizes signed.
+ return (float)(ubuf[0] - 128.0) / 128.0;
+ }
+
+ case 16: {
+ // Two's complement little-endian 16-bit integer.
+ // We convert endianness (if necessary) but assume 16-bit short.
+ unsigned char b2 = ubuf[0];
+ unsigned char b1 = ubuf[1];
+ unsigned int bits = (b1 << 8) + b2;
+ return (float)(short(bits)) / 32767.0;
+ }
+
+ case 24: {
+ // Two's complement little-endian 24-bit integer.
+ // Again, convert endianness but assume 32-bit int.
+ unsigned char b3 = ubuf[0];
+ unsigned char b2 = ubuf[1];
+ unsigned char b1 = ubuf[2];
+ // Rotate 8 bits too far in order to get the sign bit
+ // in the right place; this gives us a 32-bit value,
+ // hence the larger float divisor
+ unsigned int bits = (b1 << 24) + (b2 << 16) + (b3 << 8);
+ return (float)(int(bits)) / 2147483647.0;
+ }
+
+ case 32: {
+ // IEEE floating point
+ return *(float *)ubuf;
+ }
+
+ default:
+ return 0.0f;
+ }
+}
+
+}
+
diff --git a/src/sound/RIFFAudioFile.h b/src/sound/RIFFAudioFile.h
new file mode 100644
index 0000000..a846306
--- /dev/null
+++ b/src/sound/RIFFAudioFile.h
@@ -0,0 +1,168 @@
+// -*- c-basic-offset: 4 -*-
+
+/*
+ Rosegarden
+ A sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <bownie@bownie.com>
+
+ 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.
+*/
+
+
+// Resource Interchange File Formt - a chunk based audio
+// file format. Type of chunk varies with specialisation
+// of this class - WAV files are a specialisation with just
+// a format chunk, BWF has more chunks.
+//
+//
+
+#ifndef _RIFFAUDIOFILE_H_
+#define _RIFFAUDIOFILE_H_
+
+#include <string>
+#include <vector>
+
+#include "AudioFile.h"
+#include "RealTime.h"
+
+namespace Rosegarden
+{
+
+class RIFFAudioFile : public AudioFile
+{
+public:
+ RIFFAudioFile(unsigned int id,
+ const std::string &name,
+ const std::string &fileName);
+
+ RIFFAudioFile(const std::string &fileName,
+ unsigned int channels,
+ unsigned int sampleRate,
+ unsigned int bytesPerSecond,
+ unsigned int bytesPerFrame,
+ unsigned int bitsPerSample);
+
+ ~RIFFAudioFile();
+
+ typedef enum {
+ PCM,
+ FLOAT
+ } SubFormat;
+
+ // Our main control methods - again keeping abstract at this level
+ //
+ //virtual bool open() = 0;
+ //virtual bool write() = 0;
+ //virtual void close() = 0;
+
+ // Show the information we have on this file
+ //
+ virtual void printStats();
+
+ // Slightly dodgy code here - we keep these functions here
+ // because I don't want to duplicate them in PlayableRIFFAudioFile
+ // and also don't want that class to inherit this one.
+ //
+ // Of course the file handle we use in might be pointing to
+ // any file - for the most part we just assume it's an audio
+ // file.
+ //
+ //
+ // Move file pointer to relative time in data chunk -
+ // shouldn't be less than zero. Returns true if the
+ // scan time was valid and successful.
+ //
+ virtual bool scanTo(const RealTime &time);
+ virtual bool scanTo(std::ifstream *file, const RealTime &time);
+
+ // Scan forward in a file by a certain amount of time
+ //
+ virtual bool scanForward(const RealTime &time);
+ virtual bool scanForward(std::ifstream *file, const RealTime &time);
+
+ // Return a number of samples - caller will have to
+ // de-interleave n-channel samples themselves.
+ //
+ virtual std::string getSampleFrames(std::ifstream *file,
+ unsigned int frames);
+ virtual unsigned int getSampleFrames(std::ifstream *file,
+ char *buf,
+ unsigned int frames);
+ virtual std::string getSampleFrames(unsigned int frames);
+
+ // Return a number of (possibly) interleaved samples
+ // over a time slice from current file pointer position.
+ //
+ virtual std::string getSampleFrameSlice(std::ifstream *file,
+ const RealTime &time);
+ virtual std::string getSampleFrameSlice(const RealTime &time);
+
+ // Append a string of samples to an already open (for writing)
+ // audio file.
+ //
+ virtual bool appendSamples(const std::string &buffer);
+ virtual bool appendSamples(const char *buf, unsigned int frames);
+
+ // Get the length of the sample in Seconds/Microseconds
+ //
+ virtual RealTime getLength();
+
+ // Accessors
+ //
+ virtual unsigned int getBytesPerFrame() { return m_bytesPerFrame; }
+ unsigned int getBytesPerSecond() { return m_bytesPerSecond; }
+
+ // Allow easy identification of wav file type
+ //
+ static AudioFileType identifySubType(const std::string &filename);
+
+ // Convert a single sample from byte format, given the right
+ // number of bytes for the sample width
+ float convertBytesToSample(const unsigned char *bytes);
+
+ // Decode and de-interleave the given samples that were retrieved
+ // from this file or another with the same format as it. Place
+ // the results in the given float buffer. Return true for
+ // success. This function does crappy resampling if necessary.
+ //
+ virtual bool decode(const unsigned char *sourceData,
+ size_t sourceBytes,
+ size_t targetSampleRate,
+ size_t targetChannels,
+ size_t targetFrames,
+ std::vector<float *> &targetData,
+ bool addToResultBuffers = false) = 0;
+
+protected:
+ //virtual void parseHeader(const std::string &header);
+ //virtual void parseBody();
+
+ // Find and read in the format chunk of a RIFF file - without
+ // this chunk we don't actually have a RIFF file.
+ //
+ void readFormatChunk();
+
+ // Write out the Format chunk from the internal data we have
+ //
+ void writeFormatChunk();
+
+ SubFormat m_subFormat;
+ unsigned int m_bytesPerSecond;
+ unsigned int m_bytesPerFrame;
+};
+
+}
+
+
+#endif // _RIFFAUDIOFILE_H_
diff --git a/src/sound/RecordableAudioFile.cpp b/src/sound/RecordableAudioFile.cpp
new file mode 100644
index 0000000..09420dd
--- /dev/null
+++ b/src/sound/RecordableAudioFile.cpp
@@ -0,0 +1,164 @@
+// -*- c-basic-offset: 4 -*-
+
+/*
+ Rosegarden
+ A sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <bownie@bownie.com>
+
+ 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 "RecordableAudioFile.h"
+
+#include <cstdlib>
+
+//#define DEBUG_RECORDABLE 1
+
+namespace Rosegarden
+{
+
+RecordableAudioFile::RecordableAudioFile(AudioFile *audioFile,
+ size_t bufferSize) :
+ m_audioFile(audioFile),
+ m_status(IDLE)
+{
+ for (unsigned int ch = 0; ch < audioFile->getChannels(); ++ch) {
+
+ m_ringBuffers.push_back(new RingBuffer<sample_t>(bufferSize));
+
+ if (!m_ringBuffers[ch]->mlock()) {
+ std::cerr << "WARNING: RecordableAudioFile::initialise: couldn't lock buffer into real memory, performance may be impaired" << std::endl;
+ }
+ }
+}
+
+RecordableAudioFile::~RecordableAudioFile()
+{
+ write();
+ m_audioFile->close();
+ delete m_audioFile;
+
+ for (size_t i = 0; i < m_ringBuffers.size(); ++i) {
+ delete m_ringBuffers[i];
+ }
+}
+
+size_t
+RecordableAudioFile::buffer(const sample_t *data, int channel, size_t frames)
+{
+ if (channel >= int(m_ringBuffers.size())) {
+ std::cerr << "RecordableAudioFile::buffer: No such channel as "
+ << channel << std::endl;
+ return 0;
+ }
+
+ size_t available = m_ringBuffers[channel]->getWriteSpace();
+
+ if (frames > available) {
+ std::cerr << "RecordableAudioFile::buffer: buffer maxed out!" << std::endl;
+ frames = available;
+ }
+
+#ifdef DEBUG_RECORDABLE
+ std::cerr << "RecordableAudioFile::buffer: buffering " << frames << " frames on channel " << channel << std::endl;
+#endif
+
+ m_ringBuffers[channel]->write(data, frames);
+ return frames;
+}
+
+void
+RecordableAudioFile::write()
+{
+ // Use a static buffer -- this obviously requires that write() is
+ // only called from a single thread
+ static size_t bufferSize = 0;
+ static sample_t *buffer = 0;
+ static char *encodeBuffer = 0;
+
+ unsigned int bits = m_audioFile->getBitsPerSample();
+
+ if (bits != 16 && bits != 32) {
+ std::cerr << "ERROR: RecordableAudioFile::write: file has " << bits
+ << " bits per sample; only 16 or 32 are supported" << std::endl;
+ return ;
+ }
+
+ unsigned int channels = m_audioFile->getChannels();
+ unsigned char b1, b2;
+
+ // We need the same amount of available data on every channel
+ size_t s = 0;
+ for (unsigned int ch = 0; ch < channels; ++ch) {
+ size_t available = m_ringBuffers[ch]->getReadSpace();
+#ifdef DEBUG_RECORDABLE
+
+ std::cerr << "RecordableAudioFile::write: " << available << " frames available to write on channel " << ch << std::endl;
+#endif
+
+ if (ch == 0 || available < s)
+ s = available;
+ }
+ if (s == 0)
+ return ;
+
+ size_t bufferReqd = channels * s;
+ if (bufferReqd > bufferSize) {
+ if (buffer) {
+ buffer = (sample_t *)realloc(buffer, bufferReqd * sizeof(sample_t));
+ encodeBuffer = (char *)realloc(encodeBuffer, bufferReqd * 4);
+ } else {
+ buffer = (sample_t *) malloc(bufferReqd * sizeof(sample_t));
+ encodeBuffer = (char *)malloc(bufferReqd * 4);
+ }
+ bufferSize = bufferReqd;
+ }
+
+ for (unsigned int ch = 0; ch < channels; ++ch) {
+ m_ringBuffers[ch]->read(buffer + ch * s, s);
+ }
+
+ // interleave and convert
+
+ if (bits == 16) {
+ size_t index = 0;
+ for (size_t i = 0; i < s; ++i) {
+ for (unsigned int ch = 0; ch < channels; ++ch) {
+ float sample = buffer[i + ch * s];
+ b2 = (unsigned char)((long)(sample * 32767.0) & 0xff);
+ b1 = (unsigned char)((long)(sample * 32767.0) >> 8);
+ encodeBuffer[index++] = b2;
+ encodeBuffer[index++] = b1;
+ }
+ }
+ } else {
+ char *encodePointer = encodeBuffer;
+ for (size_t i = 0; i < s; ++i) {
+ for (unsigned int ch = 0; ch < channels; ++ch) {
+ float sample = buffer[i + ch * s];
+ *(float *)encodePointer = sample;
+ encodePointer += sizeof(float);
+ }
+ }
+ }
+
+#ifdef DEBUG_RECORDABLE
+ std::cerr << "RecordableAudioFile::write: writing " << s << " frames at " << channels << " channels and " << bits << " bits to file" << std::endl;
+#endif
+
+ m_audioFile->appendSamples(encodeBuffer, s);
+}
+
+}
+
diff --git a/src/sound/RecordableAudioFile.h b/src/sound/RecordableAudioFile.h
new file mode 100644
index 0000000..06df6f0
--- /dev/null
+++ b/src/sound/RecordableAudioFile.h
@@ -0,0 +1,68 @@
+// -*- c-basic-offset: 4 -*-
+
+/*
+ Rosegarden
+ A sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <bownie@bownie.com>
+
+ 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 _RECORDABLE_AUDIO_FILE_H_
+#define _RECORDABLE_AUDIO_FILE_H_
+
+#include "RingBuffer.h"
+#include "AudioFile.h"
+
+#include <vector>
+
+namespace Rosegarden
+{
+
+// A wrapper class for writing out a recording file. We assume the
+// data is provided by a process thread and the writes are requested
+// by a disk thread.
+//
+class RecordableAudioFile
+{
+public:
+ typedef float sample_t;
+
+ typedef enum
+ {
+ IDLE,
+ RECORDING,
+ DEFUNCT
+ } RecordStatus;
+
+ RecordableAudioFile(AudioFile *audioFile, // should be already open for writing
+ size_t bufferSize);
+ ~RecordableAudioFile();
+
+ void setStatus(const RecordStatus &status) { m_status = status; }
+ RecordStatus getStatus() const { return m_status; }
+
+ size_t buffer(const sample_t *data, int channel, size_t frames);
+ void write();
+
+protected:
+ AudioFile *m_audioFile;
+ RecordStatus m_status;
+
+ std::vector<RingBuffer<sample_t> *> m_ringBuffers; // one per channel
+};
+
+}
+
+#endif
diff --git a/src/sound/RingBuffer.h b/src/sound/RingBuffer.h
new file mode 100644
index 0000000..0cc5dc6
--- /dev/null
+++ b/src/sound/RingBuffer.h
@@ -0,0 +1,572 @@
+// -*- c-basic-offset: 4 -*-
+
+/*
+ Rosegarden
+ A sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <bownie@bownie.com>
+
+ 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 _RINGBUFFER_H_
+#define _RINGBUFFER_H_
+
+#include <sys/types.h>
+#include <sys/mman.h>
+
+#include "Scavenger.h"
+
+//#define DEBUG_RINGBUFFER 1
+//#define DEBUG_RINGBUFFER_CREATE_DESTROY 1
+
+#ifdef DEBUG_RINGBUFFER
+#define DEBUG_RINGBUFFER_CREATE_DESTROY 1
+#endif
+
+#ifdef DEBUG_RINGBUFFER_CREATE_DESTROY
+#include <iostream>
+static int __extant_ringbuffers = 0;
+#endif
+
+namespace Rosegarden {
+
+/**
+ * RingBuffer implements a lock-free ring buffer for one writer and N
+ * readers, that is to be used to store a sample type T.
+ *
+ * For efficiency, RingBuffer frequently initialises samples by
+ * writing zeroes into their memory space, so T should normally be a
+ * simple type that can safely be set to zero using memset.
+ */
+
+template <typename T, int N = 1>
+class RingBuffer
+{
+public:
+ /**
+ * Create a ring buffer with room to write n samples.
+ *
+ * Note that the internal storage size will actually be n+1
+ * samples, as one element is unavailable for administrative
+ * reasons. Since the ring buffer performs best if its size is a
+ * power of two, this means n should ideally be some power of two
+ * minus one.
+ */
+ RingBuffer(size_t n);
+
+ virtual ~RingBuffer();
+
+ /**
+ * Return the total capacity of the ring buffer in samples.
+ * (This is the argument n passed to the constructor.)
+ */
+ size_t getSize() const;
+
+ /**
+ * Resize the ring buffer. This also empties it. Actually swaps
+ * in a new, larger buffer; the old buffer is scavenged after a
+ * seemly delay. Should be called from the write thread.
+ */
+ void resize(size_t newSize);
+
+ /**
+ * Lock the ring buffer into physical memory. Returns true
+ * for success.
+ */
+ bool mlock();
+
+ /**
+ * Unlock the ring buffer from physical memory. Returns true for
+ * success.
+ */
+ bool munlock();
+
+ /**
+ * Reset read and write pointers, thus emptying the buffer.
+ * Should be called from the write thread.
+ */
+ void reset();
+
+ /**
+ * Return the amount of data available for reading by reader R, in
+ * samples.
+ */
+ size_t getReadSpace(int R = 0) const;
+
+ /**
+ * Return the amount of space available for writing, in samples.
+ */
+ size_t getWriteSpace() const;
+
+ /**
+ * Read n samples from the buffer, for reader R. If fewer than n
+ * are available, the remainder will be zeroed out. Returns the
+ * number of samples actually read.
+ */
+ size_t read(T *destination, size_t n, int R = 0);
+
+ /**
+ * Read n samples from the buffer, for reader R, adding them to
+ * the destination. If fewer than n are available, the remainder
+ * will be left alone. Returns the number of samples actually
+ * read.
+ */
+ size_t readAdding(T *destination, size_t n, int R = 0);
+
+ /**
+ * Read one sample from the buffer, for reader R. If no sample is
+ * available, this will silently return zero. Calling this
+ * repeatedly is obviously slower than calling read once, but it
+ * may be good enough if you don't want to allocate a buffer to
+ * read into.
+ */
+ T readOne(int R = 0);
+
+ /**
+ * Read n samples from the buffer, if available, for reader R,
+ * without advancing the read pointer -- i.e. a subsequent read()
+ * or skip() will be necessary to empty the buffer. If fewer than
+ * n are available, the remainder will be zeroed out. Returns the
+ * number of samples actually read.
+ */
+ size_t peek(T *destination, size_t n, int R = 0) const;
+
+ /**
+ * Read one sample from the buffer, if available, without
+ * advancing the read pointer -- i.e. a subsequent read() or
+ * skip() will be necessary to empty the buffer. Returns zero if
+ * no sample was available.
+ */
+ T peek(int R = 0) const;
+
+ /**
+ * Pretend to read n samples from the buffer, for reader R,
+ * without actually returning them (i.e. discard the next n
+ * samples). Returns the number of samples actually available for
+ * discarding.
+ */
+ size_t skip(size_t n, int R = 0);
+
+ /**
+ * Write n samples to the buffer. If insufficient space is
+ * available, not all samples may actually be written. Returns
+ * the number of samples actually written.
+ */
+ size_t write(const T *source, size_t n);
+
+ /**
+ * Write n zero-value samples to the buffer. If insufficient
+ * space is available, not all zeros may actually be written.
+ * Returns the number of zeroes actually written.
+ */
+ size_t zero(size_t n);
+
+protected:
+ T *m_buffer;
+ volatile size_t m_writer;
+ volatile size_t m_readers[N];
+ size_t m_size;
+ bool m_mlocked;
+
+ static Scavenger<ScavengerArrayWrapper<T> > m_scavenger;
+
+private:
+ RingBuffer(const RingBuffer &); // not provided
+ RingBuffer &operator=(const RingBuffer &); // not provided
+};
+
+template <typename T, int N>
+Scavenger<ScavengerArrayWrapper<T> > RingBuffer<T, N>::m_scavenger;
+
+template <typename T, int N>
+RingBuffer<T, N>::RingBuffer(size_t n) :
+ m_buffer(new T[n + 1]),
+ m_writer(0),
+ m_size(n + 1),
+ m_mlocked(false)
+{
+#ifdef DEBUG_RINGBUFFER_CREATE_DESTROY
+ std::cerr << "RingBuffer<T," << N << ">[" << this << "]::RingBuffer(" << n << ") [now have " << (++__extant_ringbuffers) << "]" << std::endl;
+#endif
+
+ for (int i = 0; i < N; ++i) m_readers[i] = 0;
+
+ m_scavenger.scavenge();
+}
+
+template <typename T, int N>
+RingBuffer<T, N>::~RingBuffer()
+{
+#ifdef DEBUG_RINGBUFFER_CREATE_DESTROY
+ std::cerr << "RingBuffer<T," << N << ">[" << this << "]::~RingBuffer [now have " << (--__extant_ringbuffers) << "]" << std::endl;
+#endif
+
+ if (m_mlocked) {
+ ::munlock((void *)m_buffer, m_size * sizeof(T));
+ }
+ delete[] m_buffer;
+
+ m_scavenger.scavenge();
+}
+
+template <typename T, int N>
+size_t
+RingBuffer<T, N>::getSize() const
+{
+#ifdef DEBUG_RINGBUFFER
+ std::cerr << "RingBuffer<T," << N << ">[" << this << "]::getSize(): " << m_size-1 << std::endl;
+#endif
+
+ return m_size - 1;
+}
+
+template <typename T, int N>
+void
+RingBuffer<T, N>::resize(size_t newSize)
+{
+#ifdef DEBUG_RINGBUFFER_CREATE_DESTROY
+ std::cerr << "RingBuffer<T," << N << ">[" << this << "]::resize(" << newSize << ")" << std::endl;
+#endif
+
+ m_scavenger.scavenge();
+
+ if (m_mlocked) {
+ ::munlock((void *)m_buffer, m_size * sizeof(T));
+ }
+
+ m_scavenger.claim(new ScavengerArrayWrapper<T>(m_buffer));
+
+ reset();
+ m_buffer = new T[newSize + 1];
+ m_size = newSize + 1;
+
+ if (m_mlocked) {
+ if (::mlock((void *)m_buffer, m_size * sizeof(T))) {
+ m_mlocked = false;
+ }
+ }
+}
+
+template <typename T, int N>
+bool
+RingBuffer<T, N>::mlock()
+{
+ if (::mlock((void *)m_buffer, m_size * sizeof(T))) return false;
+ m_mlocked = true;
+ return true;
+}
+
+template <typename T, int N>
+bool
+RingBuffer<T, N>::munlock()
+{
+ if (::munlock((void *)m_buffer, m_size * sizeof(T))) return false;
+ m_mlocked = false;
+ return true;
+}
+
+template <typename T, int N>
+void
+RingBuffer<T, N>::reset()
+{
+#ifdef DEBUG_RINGBUFFER
+ std::cerr << "RingBuffer<T," << N << ">[" << this << "]::reset" << std::endl;
+#endif
+
+ m_writer = 0;
+ for (int i = 0; i < N; ++i) m_readers[i] = 0;
+}
+
+template <typename T, int N>
+size_t
+RingBuffer<T, N>::getReadSpace(int R) const
+{
+ size_t writer = m_writer;
+ size_t reader = m_readers[R];
+ size_t space = 0;
+
+ if (writer > reader) space = writer - reader;
+ else space = ((writer + m_size) - reader) % m_size;
+
+#ifdef DEBUG_RINGBUFFER
+ std::cerr << "RingBuffer<T," << N << ">[" << this << "]::getReadSpace(" << R << "): " << space << std::endl;
+#endif
+
+ return space;
+}
+
+template <typename T, int N>
+size_t
+RingBuffer<T, N>::getWriteSpace() const
+{
+ size_t space = 0;
+ for (int i = 0; i < N; ++i) {
+ size_t here = (m_readers[i] + m_size - m_writer - 1) % m_size;
+ if (i == 0 || here < space) space = here;
+ }
+
+#ifdef DEBUG_RINGBUFFER
+ size_t rs(getReadSpace()), rp(m_readers[0]);
+
+ std::cerr << "RingBuffer: write space " << space << ", read space "
+ << rs << ", total " << (space + rs) << ", m_size " << m_size << std::endl;
+ std::cerr << "RingBuffer: reader " << rp << ", writer " << m_writer << std::endl;
+#endif
+
+#ifdef DEBUG_RINGBUFFER
+ std::cerr << "RingBuffer<T," << N << ">[" << this << "]::getWriteSpace(): " << space << std::endl;
+#endif
+
+ return space;
+}
+
+template <typename T, int N>
+size_t
+RingBuffer<T, N>::read(T *destination, size_t n, int R)
+{
+#ifdef DEBUG_RINGBUFFER
+ std::cerr << "RingBuffer<T," << N << ">[" << this << "]::read(dest, " << n << ", " << R << ")" << std::endl;
+#endif
+
+ size_t available = getReadSpace(R);
+ if (n > available) {
+#ifdef DEBUG_RINGBUFFER
+ std::cerr << "WARNING: Only " << available << " samples available"
+ << std::endl;
+#endif
+ memset(destination + available, 0, (n - available) * sizeof(T));
+ n = available;
+ }
+ if (n == 0) return n;
+
+ size_t here = m_size - m_readers[R];
+ if (here >= n) {
+ memcpy(destination, m_buffer + m_readers[R], n * sizeof(T));
+ } else {
+ memcpy(destination, m_buffer + m_readers[R], here * sizeof(T));
+ memcpy(destination + here, m_buffer, (n - here) * sizeof(T));
+ }
+
+ m_readers[R] = (m_readers[R] + n) % m_size;
+
+#ifdef DEBUG_RINGBUFFER
+ std::cerr << "RingBuffer<T," << N << ">[" << this << "]::read: read " << n << ", reader now " << m_readers[R] << std::endl;
+#endif
+
+ return n;
+}
+
+template <typename T, int N>
+size_t
+RingBuffer<T, N>::readAdding(T *destination, size_t n, int R)
+{
+#ifdef DEBUG_RINGBUFFER
+ std::cerr << "RingBuffer<T," << N << ">[" << this << "]::readAdding(dest, " << n << ", " << R << ")" << std::endl;
+#endif
+
+ size_t available = getReadSpace(R);
+ if (n > available) {
+#ifdef DEBUG_RINGBUFFER
+ std::cerr << "WARNING: Only " << available << " samples available"
+ << std::endl;
+#endif
+ n = available;
+ }
+ if (n == 0) return n;
+
+ size_t here = m_size - m_readers[R];
+
+ if (here >= n) {
+ for (size_t i = 0; i < n; ++i) {
+ destination[i] += (m_buffer + m_readers[R])[i];
+ }
+ } else {
+ for (size_t i = 0; i < here; ++i) {
+ destination[i] += (m_buffer + m_readers[R])[i];
+ }
+ for (size_t i = 0; i < (n - here); ++i) {
+ destination[i + here] += m_buffer[i];
+ }
+ }
+
+ m_readers[R] = (m_readers[R] + n) % m_size;
+ return n;
+}
+
+template <typename T, int N>
+T
+RingBuffer<T, N>::readOne(int R)
+{
+#ifdef DEBUG_RINGBUFFER
+ std::cerr << "RingBuffer<T," << N << ">[" << this << "]::readOne(" << R << ")" << std::endl;
+#endif
+
+ if (m_writer == m_readers[R]) {
+#ifdef DEBUG_RINGBUFFER
+ std::cerr << "WARNING: No sample available"
+ << std::endl;
+#endif
+ T t;
+ memset(&t, 0, sizeof(T));
+ return t;
+ }
+ T value = m_buffer[m_readers[R]];
+ if (++m_readers[R] == m_size) m_readers[R] = 0;
+ return value;
+}
+
+template <typename T, int N>
+size_t
+RingBuffer<T, N>::peek(T *destination, size_t n, int R) const
+{
+#ifdef DEBUG_RINGBUFFER
+ std::cerr << "RingBuffer<T," << N << ">[" << this << "]::peek(dest, " << n << ", " << R << ")" << std::endl;
+#endif
+
+ size_t available = getReadSpace(R);
+ if (n > available) {
+#ifdef DEBUG_RINGBUFFER
+ std::cerr << "WARNING: Only " << available << " samples available"
+ << std::endl;
+#endif
+ memset(destination + available, 0, (n - available) * sizeof(T));
+ n = available;
+ }
+ if (n == 0) return n;
+
+ size_t here = m_size - m_readers[R];
+ if (here >= n) {
+ memcpy(destination, m_buffer + m_readers[R], n * sizeof(T));
+ } else {
+ memcpy(destination, m_buffer + m_readers[R], here * sizeof(T));
+ memcpy(destination + here, m_buffer, (n - here) * sizeof(T));
+ }
+
+#ifdef DEBUG_RINGBUFFER
+ std::cerr << "RingBuffer<T," << N << ">[" << this << "]::peek: read " << n << std::endl;
+#endif
+
+ return n;
+}
+
+template <typename T, int N>
+T
+RingBuffer<T, N>::peek(int R) const
+{
+#ifdef DEBUG_RINGBUFFER
+ std::cerr << "RingBuffer<T," << N << ">[" << this << "]::peek(" << R << ")" << std::endl;
+#endif
+
+ if (m_writer == m_readers[R]) {
+#ifdef DEBUG_RINGBUFFER
+ std::cerr << "WARNING: No sample available"
+ << std::endl;
+#endif
+ T t;
+ memset(&t, 0, sizeof(T));
+ return t;
+ }
+ T value = m_buffer[m_readers[R]];
+ return value;
+}
+
+template <typename T, int N>
+size_t
+RingBuffer<T, N>::skip(size_t n, int R)
+{
+#ifdef DEBUG_RINGBUFFER
+ std::cerr << "RingBuffer<T," << N << ">[" << this << "]::skip(" << n << ", " << R << ")" << std::endl;
+#endif
+
+ size_t available = getReadSpace(R);
+ if (n > available) {
+#ifdef DEBUG_RINGBUFFER
+ std::cerr << "WARNING: Only " << available << " samples available"
+ << std::endl;
+#endif
+ n = available;
+ }
+ if (n == 0) return n;
+ m_readers[R] = (m_readers[R] + n) % m_size;
+ return n;
+}
+
+template <typename T, int N>
+size_t
+RingBuffer<T, N>::write(const T *source, size_t n)
+{
+#ifdef DEBUG_RINGBUFFER
+ std::cerr << "RingBuffer<T," << N << ">[" << this << "]::write(" << n << ")" << std::endl;
+#endif
+
+ size_t available = getWriteSpace();
+ if (n > available) {
+#ifdef DEBUG_RINGBUFFER
+ std::cerr << "WARNING: Only room for " << available << " samples"
+ << std::endl;
+#endif
+ n = available;
+ }
+ if (n == 0) return n;
+
+ size_t here = m_size - m_writer;
+ if (here >= n) {
+ memcpy(m_buffer + m_writer, source, n * sizeof(T));
+ } else {
+ memcpy(m_buffer + m_writer, source, here * sizeof(T));
+ memcpy(m_buffer, source + here, (n - here) * sizeof(T));
+ }
+
+ m_writer = (m_writer + n) % m_size;
+
+#ifdef DEBUG_RINGBUFFER
+ std::cerr << "RingBuffer<T," << N << ">[" << this << "]::write: wrote " << n << ", writer now " << m_writer << std::endl;
+#endif
+
+ return n;
+}
+
+template <typename T, int N>
+size_t
+RingBuffer<T, N>::zero(size_t n)
+{
+#ifdef DEBUG_RINGBUFFER
+ std::cerr << "RingBuffer<T," << N << ">[" << this << "]::zero(" << n << ")" << std::endl;
+#endif
+
+ size_t available = getWriteSpace();
+ if (n > available) {
+#ifdef DEBUG_RINGBUFFER
+ std::cerr << "WARNING: Only room for " << available << " samples"
+ << std::endl;
+#endif
+ n = available;
+ }
+ if (n == 0) return n;
+
+ size_t here = m_size - m_writer;
+ if (here >= n) {
+ memset(m_buffer + m_writer, 0, n * sizeof(T));
+ } else {
+ memset(m_buffer + m_writer, 0, here * sizeof(T));
+ memset(m_buffer, 0, (n - here) * sizeof(T));
+ }
+
+ m_writer = (m_writer + n) % m_size;
+ return n;
+}
+
+}
+
+#endif // _RINGBUFFER_H_
diff --git a/src/sound/RosegardenMidiRecord.mcopclass b/src/sound/RosegardenMidiRecord.mcopclass
new file mode 100644
index 0000000..41593f1
--- /dev/null
+++ b/src/sound/RosegardenMidiRecord.mcopclass
@@ -0,0 +1,5 @@
+Interface=RosegardenMidiRecord, Arts::MidiPort, Arts::Object
+Library=libRosegardenSequencer.la
+Language=C++
+Author="Richard Bown <bownie@bownie.com>", "Guillaume Laurent <glaurent@telegraph-road.org>", "Chris Cannam <cannam@all-day-breakfast.com>"
+URL="http://home"
diff --git a/src/sound/RunnablePluginInstance.cpp b/src/sound/RunnablePluginInstance.cpp
new file mode 100644
index 0000000..820aaf9
--- /dev/null
+++ b/src/sound/RunnablePluginInstance.cpp
@@ -0,0 +1,42 @@
+// -*- c-basic-offset: 4 -*-
+
+/*
+ Rosegarden
+ A sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <bownie@bownie.com>
+
+ 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 "RunnablePluginInstance.h"
+#include "PluginFactory.h"
+
+#include <iostream>
+
+namespace Rosegarden
+{
+
+RunnablePluginInstance::~RunnablePluginInstance()
+{
+// std::cerr << "RunnablePluginInstance::~RunnablePluginInstance" << std::endl;
+
+ if (m_factory) {
+// std::cerr << "Asking factory to release " << m_identifier << std::endl;
+
+ m_factory->releasePlugin(this, m_identifier);
+ }
+}
+
+}
+
diff --git a/src/sound/RunnablePluginInstance.h b/src/sound/RunnablePluginInstance.h
new file mode 100644
index 0000000..f15f146
--- /dev/null
+++ b/src/sound/RunnablePluginInstance.h
@@ -0,0 +1,114 @@
+// -*- c-basic-offset: 4 -*-
+
+/*
+ Rosegarden
+ A sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <bownie@bownie.com>
+
+ 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 _RUNNABLE_PLUGIN_INSTANCE_H_
+#define _RUNNABLE_PLUGIN_INSTANCE_H_
+
+#include <qstring.h>
+#include <qstringlist.h>
+#include <vector>
+
+#include "RealTime.h"
+
+namespace Rosegarden
+{
+
+class PluginFactory;
+
+/**
+ * RunnablePluginInstance is a very trivial interface that an audio
+ * process can use to refer to an instance of a plugin without needing
+ * to know what type of plugin it is.
+ *
+ * The audio code calls run() on an instance that has been passed to
+ * it, and assumes that the passing code has already initialised the
+ * plugin, connected its inputs and outputs and so on, and that there
+ * is an understanding in place about the sizes of the buffers in use
+ * by the plugin. All of this depends on the subclass implementation.
+ */
+
+class RunnablePluginInstance
+{
+public:
+ typedef float sample_t;
+
+ virtual ~RunnablePluginInstance();
+
+ virtual bool isOK() const = 0;
+
+ virtual QString getIdentifier() const = 0;
+
+ /**
+ * Run for one block, starting at the given time. The start time
+ * may be of interest to synths etc that may have queued events
+ * waiting. Other plugins can ignore it.
+ */
+ virtual void run(const RealTime &blockStartTime) = 0;
+
+ virtual size_t getBufferSize() = 0;
+
+ virtual size_t getAudioInputCount() = 0;
+ virtual size_t getAudioOutputCount() = 0;
+
+ virtual sample_t **getAudioInputBuffers() = 0;
+ virtual sample_t **getAudioOutputBuffers() = 0;
+
+ virtual QStringList getPrograms() { return QStringList(); }
+ virtual QString getCurrentProgram() { return QString(); }
+ virtual QString getProgram(int /* bank */, int /* program */) { return QString(); }
+ virtual unsigned long getProgram(QString /* name */) { return 0; } // bank << 16 + program
+ virtual void selectProgram(QString) { }
+
+ virtual void setPortValue(unsigned int port, float value) = 0;
+ virtual float getPortValue(unsigned int port) = 0;
+
+ virtual QString configure(QString /* key */, QString /* value */) { return QString(); }
+
+ virtual void sendEvent(const RealTime & /* eventTime */,
+ const void * /* event */) { }
+
+ virtual bool isBypassed() const = 0;
+ virtual void setBypassed(bool value) = 0;
+
+ // This should be called after setup, but while not actually playing.
+ virtual size_t getLatency() = 0;
+
+ virtual void silence() = 0;
+ virtual void discardEvents() { }
+ virtual void setIdealChannelCount(size_t channels) = 0; // must also silence(); may also re-instantiate
+
+ void setFactory(PluginFactory *f) { m_factory = f; } // ew
+
+protected:
+ RunnablePluginInstance(PluginFactory *factory, QString identifier) :
+ m_factory(factory), m_identifier(identifier) { }
+
+ PluginFactory *m_factory;
+ QString m_identifier;
+
+ friend class PluginFactory;
+};
+
+typedef std::vector<RunnablePluginInstance *> RunnablePluginInstances;
+
+}
+
+#endif
diff --git a/src/sound/SF2PatchExtractor.cpp b/src/sound/SF2PatchExtractor.cpp
new file mode 100644
index 0000000..6ba8dc5
--- /dev/null
+++ b/src/sound/SF2PatchExtractor.cpp
@@ -0,0 +1,217 @@
+// -*- c-basic-offset: 4 -*-
+
+/*
+ Rosegarden
+ A sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <bownie@bownie.com>
+
+ 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 "SF2PatchExtractor.h"
+
+#include <iostream>
+#include <fstream>
+#include <string>
+#include <map>
+#include <sys/types.h>
+
+namespace Rosegarden
+{
+
+using std::string;
+using std::cerr;
+using std::endl;
+using std::ifstream;
+using std::ios;
+
+
+struct Chunk
+{
+ char id[4];
+ u_int32_t size;
+
+ Chunk(ifstream *, bool idOnly = false);
+ bool isa(std::string s);
+};
+
+Chunk::Chunk(ifstream *file, bool idOnly)
+{
+ file->read((char *)this->id, 4);
+ size = 0;
+
+ if (idOnly)
+ return ;
+
+ unsigned char sz[4];
+ file->read((char *)sz, 4);
+ for (int i = 0; i < 4; ++i)
+ size += sz[i] << (i * 8);
+}
+
+bool
+Chunk::isa(string s)
+{
+ return string(id, 4) == s;
+}
+
+bool
+SF2PatchExtractor::isSF2File(string fileName)
+{
+ ifstream *file = new ifstream(fileName.c_str(), ios::in | ios::binary);
+ if (!file)
+ throw FileNotFoundException();
+
+ Chunk riffchunk(file);
+ if (!riffchunk.isa("RIFF")) {
+ file->close();
+ return false;
+ }
+
+ Chunk sfbkchunk(file, true);
+ if (!sfbkchunk.isa("sfbk")) {
+ file->close();
+ return false;
+ }
+
+ file->close();
+ return true;
+}
+
+SF2PatchExtractor::Device
+SF2PatchExtractor::read(string fileName)
+{
+ Device device;
+
+ ifstream *file = new ifstream(fileName.c_str(), ios::in | ios::binary);
+ if (!file)
+ throw FileNotFoundException();
+
+ Chunk riffchunk(file);
+ if (!riffchunk.isa("RIFF")) {
+ file->close();
+ throw WrongFileFormatException();
+ }
+
+ Chunk sfbkchunk(file, true);
+ if (!sfbkchunk.isa("sfbk")) {
+ file->close();
+ throw WrongFileFormatException();
+ }
+
+ while (!file->eof()) {
+
+ Chunk chunk(file);
+
+ if (!chunk.isa("LIST")) {
+ // cerr << "Skipping " << string(chunk.id, 4) << endl;
+ file->seekg(chunk.size, ios::cur);
+ continue;
+ }
+
+ Chunk listchunk(file, true);
+ if (!listchunk.isa("pdta")) {
+ // cerr << "Skipping " << string(id, 4) << endl;
+ file->seekg(chunk.size - 4, ios::cur);
+ continue;
+ }
+
+ int size = chunk.size - 4;
+ while (size > 0) {
+
+ Chunk pdtachunk(file);
+ size -= 8 + pdtachunk.size;
+ if (file->eof()) {
+ break;
+ }
+
+ if (!pdtachunk.isa("phdr")) { // preset header
+ // cerr << "Skipping " << string(pdtachunk.id, 4) << endl;
+ file->seekg(pdtachunk.size, ios::cur);
+ continue;
+ }
+
+ int presets = pdtachunk.size / 38;
+ for (int i = 0; i < presets; ++i) {
+
+ char name[21];
+ u_int16_t bank, program;
+
+ file->read((char *)name, 20);
+ name[20] = '\0';
+ file->read((char *)&program, 2);
+ file->read((char *)&bank, 2);
+
+ // cerr << "Read name as " << name << endl;
+
+ file->seekg(14, ios::cur);
+
+ if (i == presets - 1 &&
+ bank == 255 &&
+ program == 255 &&
+ string(name) == "EOP")
+ continue;
+
+ device[bank][program] = name;
+ }
+ }
+ }
+
+ file->close();
+ return device;
+}
+
+}
+
+
+#ifdef TEST_SF2_PATCH_EXTRACTOR
+
+int main(int argc, char **argv)
+{
+ using SF2PatchExtractor;
+
+ if (argc != 2) {
+ std::cerr << "Usage: " << argv[0] << " sf2filename" << std::endl;
+ return 2;
+ }
+
+ try {
+ SF2PatchExtractor::Device device =
+ SF2PatchExtractor::read(argv[1]);
+
+ std::cerr << "Done. Presets are:" << std::endl;
+
+ for (SF2PatchExtractor::Device::iterator di = device.begin();
+ di != device.end(); ++di) {
+
+ std::cerr << "Bank " << di->first << ":" << std::endl;
+
+ for (SF2PatchExtractor::Bank::iterator bi = di->second.begin();
+ bi != di->second.end();
+ ++bi) {
+
+ std::cerr << "Program " << bi->first << ": \"" << bi->second
+ << "\"" << std::endl;
+ }
+ }
+ } catch (SF2PatchExtractor::WrongFileFormatException) {
+ std::cerr << "Wrong file format" << std::endl;
+ } catch (SF2PatchExtractor::FileNotFoundException) {
+ std::cerr << "File not found or couldn't be opened" << std::endl;
+ }
+
+ return 0;
+}
+
+#endif
diff --git a/src/sound/SF2PatchExtractor.h b/src/sound/SF2PatchExtractor.h
new file mode 100644
index 0000000..a9d5453
--- /dev/null
+++ b/src/sound/SF2PatchExtractor.h
@@ -0,0 +1,58 @@
+// -*- c-basic-offset: 4 -*-
+
+/*
+ Rosegarden
+ A sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <bownie@bownie.com>
+
+ 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 _SF2_PATCH_EXTRACTOR_H_
+#define _SF2_PATCH_EXTRACTOR_H_
+
+#include <string>
+#include <map>
+
+namespace Rosegarden {
+
+/**
+ * Trivial class to suck the patch map out of a .sf2 SoundFont file.
+ * Inspired by (but not based on) sftovkb by Takashi Iwai.
+ *
+ * SoundFont is a straightforward RIFF format so there's some
+ * redundancy between this and RIFFAudioFile -- we don't take any
+ * advantage of that, and this class is completely self-contained.
+ *
+ * Tolerates garbled files; will just suck all it can rather than
+ * throw an error, except if the file is not a SoundFont at all.
+ */
+
+class SF2PatchExtractor
+{
+public:
+ typedef std::map<int, std::string> Bank;
+ typedef std::map<int, Bank> Device;
+
+ struct FileNotFoundException { };
+ struct WrongFileFormatException { };
+
+ static bool isSF2File(std::string fileName);
+ static Device read(std::string fileName);
+};
+
+}
+
+#endif
+
diff --git a/src/sound/SampleWindow.h b/src/sound/SampleWindow.h
new file mode 100644
index 0000000..88400f7
--- /dev/null
+++ b/src/sound/SampleWindow.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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+/*
+ This file is derived from
+
+ Sonic Visualiser
+ An audio file viewer and annotation editor.
+ Centre for Digital Music, Queen Mary, University of London.
+ This file copyright 2006 Chris Cannam.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+#ifndef _SAMPLE_WINDOW_H_
+#define _SAMPLE_WINDOW_H_
+
+#include <cmath>
+#include <cstdlib>
+#include <iostream>
+#include <map>
+
+namespace Rosegarden
+{
+
+template <typename T>
+class SampleWindow
+{
+public:
+ enum Type {
+ Rectangular,
+ Bartlett,
+ Hamming,
+ Hanning,
+ Blackman,
+ Gaussian,
+ Parzen,
+ Nuttall,
+ BlackmanHarris
+ };
+
+ /**
+ * Construct a windower of the given type.
+ */
+ SampleWindow(Type type, size_t size) : m_type(type), m_size(size) { encache(); }
+ SampleWindow(const SampleWindow &w) : m_type(w.m_type), m_size(w.m_size) { encache(); }
+ SampleWindow &operator=(const SampleWindow &w) {
+ if (&w == this) return *this;
+ m_type = w.m_type;
+ m_size = w.m_size;
+ encache();
+ return *this;
+ }
+ virtual ~SampleWindow() { delete[] m_cache; }
+
+ void cut(T *src) const { cut(src, src); }
+ void cut(T *src, T *dst) const {
+ for (size_t i = 0; i < m_size; ++i) dst[i] = src[i] * m_cache[i];
+ }
+
+ T getArea() { return m_area; }
+ T getValue(size_t i) { return m_cache[i]; }
+
+ Type getType() const { return m_type; }
+ size_t getSize() const { return m_size; }
+
+protected:
+ Type m_type;
+ size_t m_size;
+ T *m_cache;
+ T m_area;
+
+ void encache();
+ void cosinewin(T *, T, T, T, T);
+};
+
+template <typename T>
+void SampleWindow<T>::encache()
+{
+ int n = int(m_size);
+ T *mult = new T[n];
+ int i;
+ for (i = 0; i < n; ++i) mult[i] = 1.0;
+
+ switch (m_type) {
+
+ case Rectangular:
+ for (i = 0; i < n; ++i) {
+ mult[i] *= 0.5;
+ }
+ break;
+
+ case Bartlett:
+ for (i = 0; i < n/2; ++i) {
+ mult[i] *= (i / T(n/2));
+ mult[i + n/2] *= (1.0 - (i / T(n/2)));
+ }
+ break;
+
+ case Hamming:
+ cosinewin(mult, 0.54, 0.46, 0.0, 0.0);
+ break;
+
+ case Hanning:
+ cosinewin(mult, 0.50, 0.50, 0.0, 0.0);
+ break;
+
+ case Blackman:
+ cosinewin(mult, 0.42, 0.50, 0.08, 0.0);
+ break;
+
+ case Gaussian:
+ for (i = 0; i < n; ++i) {
+ mult[i] *= pow(2, - pow((i - (n-1)/2.0) / ((n-1)/2.0 / 3), 2));
+ }
+ break;
+
+ case Parzen:
+ {
+ int N = n-1;
+ for (i = 0; i < N/4; ++i) {
+ T m = 2 * pow(1.0 - (T(N)/2 - i) / (T(N)/2), 3);
+ mult[i] *= m;
+ mult[N-i] *= m;
+ }
+ for (i = N/4; i <= N/2; ++i) {
+ int wn = i - N/2;
+ T m = 1.0 - 6 * pow(wn / (T(N)/2), 2) * (1.0 - abs(wn) / (T(N)/2));
+ mult[i] *= m;
+ mult[N-i] *= m;
+ }
+ break;
+ }
+
+ case Nuttall:
+ cosinewin(mult, 0.3635819, 0.4891775, 0.1365995, 0.0106411);
+ break;
+
+ case BlackmanHarris:
+ cosinewin(mult, 0.35875, 0.48829, 0.14128, 0.01168);
+ break;
+ }
+
+ m_cache = mult;
+
+ m_area = 0;
+ for (int i = 0; i < n; ++i) {
+ m_area += m_cache[i];
+ }
+ m_area /= n;
+}
+
+template <typename T>
+void SampleWindow<T>::cosinewin(T *mult, T a0, T a1, T a2, T a3)
+{
+ int n = int(m_size);
+ for (int i = 0; i < n; ++i) {
+ mult[i] *= (a0
+ - a1 * cos(2 * M_PI * i / n)
+ + a2 * cos(4 * M_PI * i / n)
+ - a3 * cos(6 * M_PI * i / n));
+ }
+}
+
+}
+
+#endif
diff --git a/src/sound/Scavenger.h b/src/sound/Scavenger.h
new file mode 100644
index 0000000..b27e848
--- /dev/null
+++ b/src/sound/Scavenger.h
@@ -0,0 +1,211 @@
+// -*- c-basic-offset: 4 -*-
+
+/*
+ Rosegarden
+ A sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <bownie@bownie.com>
+
+ 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 _SCAVENGER_H_
+#define _SCAVENGER_H_
+
+#include <vector>
+#include <list>
+#include <sys/time.h>
+#include <pthread.h>
+#include <iostream>
+
+namespace Rosegarden
+{
+
+/**
+ * A very simple class that facilitates running things like plugins
+ * without locking, by collecting unwanted objects and deleting them
+ * after a delay so as to be sure nobody's in the middle of using
+ * them. Requires scavenge() to be called regularly from a non-RT
+ * thread.
+ *
+ * This is currently not at all suitable for large numbers of objects
+ * -- it's just a quick hack for use with things like plugins.
+ */
+
+template <typename T>
+class Scavenger
+{
+public:
+ Scavenger(int sec = 2, int defaultObjectListSize = 200);
+ ~Scavenger();
+
+ /**
+ * Call from an RT thread etc., to pass ownership of t to us for
+ * later disposal. Only one thread should be calling this on any
+ * given scavenger.
+ *
+ * This is only lock-free so long as a slot is available in the
+ * object list; otherwise it takes a lock and allocates memory.
+ * Scavengers should always be used with an object list size
+ * sufficient to ensure that enough slots are always available in
+ * normal use.
+ */
+ void claim(T *t);
+
+ /**
+ * Call from a non-RT thread.
+ * Only one thread should be calling this on any given scavenger.
+ */
+ void scavenge();
+
+protected:
+ typedef std::pair<T *, int> ObjectTimePair;
+ typedef std::vector<ObjectTimePair> ObjectTimeList;
+ ObjectTimeList m_objects;
+ int m_sec;
+
+ typedef std::list<T *> ObjectList;
+ ObjectList m_excess;
+ int m_lastExcess;
+ pthread_mutex_t m_excessMutex;
+ void pushExcess(T *);
+ void clearExcess(int);
+
+ unsigned int m_claimed;
+ unsigned int m_scavenged;
+};
+
+/**
+ * A wrapper to permit arrays to be scavenged.
+ */
+
+template <typename T>
+class ScavengerArrayWrapper
+{
+public:
+ ScavengerArrayWrapper(T *array) : m_array(array) { }
+ ~ScavengerArrayWrapper() { delete[] m_array; }
+
+private:
+ T *m_array;
+};
+
+
+template <typename T>
+Scavenger<T>::Scavenger(int sec, int defaultObjectListSize) :
+ m_objects(ObjectTimeList(defaultObjectListSize)),
+ m_sec(sec),
+ m_lastExcess(0),
+ m_claimed(0),
+ m_scavenged(0)
+{
+ pthread_mutex_init(&m_excessMutex, NULL);
+}
+
+template <typename T>
+Scavenger<T>::~Scavenger()
+{
+ if (m_scavenged < m_claimed) {
+ for (size_t i = 0; i < m_objects.size(); ++i) {
+ ObjectTimePair &pair = m_objects[i];
+ if (pair.first != 0) {
+ T *ot = pair.first;
+ pair.first = 0;
+ delete ot;
+ ++m_scavenged;
+ }
+ }
+ }
+
+ clearExcess(0);
+
+ pthread_mutex_destroy(&m_excessMutex);
+}
+
+template <typename T>
+void
+Scavenger<T>::claim(T *t)
+{
+ struct timeval tv;
+ (void)gettimeofday(&tv, 0);
+ int sec = tv.tv_sec;
+
+ for (size_t i = 0; i < m_objects.size(); ++i) {
+ ObjectTimePair &pair = m_objects[i];
+ if (pair.first == 0) {
+ pair.second = sec;
+ pair.first = t;
+ ++m_claimed;
+ return;
+ }
+ }
+
+ std::cerr << "WARNING: Scavenger::claim(" << t << "): run out of slots, "
+ << "using non-RT-safe method" << std::endl;
+ pushExcess(t);
+}
+
+template <typename T>
+void
+Scavenger<T>::scavenge()
+{
+ if (m_scavenged >= m_claimed) return;
+
+ struct timeval tv;
+ (void)gettimeofday(&tv, 0);
+ int sec = tv.tv_sec;
+
+ for (size_t i = 0; i < m_objects.size(); ++i) {
+ ObjectTimePair &pair = m_objects[i];
+ if (pair.first != 0 && pair.second + m_sec < sec) {
+ T *ot = pair.first;
+ pair.first = 0;
+ delete ot;
+ ++m_scavenged;
+ }
+ }
+
+ if (sec > m_lastExcess + m_sec) {
+ clearExcess(sec);
+ }
+}
+
+template <typename T>
+void
+Scavenger<T>::pushExcess(T *t)
+{
+ pthread_mutex_lock(&m_excessMutex);
+ m_excess.push_back(t);
+ struct timeval tv;
+ (void)gettimeofday(&tv, 0);
+ m_lastExcess = tv.tv_sec;
+ pthread_mutex_unlock(&m_excessMutex);
+}
+
+template <typename T>
+void
+Scavenger<T>::clearExcess(int sec)
+{
+ pthread_mutex_lock(&m_excessMutex);
+ for (typename ObjectList::iterator i = m_excess.begin();
+ i != m_excess.end(); ++i) {
+ delete *i;
+ }
+ m_excess.clear();
+ m_lastExcess = sec;
+ pthread_mutex_unlock(&m_excessMutex);
+}
+
+}
+
+#endif
diff --git a/src/sound/SequencerDataBlock.cpp b/src/sound/SequencerDataBlock.cpp
new file mode 100644
index 0000000..bc5e80b
--- /dev/null
+++ b/src/sound/SequencerDataBlock.cpp
@@ -0,0 +1,361 @@
+// -*- c-basic-offset: 4 -*-
+
+/*
+ Rosegarden
+ A sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <bownie@bownie.com>
+
+ 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 "SequencerDataBlock.h"
+#include "MappedComposition.h"
+
+namespace Rosegarden
+{
+
+SequencerDataBlock::SequencerDataBlock(bool initialise)
+{
+ if (initialise)
+ clearTemporaries();
+}
+
+bool
+SequencerDataBlock::getVisual(MappedEvent &ev) const
+{
+ static int eventIndex = 0;
+
+ if (!m_haveVisualEvent) {
+ return false;
+ } else {
+ int thisEventIndex = m_visualEventIndex;
+ if (thisEventIndex == eventIndex)
+ return false;
+ ev = *((MappedEvent *) & m_visualEvent);
+ eventIndex = thisEventIndex;
+ return true;
+ }
+}
+
+void
+SequencerDataBlock::setVisual(const MappedEvent *ev)
+{
+ m_haveVisualEvent = false;
+ if (ev) {
+ *((MappedEvent *)&m_visualEvent) = *ev;
+ ++m_visualEventIndex;
+ m_haveVisualEvent = true;
+ }
+}
+
+int
+SequencerDataBlock::getRecordedEvents(MappedComposition &mC) const
+{
+ static int readIndex = -1;
+
+ if (readIndex == -1) {
+ readIndex = m_recordEventIndex;
+ return 0;
+ }
+
+ int currentIndex = m_recordEventIndex;
+ int count = 0;
+
+ MappedEvent *recordBuffer = (MappedEvent *)m_recordBuffer;
+
+ while (readIndex != currentIndex) {
+ mC.insert(new MappedEvent(recordBuffer[readIndex]));
+ if (++readIndex == SEQUENCER_DATABLOCK_RECORD_BUFFER_SIZE)
+ readIndex = 0;
+ ++count;
+ }
+
+ return count;
+}
+
+void
+SequencerDataBlock::addRecordedEvents(MappedComposition *mC)
+{
+ // ringbuffer
+ int index = m_recordEventIndex;
+ MappedEvent *recordBuffer = (MappedEvent *)m_recordBuffer;
+
+ for (MappedComposition::iterator i = mC->begin(); i != mC->end(); ++i) {
+ recordBuffer[index] = **i;
+ if (++index == SEQUENCER_DATABLOCK_RECORD_BUFFER_SIZE)
+ index = 0;
+ }
+
+ m_recordEventIndex = index;
+}
+
+int
+SequencerDataBlock::instrumentToIndex(InstrumentId id) const
+{
+ int i;
+
+ for (i = 0; i < m_knownInstrumentCount; ++i) {
+ if (m_knownInstruments[i] == id)
+ return i;
+ }
+
+ return -1;
+}
+
+int
+SequencerDataBlock::instrumentToIndexCreating(InstrumentId id)
+{
+ int i;
+
+ for (i = 0; i < m_knownInstrumentCount; ++i) {
+ if (m_knownInstruments[i] == id)
+ return i;
+ }
+
+ if (i == SEQUENCER_DATABLOCK_MAX_NB_INSTRUMENTS) {
+ std::cerr << "ERROR: SequencerDataBlock::instrumentToIndexCreating("
+ << id << "): out of instrument index space" << std::endl;
+ return -1;
+ }
+
+ m_knownInstruments[i] = id;
+ ++m_knownInstrumentCount;
+ return i;
+}
+
+bool
+SequencerDataBlock::getInstrumentLevel(InstrumentId id,
+ LevelInfo &info) const
+{
+ static int lastUpdateIndex[SEQUENCER_DATABLOCK_MAX_NB_INSTRUMENTS];
+
+ int index = instrumentToIndex(id);
+ if (index < 0) {
+ info.level = info.levelRight = 0;
+ return false;
+ }
+
+ int currentUpdateIndex = m_levelUpdateIndices[index];
+ info = m_levels[index];
+
+ /*
+ std::cout << "SequencerDataBlock::getInstrumentLevel - "
+ << "id = " << id
+ << ", level = " << info.level << std::endl;
+ */
+
+ if (lastUpdateIndex[index] != currentUpdateIndex) {
+ lastUpdateIndex[index] = currentUpdateIndex;
+ return true;
+ } else {
+ return false; // no change
+ }
+}
+
+bool
+SequencerDataBlock::getInstrumentLevelForMixer(InstrumentId id,
+ LevelInfo &info) const
+{
+ static int lastUpdateIndex[SEQUENCER_DATABLOCK_MAX_NB_INSTRUMENTS];
+
+ int index = instrumentToIndex(id);
+ if (index < 0) {
+ info.level = info.levelRight = 0;
+ return false;
+ }
+
+ int currentUpdateIndex = m_levelUpdateIndices[index];
+ info = m_levels[index];
+
+ if (lastUpdateIndex[index] != currentUpdateIndex) {
+ lastUpdateIndex[index] = currentUpdateIndex;
+ return true;
+ } else {
+ return false; // no change
+ }
+}
+
+void
+SequencerDataBlock::setInstrumentLevel(InstrumentId id, const LevelInfo &info)
+{
+ int index = instrumentToIndexCreating(id);
+ if (index < 0)
+ return ;
+
+ m_levels[index] = info;
+ ++m_levelUpdateIndices[index];
+}
+
+bool
+SequencerDataBlock::getInstrumentRecordLevel(InstrumentId id, LevelInfo &info) const
+{
+ static int lastUpdateIndex[SEQUENCER_DATABLOCK_MAX_NB_INSTRUMENTS];
+
+ int index = instrumentToIndex(id);
+ if (index < 0) {
+ info.level = info.levelRight = 0;
+ return false;
+ }
+
+ int currentUpdateIndex = m_recordLevelUpdateIndices[index];
+ info = m_recordLevels[index];
+
+ if (lastUpdateIndex[index] != currentUpdateIndex) {
+ lastUpdateIndex[index] = currentUpdateIndex;
+ return true;
+ } else {
+ return false; // no change
+ }
+}
+
+bool
+SequencerDataBlock::getInstrumentRecordLevelForMixer(InstrumentId id, LevelInfo &info) const
+{
+ static int lastUpdateIndex[SEQUENCER_DATABLOCK_MAX_NB_INSTRUMENTS];
+
+ int index = instrumentToIndex(id);
+ if (index < 0) {
+ info.level = info.levelRight = 0;
+ return false;
+ }
+
+ int currentUpdateIndex = m_recordLevelUpdateIndices[index];
+ info = m_recordLevels[index];
+
+ if (lastUpdateIndex[index] != currentUpdateIndex) {
+ lastUpdateIndex[index] = currentUpdateIndex;
+ return true;
+ } else {
+ return false; // no change
+ }
+}
+
+void
+SequencerDataBlock::setInstrumentRecordLevel(InstrumentId id, const LevelInfo &info)
+{
+ int index = instrumentToIndexCreating(id);
+ if (index < 0)
+ return ;
+
+ m_recordLevels[index] = info;
+ ++m_recordLevelUpdateIndices[index];
+}
+
+void
+SequencerDataBlock::setTrackLevel(TrackId id, const LevelInfo &info)
+{
+ if (m_controlBlock) {
+ setInstrumentLevel(m_controlBlock->getInstrumentForTrack(id), info);
+ }
+}
+
+bool
+SequencerDataBlock::getTrackLevel(TrackId id, LevelInfo &info) const
+{
+ info.level = info.levelRight = 0;
+
+ if (m_controlBlock) {
+ return getInstrumentLevel(m_controlBlock->getInstrumentForTrack(id),
+ info);
+ }
+
+ return false;
+}
+
+bool
+SequencerDataBlock::getSubmasterLevel(int submaster, LevelInfo &info) const
+{
+ static int lastUpdateIndex[SEQUENCER_DATABLOCK_MAX_NB_SUBMASTERS];
+
+ if (submaster < 0 || submaster > SEQUENCER_DATABLOCK_MAX_NB_SUBMASTERS) {
+ info.level = info.levelRight = 0;
+ return false;
+ }
+
+ int currentUpdateIndex = m_submasterLevelUpdateIndices[submaster];
+ info = m_submasterLevels[submaster];
+
+ if (lastUpdateIndex[submaster] != currentUpdateIndex) {
+ lastUpdateIndex[submaster] = currentUpdateIndex;
+ return true;
+ } else {
+ return false; // no change
+ }
+}
+
+void
+SequencerDataBlock::setSubmasterLevel(int submaster, const LevelInfo &info)
+{
+ if (submaster < 0 || submaster > SEQUENCER_DATABLOCK_MAX_NB_SUBMASTERS) {
+ return ;
+ }
+
+ m_submasterLevels[submaster] = info;
+ ++m_submasterLevelUpdateIndices[submaster];
+}
+
+bool
+SequencerDataBlock::getMasterLevel(LevelInfo &level) const
+{
+ static int lastUpdateIndex = 0;
+
+ int currentIndex = m_masterLevelUpdateIndex;
+ level = m_masterLevel;
+
+ if (lastUpdateIndex != currentIndex) {
+ lastUpdateIndex = currentIndex;
+ return true;
+ } else {
+ return false;
+ }
+}
+
+void
+SequencerDataBlock::setMasterLevel(const LevelInfo &info)
+{
+ m_masterLevel = info;
+ ++m_masterLevelUpdateIndex;
+}
+
+void
+SequencerDataBlock::clearTemporaries()
+{
+ m_controlBlock = 0;
+ m_positionSec = 0;
+ m_positionNsec = 0;
+ m_visualEventIndex = 0;
+ *((MappedEvent *)&m_visualEvent) = MappedEvent();
+ m_haveVisualEvent = false;
+ m_recordEventIndex = 0;
+ //!!! m_recordLevel.level = 0;
+ //!!! m_recordLevel.levelRight = 0;
+ memset(m_knownInstruments, 0,
+ SEQUENCER_DATABLOCK_MAX_NB_INSTRUMENTS * sizeof(InstrumentId));
+ m_knownInstrumentCount = 0;
+ memset(m_levelUpdateIndices, 0,
+ SEQUENCER_DATABLOCK_MAX_NB_INSTRUMENTS * sizeof(int));
+ memset(m_levels, 0,
+ SEQUENCER_DATABLOCK_MAX_NB_INSTRUMENTS * sizeof(LevelInfo));
+ memset(m_submasterLevelUpdateIndices, 0,
+ SEQUENCER_DATABLOCK_MAX_NB_SUBMASTERS * sizeof(int));
+ memset(m_submasterLevels, 0,
+ SEQUENCER_DATABLOCK_MAX_NB_SUBMASTERS * sizeof(LevelInfo));
+ m_masterLevelUpdateIndex = 0;
+ m_masterLevel.level = 0;
+ m_masterLevel.levelRight = 0;
+
+}
+
+}
+
diff --git a/src/sound/SequencerDataBlock.h b/src/sound/SequencerDataBlock.h
new file mode 100644
index 0000000..2cfdefe
--- /dev/null
+++ b/src/sound/SequencerDataBlock.h
@@ -0,0 +1,140 @@
+// -*- c-basic-offset: 4 -*-
+
+/*
+ Rosegarden
+ A sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <bownie@bownie.com>
+
+ 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 _SEQUENCER_DATA_BLOCK_H_
+#define _SEQUENCER_DATA_BLOCK_H_
+
+#include "ControlBlock.h"
+#include "RealTime.h"
+#include "MappedEvent.h"
+
+namespace Rosegarden
+{
+
+/**
+ * ONLY PUT PLAIN DATA HERE - NO POINTERS EVER
+ * (and this struct mustn't have a constructor)
+ */
+struct LevelInfo
+{
+ int level;
+ int levelRight; // if stereo audio
+};
+
+class MappedComposition;
+
+
+#define SEQUENCER_DATABLOCK_MAX_NB_INSTRUMENTS 512 // can't be a symbol
+#define SEQUENCER_DATABLOCK_MAX_NB_SUBMASTERS 64 // can't be a symbol
+#define SEQUENCER_DATABLOCK_RECORD_BUFFER_SIZE 1024 // MIDI events
+
+class SequencerDataBlock
+{
+public:
+ /**
+ * Constructor only initialises memory if initialise is true
+ */
+ SequencerDataBlock(bool initialise);
+
+ RealTime getPositionPointer() const {
+ return RealTime(m_positionSec, m_positionNsec);
+ }
+ void setPositionPointer(const RealTime &rt) {
+ m_positionSec = rt.sec;
+ m_positionNsec = rt.nsec;
+ }
+
+ bool getVisual(MappedEvent &ev) const;
+ void setVisual(const MappedEvent *ev);
+
+ int getRecordedEvents(MappedComposition &) const;
+ void addRecordedEvents(MappedComposition *);
+
+ bool getTrackLevel(TrackId track, LevelInfo &) const;
+ void setTrackLevel(TrackId track, const LevelInfo &);
+
+ // Two of these to rather hamfistedly get around the fact
+ // we need to fetch this value twice - once from IPB,
+ // and again for the Mixer.
+ //
+ bool getInstrumentLevel(InstrumentId id, LevelInfo &) const;
+ bool getInstrumentLevelForMixer(InstrumentId id, LevelInfo &) const;
+
+ void setInstrumentLevel(InstrumentId id, const LevelInfo &);
+
+ bool getInstrumentRecordLevel(InstrumentId id, LevelInfo &) const;
+ bool getInstrumentRecordLevelForMixer(InstrumentId id, LevelInfo &) const;
+
+ void setInstrumentRecordLevel(InstrumentId id, const LevelInfo &);
+
+ bool getSubmasterLevel(int submaster, LevelInfo &) const;
+ void setSubmasterLevel(int submaster, const LevelInfo &);
+
+ bool getMasterLevel(LevelInfo &) const;
+ void setMasterLevel(const LevelInfo &);
+
+ void setControlBlock(ControlBlock *cb) { m_controlBlock = cb; }
+ ControlBlock *getControlBlock() { return m_controlBlock; }
+
+ // Reset the temporaries on (for example) GUI restart
+ //
+ void clearTemporaries();
+
+protected:
+ int instrumentToIndex(InstrumentId id) const;
+ int instrumentToIndexCreating(InstrumentId id);
+ ControlBlock *m_controlBlock;
+
+ // Two ints rather than a RealTime, as the RealTime default ctor
+ // initialises the space & so can't be used from the GUI's
+ // placement-new ctor (which has no write access and doesn't want
+ // it anyway). Likewise we use char[] instead of MappedEvents
+
+ int m_positionSec;
+ int m_positionNsec;
+
+ int m_visualEventIndex;
+ bool m_haveVisualEvent;
+ char m_visualEvent[sizeof(MappedEvent)];
+
+ int m_recordEventIndex;
+ char m_recordBuffer[sizeof(MappedEvent) *
+ SEQUENCER_DATABLOCK_RECORD_BUFFER_SIZE];
+
+ InstrumentId m_knownInstruments[SEQUENCER_DATABLOCK_MAX_NB_INSTRUMENTS];
+ int m_knownInstrumentCount;
+
+ int m_levelUpdateIndices[SEQUENCER_DATABLOCK_MAX_NB_INSTRUMENTS];
+ LevelInfo m_levels[SEQUENCER_DATABLOCK_MAX_NB_INSTRUMENTS];
+
+ int m_recordLevelUpdateIndices[SEQUENCER_DATABLOCK_MAX_NB_INSTRUMENTS];
+ LevelInfo m_recordLevels[SEQUENCER_DATABLOCK_MAX_NB_INSTRUMENTS];
+
+ int m_submasterLevelUpdateIndices[SEQUENCER_DATABLOCK_MAX_NB_SUBMASTERS];
+ LevelInfo m_submasterLevels[SEQUENCER_DATABLOCK_MAX_NB_SUBMASTERS];
+
+ int m_masterLevelUpdateIndex;
+ LevelInfo m_masterLevel;
+};
+
+}
+
+#endif
diff --git a/src/sound/SoundDriver.cpp b/src/sound/SoundDriver.cpp
new file mode 100644
index 0000000..aab641c
--- /dev/null
+++ b/src/sound/SoundDriver.cpp
@@ -0,0 +1,391 @@
+// -*- c-indentation-style:"stroustrup" c-basic-offset: 4 -*-
+/*
+ Rosegarden
+ A sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <bownie@bownie.com>
+
+ 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 <stdlib.h>
+
+#include "SoundDriver.h"
+#include "WAVAudioFile.h"
+#include "MappedStudio.h"
+#include "AudioPlayQueue.h"
+
+#include <unistd.h>
+#include <sys/time.h>
+#include <pthread.h> // for mutex
+
+//#define DEBUG_SOUND_DRIVER 1
+
+namespace Rosegarden
+{
+
+// ---------- SoundDriver -----------
+//
+
+
+SoundDriver::SoundDriver(MappedStudio *studio, const std::string &name):
+ m_name(name),
+ m_driverStatus(NO_DRIVER),
+ m_playStartPosition(0, 0),
+ m_startPlayback(false),
+ m_playing(false),
+ m_midiRecordDevice(0),
+ m_recordStatus(RECORD_OFF),
+ m_midiRunningId(MidiInstrumentBase),
+ m_audioRunningId(AudioInstrumentBase),
+ // m_audioQueueScavenger(4, 50),
+ m_audioQueue(0),
+ m_lowLatencyMode(true),
+ m_audioRecFileFormat(RIFFAudioFile::FLOAT),
+ m_studio(studio),
+ m_sequencerDataBlock(0),
+ m_externalTransport(0),
+ m_mmcStatus(TRANSPORT_OFF),
+ m_mtcStatus(TRANSPORT_OFF),
+ m_mmcId(0), // default MMC id of 0
+ m_midiClockEnabled(false),
+ m_midiClockInterval(0, 0),
+ m_midiClockSendTime(RealTime::zeroTime),
+ m_midiSongPositionPointer(0)
+{
+ m_audioQueue = new AudioPlayQueue();
+}
+
+
+SoundDriver::~SoundDriver()
+{
+ std::cout << "SoundDriver::~SoundDriver (exiting)" << std::endl;
+ delete m_audioQueue;
+}
+
+MappedInstrument*
+SoundDriver::getMappedInstrument(InstrumentId id)
+{
+ std::vector<MappedInstrument*>::const_iterator it;
+
+ for (it = m_instruments.begin(); it != m_instruments.end(); it++) {
+ if ((*it)->getId() == id)
+ return (*it);
+ }
+
+ return 0;
+}
+
+void
+SoundDriver::initialiseAudioQueue(const std::vector<MappedEvent> &events)
+{
+ AudioPlayQueue *newQueue = new AudioPlayQueue();
+
+ for (std::vector<MappedEvent>::const_iterator i = events.begin();
+ i != events.end(); ++i) {
+
+ // Check for existence of file - if the sequencer has died
+ // and been restarted then we're not always loaded up with
+ // the audio file references we should have. In the future
+ // we could make this just get the gui to reload our files
+ // when (or before) this fails.
+ //
+ AudioFile *audioFile = getAudioFile(i->getAudioID());
+
+ if (audioFile) {
+ MappedAudioFader *fader =
+ dynamic_cast<MappedAudioFader*>
+ (getMappedStudio()->getAudioFader(i->getInstrument()));
+
+ if (!fader) {
+ std::cerr << "WARNING: SoundDriver::initialiseAudioQueue: no fader for audio instrument " << i->getInstrument() << std::endl;
+ continue;
+ }
+
+ unsigned int channels = fader->getPropertyList(
+ MappedAudioFader::Channels)[0].toInt();
+
+ //#define DEBUG_PLAYING_AUDIO
+#ifdef DEBUG_PLAYING_AUDIO
+
+ std::cout << "Creating playable audio file: id " << audioFile->getId() << ", event time " << i->getEventTime() << ", time now " << getSequencerTime() << ", start marker " << i->getAudioStartMarker() << ", duration " << i->getDuration() << ", instrument " << i->getInstrument() << " channels " << channels << std::endl;
+#endif
+
+ RealTime bufferLength = getAudioReadBufferLength();
+ int bufferFrames = RealTime::realTime2Frame
+ (bufferLength, getSampleRate());
+
+ PlayableAudioFile *paf = 0;
+
+ try {
+ paf = new PlayableAudioFile(i->getInstrument(),
+ audioFile,
+ i->getEventTime(),
+ i->getAudioStartMarker(),
+ i->getDuration(),
+ bufferFrames,
+ getSmallFileSize() * 1024,
+ channels,
+ getSampleRate());
+ } catch (...) {
+ continue;
+ }
+
+ paf->setRuntimeSegmentId(i->getRuntimeSegmentId());
+
+ if (i->isAutoFading()) {
+ paf->setAutoFade(true);
+ paf->setFadeInTime(i->getFadeInTime());
+ paf->setFadeOutTime(i->getFadeInTime());
+
+ //#define DEBUG_AUTOFADING
+#ifdef DEBUG_AUTOFADING
+
+ std::cout << "SoundDriver::initialiseAudioQueue - "
+ << "PlayableAudioFile is AUTOFADING - "
+ << "in = " << i->getFadeInTime()
+ << ", out = " << i->getFadeOutTime()
+ << std::endl;
+#endif
+
+ }
+#ifdef DEBUG_AUTOFADING
+ else {
+ std::cout << "PlayableAudioFile has no AUTOFADE"
+ << std::endl;
+ }
+#endif
+
+ newQueue->addScheduled(paf);
+ } else {
+ std::cerr << "SoundDriver::initialiseAudioQueue - "
+ << "can't find audio file reference for id " << i->getAudioID()
+ << std::endl;
+
+ std::cerr << "SoundDriver::initialiseAudioQueue - "
+ << "try reloading the current Rosegarden file"
+ << std::endl;
+ }
+ }
+
+ std::cout << "SoundDriver::initialiseAudioQueue -- new queue has "
+ << newQueue->size() << " files"
+ << std::endl;
+
+ if (newQueue->empty()) {
+ if (m_audioQueue->empty()) {
+ delete newQueue;
+ return ;
+ }
+ }
+
+ AudioPlayQueue *oldQueue = m_audioQueue;
+ m_audioQueue = newQueue;
+ if (oldQueue)
+ m_audioQueueScavenger.claim(oldQueue);
+}
+
+void
+SoundDriver::clearAudioQueue()
+{
+ std::cout << "SoundDriver::clearAudioQueue" << std::endl;
+
+ if (m_audioQueue->empty())
+ return ;
+
+ AudioPlayQueue *newQueue = new AudioPlayQueue();
+ AudioPlayQueue *oldQueue = m_audioQueue;
+ m_audioQueue = newQueue;
+ if (oldQueue)
+ m_audioQueueScavenger.claim(oldQueue);
+}
+void
+SoundDriver::cancelAudioFile(MappedEvent *mE)
+{
+ std::cout << "SoundDriver::cancelAudioFile" << std::endl;
+
+ if (!m_audioQueue)
+ return ;
+
+ // For now we only permit cancelling unscheduled files.
+
+ const AudioPlayQueue::FileList &files = m_audioQueue->getAllUnscheduledFiles();
+ for (AudioPlayQueue::FileList::const_iterator fi = files.begin();
+ fi != files.end(); ++fi) {
+ PlayableAudioFile *file = *fi;
+ if (mE->getRuntimeSegmentId() == -1) {
+
+ // ERROR? The comparison between file->getAudioFile()->getId() of type unsigned int
+ // and mE->getAudioID() of type int.
+ if (file->getInstrument() == mE->getInstrument() &&
+ int(file->getAudioFile()->getId() == mE->getAudioID())) {
+ file->cancel();
+ }
+ } else {
+ if (file->getRuntimeSegmentId() == mE->getRuntimeSegmentId() &&
+ file->getStartTime() == mE->getEventTime()) {
+ file->cancel();
+ }
+ }
+ }
+}
+
+const AudioPlayQueue *
+SoundDriver::getAudioQueue() const
+{
+ return m_audioQueue;
+}
+
+
+void
+SoundDriver::setMappedInstrument(MappedInstrument *mI)
+{
+ std::vector<MappedInstrument*>::iterator it;
+
+ // If we match then change existing entry
+ for (it = m_instruments.begin(); it != m_instruments.end(); it++) {
+ if ((*it)->getId() == mI->getId()) {
+ (*it)->setChannel(mI->getChannel());
+ (*it)->setType(mI->getType());
+ delete mI;
+ return ;
+ }
+ }
+
+ // else create a new one
+ m_instruments.push_back(mI);
+
+ std::cout << "SoundDriver: setMappedInstrument() : "
+ << "type = " << mI->getType() << " : "
+ << "channel = " << (int)(mI->getChannel()) << " : "
+ << "id = " << mI->getId() << std::endl;
+
+}
+
+unsigned int
+SoundDriver::getDevices()
+{
+ return m_devices.size();
+}
+
+MappedDevice
+SoundDriver::getMappedDevice(DeviceId id)
+{
+ MappedDevice retDevice;
+ std::vector<MappedInstrument*>::iterator it;
+
+ std::vector<MappedDevice*>::iterator dIt = m_devices.begin();
+ for (; dIt != m_devices.end(); dIt++) {
+ if ((*dIt)->getId() == id)
+ retDevice = **dIt;
+ }
+
+ // If we match then change existing entry
+ for (it = m_instruments.begin(); it != m_instruments.end(); it++) {
+ if ((*it)->getDevice() == id)
+ retDevice.push_back(*it);
+ }
+
+#ifdef DEBUG_SOUND_DRIVER
+ std::cout << "SoundDriver::getMappedDevice(" << id << ") - "
+ << "name = \"" << retDevice.getName()
+ << "\" type = " << retDevice.getType()
+ << " direction = " << retDevice.getDirection()
+ << " connection = \"" << retDevice.getConnection() << "\""
+ << " recording = " << retDevice.isRecording()
+ << std::endl;
+#endif
+
+ return retDevice;
+}
+
+
+
+bool
+SoundDriver::addAudioFile(const std::string &fileName, unsigned int id)
+{
+ AudioFile *ins = 0;
+
+ try {
+ ins = new WAVAudioFile(id, fileName, fileName);
+ ins->open();
+ m_audioFiles.push_back(ins);
+
+ // std::cout << "Sequencer::addAudioFile() = \"" << fileName << "\"" << std::endl;
+
+ return true;
+
+ } catch (SoundFile::BadSoundFileException e) {
+ std::cerr << "SoundDriver::addAudioFile: Failed to add audio file " << fileName << ": " << e.getMessage() << std::endl;
+ delete ins;
+ return false;
+ }
+}
+
+bool
+SoundDriver::removeAudioFile(unsigned int id)
+{
+ std::vector<AudioFile*>::iterator it;
+ for (it = m_audioFiles.begin(); it != m_audioFiles.end(); it++) {
+ if ((*it)->getId() == id) {
+ std::cout << "Sequencer::removeAudioFile() = \"" <<
+ (*it)->getFilename() << "\"" << std::endl;
+
+ delete (*it);
+ m_audioFiles.erase(it);
+ return true;
+ }
+ }
+
+ return false;
+}
+
+AudioFile*
+SoundDriver::getAudioFile(unsigned int id)
+{
+ std::vector<AudioFile*>::iterator it;
+ for (it = m_audioFiles.begin(); it != m_audioFiles.end(); it++) {
+ if ((*it)->getId() == id)
+ return *it;
+ }
+
+ return 0;
+}
+
+void
+SoundDriver::clearAudioFiles()
+{
+ // std::cout << "SoundDriver::clearAudioFiles() - clearing down audio files"
+ // << std::endl;
+
+ std::vector<AudioFile*>::iterator it;
+ for (it = m_audioFiles.begin(); it != m_audioFiles.end(); it++)
+ delete(*it);
+
+ m_audioFiles.erase(m_audioFiles.begin(), m_audioFiles.end());
+}
+
+void
+SoundDriver::sleep(const RealTime &rt)
+{
+ // The usleep man page says it's deprecated and we should use
+ // nanosleep. And that's what we did. But it seems quite a few
+ // people don't have nanosleep, so we're reverting to usleep.
+
+ unsigned long usec = rt.sec * 1000000 + rt.usec();
+ usleep(usec);
+}
+
+
+}
+
diff --git a/src/sound/SoundDriver.h b/src/sound/SoundDriver.h
new file mode 100644
index 0000000..fabbaef
--- /dev/null
+++ b/src/sound/SoundDriver.h
@@ -0,0 +1,529 @@
+// -*- c-indentation-style:"stroustrup" c-basic-offset: 4 -*-
+/*
+ Rosegarden
+ A sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <bownie@bownie.com>
+
+ 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 <string>
+#include <vector>
+#include <list>
+#include <qstringlist.h>
+
+#include "Device.h"
+#include "MappedComposition.h"
+#include "MappedInstrument.h"
+#include "MappedDevice.h"
+#include "SequencerDataBlock.h"
+#include "PlayableAudioFile.h"
+#include "Scavenger.h"
+#include "RIFFAudioFile.h" // for SubFormat enum
+
+// Abstract base to support SoundDrivers, such as ALSA.
+//
+// This base class provides the generic driver support for
+// these drivers with the Sequencer class owning an instance
+// of a sub class of this class and directing it and required
+// by the rosegardensequencer itself.
+//
+//
+
+#ifndef _SOUNDDRIVER_H_
+#define _SOUNDDRIVER_H_
+
+namespace Rosegarden
+{
+
+// Current recording status - whether we're monitoring anything
+// or recording.
+//
+typedef enum
+{
+ RECORD_OFF,
+ RECORD_ON,
+} RecordStatus;
+
+
+// Status of a SoundDriver - whether we're got an audio and
+// MIDI subsystem or not. This is reported right up to the
+// gui.
+//
+typedef enum
+{
+ NO_DRIVER = 0x00, // Nothing's OK
+ AUDIO_OK = 0x01, // AUDIO's OK
+ MIDI_OK = 0x02, // MIDI's OK
+ VERSION_OK = 0x04 // GUI and sequencer versions match
+} SoundDriverStatus;
+
+
+// Used for MMC and MTC, not for JACK transport
+//
+typedef enum
+{
+ TRANSPORT_OFF,
+ TRANSPORT_MASTER,
+ TRANSPORT_SLAVE
+} TransportSyncStatus;
+
+
+// The NoteOffQueue holds a time ordered set of
+// pending MIDI NOTE OFF events.
+//
+class NoteOffEvent
+{
+public:
+ NoteOffEvent() {;}
+ NoteOffEvent(const RealTime &realTime,
+ unsigned int pitch,
+ MidiByte channel,
+ InstrumentId instrument):
+ m_realTime(realTime),
+ m_pitch(pitch),
+ m_channel(channel),
+ m_instrument(instrument) {;}
+ ~NoteOffEvent() {;}
+
+ struct NoteOffEventCmp
+ {
+ bool operator()(NoteOffEvent *nO1, NoteOffEvent *nO2)
+ {
+ return nO1->getRealTime() < nO2->getRealTime();
+ }
+ };
+
+ void setRealTime(const RealTime &time) { m_realTime = time; }
+ RealTime getRealTime() const { return m_realTime; }
+
+ MidiByte getPitch() const { return m_pitch; }
+ MidiByte getChannel() const { return m_channel; }
+ InstrumentId getInstrument() const { return m_instrument; }
+
+private:
+ RealTime m_realTime;
+ MidiByte m_pitch;
+ MidiByte m_channel;
+ InstrumentId m_instrument;
+
+};
+
+
+// The queue itself
+//
+class NoteOffQueue : public std::multiset<NoteOffEvent *,
+ NoteOffEvent::NoteOffEventCmp>
+{
+public:
+ NoteOffQueue() {;}
+ ~NoteOffQueue() {;}
+private:
+};
+
+
+class MappedStudio;
+class ExternalTransport;
+class AudioPlayQueue;
+
+typedef std::vector<PlayableAudioFile *> PlayableAudioFileList;
+
+// The abstract SoundDriver
+//
+//
+class SoundDriver
+{
+public:
+ SoundDriver(MappedStudio *studio, const std::string &name);
+ virtual ~SoundDriver();
+
+ virtual bool initialise() = 0;
+ virtual void shutdown() { }
+
+ virtual void initialisePlayback(const RealTime &position) = 0;
+ virtual void stopPlayback() = 0;
+ virtual void punchOut() = 0; // stop recording, continue playing
+ virtual void resetPlayback(const RealTime &oldPosition, const RealTime &position) = 0;
+ virtual void allNotesOff() = 0;
+
+ virtual RealTime getSequencerTime() = 0;
+
+ virtual MappedComposition *getMappedComposition() = 0;
+
+ virtual void startClocks() { }
+ virtual void stopClocks() { }
+
+ // Process some asynchronous events
+ //
+ virtual void processEventsOut(const MappedComposition &mC) = 0;
+
+ // Process some scheduled events on the output queue. The
+ // slice times are here so that the driver can interleave
+ // note-off events as appropriate.
+ //
+ virtual void processEventsOut(const MappedComposition &mC,
+ const RealTime &sliceStart,
+ const RealTime &sliceEnd) = 0;
+
+ // Activate a recording state. armedInstruments and audioFileNames
+ // can be NULL if no audio tracks recording.
+ //
+ virtual bool record(RecordStatus recordStatus,
+ const std::vector<InstrumentId> *armedInstruments = 0,
+ const std::vector<QString> *audioFileNames = 0) = 0;
+
+ // Process anything that's pending
+ //
+ virtual void processPending() = 0;
+
+ // Get the driver's operating sample rate
+ //
+ virtual unsigned int getSampleRate() const = 0;
+
+ // Plugin instance management
+ //
+ virtual void setPluginInstance(InstrumentId id,
+ QString identifier,
+ int position) = 0;
+
+ virtual void removePluginInstance(InstrumentId id,
+ int position) = 0;
+
+ // Clear down and remove all plugin instances
+ //
+ virtual void removePluginInstances() = 0;
+
+ virtual void setPluginInstancePortValue(InstrumentId id,
+ int position,
+ unsigned long portNumber,
+ float value) = 0;
+
+ virtual float getPluginInstancePortValue(InstrumentId id,
+ int position,
+ unsigned long portNumber) = 0;
+
+ virtual void setPluginInstanceBypass(InstrumentId id,
+ int position,
+ bool value) = 0;
+
+ virtual QStringList getPluginInstancePrograms(InstrumentId id,
+ int position) = 0;
+
+ virtual QString getPluginInstanceProgram(InstrumentId id,
+ int position) = 0;
+
+ virtual QString getPluginInstanceProgram(InstrumentId id,
+ int position,
+ int bank,
+ int program) = 0;
+
+ virtual unsigned long getPluginInstanceProgram(InstrumentId id,
+ int position,
+ QString name) = 0;
+
+ virtual void setPluginInstanceProgram(InstrumentId id,
+ int position,
+ QString program) = 0;
+
+ virtual QString configurePlugin(InstrumentId id,
+ int position,
+ QString key,
+ QString value) = 0;
+
+ virtual void setAudioBussLevels(int bussId,
+ float dB,
+ float pan) = 0;
+
+ virtual void setAudioInstrumentLevels(InstrumentId id,
+ float dB,
+ float pan) = 0;
+
+ // Poll for new clients (for new Devices/Instruments)
+ //
+ virtual bool checkForNewClients() = 0;
+
+ // Set a loop position at the driver (used for transport)
+ //
+ virtual void setLoop(const RealTime &loopStart, const RealTime &loopEnd)
+ = 0;
+
+ virtual void sleep(const RealTime &rt);
+
+ virtual QString getStatusLog() { return ""; }
+
+ // Mapped Instruments
+ //
+ void setMappedInstrument(MappedInstrument *mI);
+ MappedInstrument* getMappedInstrument(InstrumentId id);
+
+ // Return the current status of the driver
+ //
+ unsigned int getStatus() const { return m_driverStatus; }
+
+ // Are we playing?
+ //
+ bool isPlaying() const { return m_playing; }
+
+ // Are we counting? By default a subclass probably wants to
+ // return true, if it doesn't know better.
+ //
+ virtual bool areClocksRunning() const = 0;
+
+ RealTime getStartPosition() const { return m_playStartPosition; }
+ RecordStatus getRecordStatus() const { return m_recordStatus; }
+
+ // Return a MappedDevice full of the Instrument mappings
+ // that the driver has discovered. The gui can then use
+ // this list (complete with names) to generate its proper
+ // Instruments under the MidiDevice and AudioDevice.
+ //
+ MappedDevice getMappedDevice(DeviceId id);
+
+ // Return the number of devices we've found
+ //
+ unsigned int getDevices();
+
+ virtual bool canReconnect(Device::DeviceType) { return false; }
+
+ virtual DeviceId addDevice(Device::DeviceType,
+ MidiDevice::DeviceDirection) {
+ return Device::NO_DEVICE;
+ }
+ virtual void removeDevice(DeviceId) { }
+ virtual void renameDevice(DeviceId, QString) { }
+
+ virtual unsigned int getConnections(Device::DeviceType,
+ MidiDevice::DeviceDirection) { return 0; }
+ virtual QString getConnection(Device::DeviceType,
+ MidiDevice::DeviceDirection,
+ unsigned int) { return ""; }
+ virtual void setConnection(DeviceId, QString) { }
+ virtual void setPlausibleConnection(DeviceId id, QString c) { setConnection(id, c); }
+
+ virtual unsigned int getTimers() { return 0; }
+ virtual QString getTimer(unsigned int) { return ""; }
+ virtual QString getCurrentTimer() { return ""; }
+ virtual void setCurrentTimer(QString) { }
+
+ virtual void getAudioInstrumentNumbers(InstrumentId &, int &) = 0;
+ virtual void getSoftSynthInstrumentNumbers(InstrumentId &, int &) = 0;
+
+ // Plugin management -- SoundDrivers should maintain a plugin
+ // scavenger which the audio process code can use for defunct
+ // plugins. Ownership of plugin is passed to the SoundDriver.
+ //
+ virtual void claimUnwantedPlugin(void *plugin) = 0;
+
+ // This causes all scavenged plugins to be destroyed. It
+ // should only be called in non-RT contexts.
+ //
+ virtual void scavengePlugins() = 0;
+
+ // Handle audio file references
+ //
+ void clearAudioFiles();
+ bool addAudioFile(const std::string &fileName, unsigned int id);
+ bool removeAudioFile(unsigned int id);
+
+ void initialiseAudioQueue(const std::vector<MappedEvent> &audioEvents);
+ void clearAudioQueue();
+ const AudioPlayQueue *getAudioQueue() const;
+
+ RIFFAudioFile::SubFormat getAudioRecFileFormat() const { return m_audioRecFileFormat; }
+
+
+ // Latencies
+ //
+ virtual RealTime getAudioPlayLatency() { return RealTime::zeroTime; }
+ virtual RealTime getAudioRecordLatency() { return RealTime::zeroTime; }
+ virtual RealTime getInstrumentPlayLatency(InstrumentId) { return RealTime::zeroTime; }
+ virtual RealTime getMaximumPlayLatency() { return RealTime::zeroTime; }
+
+ // Buffer sizes
+ //
+ void setAudioBufferSizes(RealTime mix, RealTime read, RealTime write,
+ int smallFileSize) {
+ m_audioMixBufferLength = mix;
+ m_audioReadBufferLength = read;
+ m_audioWriteBufferLength = write;
+ m_smallFileSize = smallFileSize;
+ }
+
+ RealTime getAudioMixBufferLength() { return m_audioMixBufferLength; }
+ RealTime getAudioReadBufferLength() { return m_audioReadBufferLength; }
+ RealTime getAudioWriteBufferLength() { return m_audioWriteBufferLength; }
+ int getSmallFileSize() { return m_smallFileSize; }
+
+ void setLowLatencyMode(bool ll) { m_lowLatencyMode = ll; }
+ bool getLowLatencyMode() const { return m_lowLatencyMode; }
+
+ // Cancel the playback of an audio file - either by instrument and audio file id
+ // or by audio segment id.
+ //
+ void cancelAudioFile(MappedEvent *mE);
+
+ // Studio linkage
+ //
+ MappedStudio* getMappedStudio() { return m_studio; }
+ void setMappedStudio(MappedStudio *studio) { m_studio = studio; }
+
+ // Modify MIDI record device
+ //
+ void setMidiRecordDevice(DeviceId id) { m_midiRecordDevice = id; }
+ DeviceId getMIDIRecordDevice() const { return m_midiRecordDevice; }
+
+ // MIDI Realtime Sync setting
+ //
+ TransportSyncStatus getMIDISyncStatus() const { return m_midiSyncStatus; }
+ void setMIDISyncStatus(TransportSyncStatus status) { m_midiSyncStatus = status; }
+
+ // MMC master/slave setting
+ //
+ TransportSyncStatus getMMCStatus() const { return m_mmcStatus; }
+ void setMMCStatus(TransportSyncStatus status) { m_mmcStatus = status; }
+
+ // MTC master/slave setting
+ //
+ TransportSyncStatus getMTCStatus() const { return m_mtcStatus; }
+ void setMTCStatus(TransportSyncStatus status) { m_mtcStatus = status; }
+
+ // MMC Id
+ //
+ int getMMCId() const { return ((int)(m_mmcId)); }
+ void setMMCId(int id) { m_mmcId = (MidiByte)(id); }
+
+ // Set MIDI clock interval - allow redefinition above to ensure
+ // we handle this reset correctly.
+ //
+ virtual void setMIDIClockInterval(RealTime interval)
+ { m_midiClockInterval = interval; }
+
+ // Get and set the mapper which may optionally be used to
+ // store recording levels etc for communication back to the GUI.
+ // (If a subclass wants this and finds it's not there, it should
+ // simply continue without.)
+ //
+ SequencerDataBlock *getSequencerDataBlock() { return m_sequencerDataBlock; }
+ void setSequencerDataBlock(SequencerDataBlock *d) { m_sequencerDataBlock = d; }
+
+ ExternalTransport *getExternalTransportControl() const {
+ return m_externalTransport;
+ }
+ void setExternalTransportControl(ExternalTransport *transport) {
+ m_externalTransport = transport;
+ }
+
+ // Do any bits and bobs of work that need to be done continuously
+ // (this is called repeatedly whether playing or not).
+ //
+ virtual void runTasks() { }
+
+ // Report a failure back to the GUI - ideally. Default does nothing.
+ //
+ virtual void reportFailure(MappedEvent::FailureCode) { }
+
+protected:
+ // Helper functions to be implemented by subclasses
+ //
+ virtual void processMidiOut(const MappedComposition &mC,
+ const RealTime &sliceStart,
+ const RealTime &sliceEnd) = 0;
+ virtual void generateInstruments() = 0;
+
+ // Audio
+ //
+ AudioFile* getAudioFile(unsigned int id);
+
+ std::string m_name;
+ unsigned int m_driverStatus;
+ RealTime m_playStartPosition;
+ bool m_startPlayback;
+ bool m_playing;
+
+ // MIDI Note-off handling
+ //
+ NoteOffQueue m_noteOffQueue;
+
+ // This is our driver's own list of MappedInstruments and MappedDevices.
+ // These are uncoupled at this level - the Instruments and Devices float
+ // free and only index each other - the Devices hold information only like
+ // name, id and if the device is duplex capable.
+ //
+ typedef std::vector<MappedInstrument*> MappedInstrumentList;
+ MappedInstrumentList m_instruments;
+
+ typedef std::vector<MappedDevice*> MappedDeviceList;
+ MappedDeviceList m_devices;
+
+ DeviceId m_midiRecordDevice;
+
+ MappedComposition m_recordComposition;
+ MappedComposition m_returnComposition;
+ RecordStatus m_recordStatus;
+
+
+ InstrumentId m_midiRunningId;
+ InstrumentId m_audioRunningId;
+
+ // Subclass _MUST_ scavenge this regularly:
+ Scavenger<AudioPlayQueue> m_audioQueueScavenger;
+ AudioPlayQueue *m_audioQueue;
+
+ // A list of AudioFiles that we can play.
+ //
+ std::vector<AudioFile*> m_audioFiles;
+
+ RealTime m_audioMixBufferLength;
+ RealTime m_audioReadBufferLength;
+ RealTime m_audioWriteBufferLength;
+ int m_smallFileSize;
+ bool m_lowLatencyMode;
+
+ RIFFAudioFile::SubFormat m_audioRecFileFormat;
+
+ // Virtual studio hook
+ //
+ MappedStudio *m_studio;
+
+ // Sequencer data block for communication back to GUI
+ //
+ SequencerDataBlock *m_sequencerDataBlock;
+
+ // Controller to make externally originated transport requests on
+ //
+ ExternalTransport *m_externalTransport;
+
+ // MMC and MTC status and ID
+ //
+ TransportSyncStatus m_midiSyncStatus;
+ TransportSyncStatus m_mmcStatus;
+ TransportSyncStatus m_mtcStatus;
+ MidiByte m_mmcId; // device id
+
+ // MIDI clock interval
+ //
+ bool m_midiClockEnabled;
+ RealTime m_midiClockInterval;
+ RealTime m_midiClockSendTime;
+
+ // MIDI Song Position pointer
+ //
+ long m_midiSongPositionPointer;
+
+};
+
+}
+
+#endif // _SOUNDDRIVER_H_
+
diff --git a/src/sound/SoundDriverFactory.cpp b/src/sound/SoundDriverFactory.cpp
new file mode 100644
index 0000000..d081a4e
--- /dev/null
+++ b/src/sound/SoundDriverFactory.cpp
@@ -0,0 +1,66 @@
+// -*- c-indentation-style:"stroustrup" c-basic-offset: 4 -*-
+/*
+ Rosegarden
+ A sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <bownie@bownie.com>
+
+ 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 "DummyDriver.h"
+
+#ifdef HAVE_ALSA
+#include "AlsaDriver.h"
+#endif
+
+#include "SoundDriverFactory.h"
+
+namespace Rosegarden
+{
+
+SoundDriver *
+SoundDriverFactory::createDriver(MappedStudio *studio)
+{
+ SoundDriver *driver = 0;
+ bool initialised = false;
+#ifdef NO_SOUND
+
+ driver = new DummyDriver(studio);
+#else
+#ifdef HAVE_ALSA
+
+ driver = new AlsaDriver(studio);
+#endif
+#endif
+
+ initialised = driver->initialise();
+
+ if ( ! initialised ) {
+ driver->shutdown();
+ delete driver;
+
+ // if the driver couldn't be initialised, then
+ // fall to the DummyDriver as a last chance,
+ // so GUI can still be used for notation.
+ //
+ driver = new DummyDriver(studio);
+ driver->initialise();
+ }
+ return driver;
+}
+
+
+}
+
+
diff --git a/src/sound/SoundDriverFactory.h b/src/sound/SoundDriverFactory.h
new file mode 100644
index 0000000..56bc889
--- /dev/null
+++ b/src/sound/SoundDriverFactory.h
@@ -0,0 +1,37 @@
+// -*- c-indentation-style:"stroustrup" c-basic-offset: 4 -*-
+/*
+ Rosegarden
+ A sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <bownie@bownie.com>
+
+ 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 SOUND_DRIVER_FACTORY_H
+#define SOUND_DRIVER_FACTORY_H
+
+namespace Rosegarden {
+
+class SoundDriver;
+
+class SoundDriverFactory
+{
+public:
+ static SoundDriver *createDriver(MappedStudio *studio);
+};
+
+}
+
+#endif
+
diff --git a/src/sound/SoundFile.cpp b/src/sound/SoundFile.cpp
new file mode 100644
index 0000000..b87cef0
--- /dev/null
+++ b/src/sound/SoundFile.cpp
@@ -0,0 +1,295 @@
+// -*- c-indentation-style:"stroustrup" c-basic-offset: 4 -*-
+/*
+ Rosegarden
+ A sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <bownie@bownie.com>
+
+ 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 "SoundFile.h"
+#include "Profiler.h"
+
+
+//#define DEBUG_SOUNDFILE 1
+
+namespace Rosegarden
+
+{
+
+SoundFile::SoundFile(const std::string &fileName):
+ m_fileName(fileName),
+ m_readChunkPtr( -1),
+ m_readChunkSize(4096), // 4k blocks
+ m_inFile(0),
+ m_outFile(0),
+ m_loseBuffer(false),
+ m_fileSize(0)
+{}
+
+// Tidies up for any dervied classes
+//
+SoundFile::~SoundFile()
+{
+ if (m_inFile) {
+ m_inFile->close();
+ delete m_inFile;
+ }
+
+ if (m_outFile) {
+ m_outFile->close();
+ delete m_outFile;
+ }
+
+}
+
+// Read in a specified number of bytes and return them
+// as a string.
+//
+std::string
+SoundFile::getBytes(std::ifstream *file, unsigned int numberOfBytes)
+{
+ if (file->eof()) {
+ // Reset the input stream so it's operational again
+ //
+ file->clear();
+
+ throw(BadSoundFileException(m_fileName, "SoundFile::getBytes() - EOF encountered"));
+ }
+
+ if (!(*file)) {
+ std::cerr << "SoundFile::getBytes() - stream is not well";
+ }
+
+
+ std::string rS;
+ char *fileBytes = new char[numberOfBytes];
+
+ file->read(fileBytes, numberOfBytes);
+
+ for (int i = 0; i < file->gcount(); i++)
+ rS += (unsigned char)fileBytes[i];
+
+#ifdef DEBUG_SOUNDFILE
+ // complain but return
+ //
+ if (rS.length() < numberOfBytes)
+ std::cerr << "SoundFile::getBytes() - couldn't get all bytes ("
+ << rS.length() << " from " << numberOfBytes << ")"
+ << std::endl;
+#endif
+
+ // clear down
+ delete [] fileBytes;
+
+ return rS;
+}
+
+// Read a specified number of bytes into a buffer.
+//
+size_t
+SoundFile::getBytes(std::ifstream *file, char *buf, size_t n)
+{
+ if (!(*file)) {
+ std::cerr << "SoundFile::getBytes() - stream is not well";
+ return 0;
+ }
+
+ if (file->eof()) {
+ file->clear();
+ return 0;
+ }
+
+ file->read(buf, n);
+ return file->gcount();
+}
+
+// A buffered read based on the current file handle.
+//
+std::string
+SoundFile::getBytes(unsigned int numberOfBytes)
+{
+ if (m_inFile == 0)
+ throw(BadSoundFileException(m_fileName, "SoundFile::getBytes - no open file handle"));
+
+ if (m_inFile->eof()) {
+ // Reset the input stream so it's operational again
+ //
+ m_inFile->clear();
+
+ throw(BadSoundFileException(m_fileName, "SoundFile::getBytes() - EOF encountered"));
+ }
+
+
+ // If this flag is set we dump the buffer and re-read it -
+ // should be set if specialised class is scanning about
+ // when we're doing buffered reads
+ //
+ if (m_loseBuffer) {
+ m_readChunkPtr = -1;
+ m_loseBuffer = false;
+ }
+
+ std::string rS;
+ char *fileBytes = new char[m_readChunkSize];
+ int oldLength;
+
+ while (rS.length() < numberOfBytes && !m_inFile->eof()) {
+ if (m_readChunkPtr == -1) {
+ // clear buffer
+ m_readBuffer = "";
+
+ // reset read pointer
+ m_readChunkPtr = 0;
+
+ // Try to read the whole chunk
+ //
+ m_inFile->read(fileBytes, m_readChunkSize);
+
+ // file->gcount holds the number of bytes we've actually read
+ // so copy them across into our string
+ //
+ for (int i = 0; i < m_inFile->gcount(); i++)
+ m_readBuffer += (unsigned char)fileBytes[i];
+ }
+
+ // Can we fulfill our request at this pass? If so read the
+ // bytes across and we'll exit at the end of this loop.
+ // m_readChunkPtr keeps our position for next time.
+ //
+ if (numberOfBytes - rS.length() <= m_readBuffer.length() -
+ m_readChunkPtr) {
+ oldLength = rS.length();
+
+ rS += m_readBuffer.substr(m_readChunkPtr,
+ numberOfBytes - oldLength);
+
+ m_readChunkPtr += rS.length() - oldLength;
+ } else {
+ // Fill all we can this time and reset the m_readChunkPtr
+ // so that we fetch another chunk of bytes from the file.
+ //
+ rS += m_readBuffer.substr(m_readChunkPtr,
+ m_readChunkSize - m_readChunkPtr);
+ m_readChunkPtr = -1;
+ }
+
+ // If we're EOF here we must've read and copied across everything
+ // we can do. Reset and break out.
+ //
+ if (m_inFile->eof()) {
+ m_inFile->clear();
+ break;
+ }
+
+ }
+
+#ifdef DEBUG_SOUNDFILE
+ // complain but return
+ //
+ if (rS.length() < numberOfBytes)
+ std::cerr << "SoundFile::getBytes() buffered - couldn't get all bytes ("
+ << rS.length() << " from " << numberOfBytes << ")"
+ << std::endl;
+#endif
+
+ delete [] fileBytes;
+
+ // Reset and return if EOF
+ //
+ if (m_inFile->eof())
+ m_inFile->clear();
+
+ return rS;
+}
+
+
+// Write out a sequence of FileBytes to the stream
+//
+void
+SoundFile::putBytes(std::ofstream *file,
+ const std::string oS)
+{
+ for (unsigned int i = 0; i < oS.length(); i++)
+ *file << (FileByte) oS[i];
+}
+
+void
+SoundFile::putBytes(std::ofstream *file, const char *buffer, size_t n)
+{
+ file->write(buffer, n);
+}
+
+
+// Clip off any path from the filename
+std::string
+SoundFile::getShortFilename() const
+{
+ std::string rS = m_fileName;
+ unsigned int pos = rS.find_last_of("/");
+
+ if (pos > 0 && ( pos + 1 ) < rS.length())
+ rS = rS.substr(pos + 1, rS.length());
+
+ return rS;
+}
+
+
+// Turn a little endian binary std::string into an integer
+//
+int
+SoundFile::getIntegerFromLittleEndian(const std::string &s)
+{
+ int r = 0;
+
+ for (unsigned int i = 0; i < s.length(); i++) {
+ r += (int)(((FileByte)s[i]) << (i * 8));
+ }
+
+ return r;
+}
+
+
+// Turn a value into a little endian string of "length"
+//
+std::string
+SoundFile::getLittleEndianFromInteger(unsigned int value, unsigned int length)
+{
+ std::string r = "";
+
+ do {
+ r += (unsigned char)((long)((value >> (8 * r.length())) & 0xff));
+ } while (r.length() < length);
+
+ return r;
+}
+
+int
+SoundFile::getIntegerFromBigEndian(const std::string &s)
+{
+ return 0;
+}
+
+std::string
+SoundFile::getBigEndianFromInteger(unsigned int value, unsigned int length)
+{
+ std::string r;
+
+ return r;
+}
+
+
+}
+
diff --git a/src/sound/SoundFile.h b/src/sound/SoundFile.h
new file mode 100644
index 0000000..b048226
--- /dev/null
+++ b/src/sound/SoundFile.h
@@ -0,0 +1,155 @@
+// -*- c-indentation-style:"stroustrup" c-basic-offset: 4 -*- /*
+/*
+ Rosegarden
+ A sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <bownie@bownie.com>
+
+ 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 _SOUNDFILE_H_
+#define _SOUNDFILE_H_
+
+// SoundFile is an abstract base class defining behaviour for both
+// MidiFiles and AudioFiles. The getBytes routine is buffered into
+// suitably sized chunks to prevent excessive file reads.
+//
+//
+
+#include <iostream>
+#include <fstream>
+#include <string>
+
+#include "Exception.h"
+
+namespace Rosegarden
+{
+
+
+// Constants related to RIFF/WAV files
+//
+const std::string AUDIO_RIFF_ID = "RIFF";
+const std::string AUDIO_WAVE_ID = "WAVE";
+const std::string AUDIO_FORMAT_ID = "fmt "; // Always four bytes
+
+const std::string AUDIO_BWF_ID = "bext"; // BWF chunk id
+const std::string AUDIO_BWF_PEAK_ID = "levl"; // BWF peak chunk id
+
+
+const float SAMPLE_MAX_8BIT = (float)(0xff);
+const float SAMPLE_MAX_16BIT = (float)(0xffff/2);
+const float SAMPLE_MAX_24BIT = (float)(0xffffff/2);
+
+
+
+typedef unsigned char FileByte;
+
+class SoundFile
+{
+public:
+ SoundFile(const std::string &fileName);
+ virtual ~SoundFile();
+
+ class BadSoundFileException : public Exception
+ {
+ public:
+ BadSoundFileException(std::string path) :
+ Exception("Bad sound file " + path), m_path(path) { }
+ BadSoundFileException(std::string path, std::string message) :
+ Exception("Bad sound file " + path + ": " + message), m_path(path) { }
+ BadSoundFileException(std::string path, std::string file, int line) :
+ Exception("Bad sound file " + path, file, line), m_path(path) { }
+
+ ~BadSoundFileException() throw() { }
+
+ std::string getPath() const { return m_path; }
+
+ private:
+ std::string m_path;
+ };
+
+ // All files should be able open, write and close
+ virtual bool open() = 0;
+ virtual bool write() = 0;
+ virtual void close() = 0;
+
+ std::string getShortFilename() const;
+ std::string getFilename() const { return m_fileName; }
+ void setFilename(const std::string &fileName) { m_fileName = fileName; }
+
+ // Useful methods that operate on our file data
+ //
+ int getIntegerFromLittleEndian(const std::string &s);
+ std::string getLittleEndianFromInteger(unsigned int value,
+ unsigned int length);
+
+ int getIntegerFromBigEndian(const std::string &s);
+ std::string getBigEndianFromInteger(unsigned int value,
+ unsigned int length);
+
+ // Buffered read - allow this to be public
+ //
+ std::string getBytes(unsigned int numberOfBytes);
+
+ // Return file size
+ //
+ unsigned int getSize() const { return m_fileSize; }
+
+ void resetStream() { m_inFile->seekg(0); m_inFile->clear(); }
+
+ // check EOF status
+ //
+ bool isEof() const
+ { if (m_inFile) return m_inFile->eof(); else return true; }
+
+protected:
+ std::string m_fileName;
+
+ // get some bytes from an input stream - unbuffered as we can
+ // modify the file stream
+ std::string getBytes(std::ifstream *file, unsigned int numberOfBytes);
+
+ // Get n bytes from an input stream and write them into buffer.
+ // Return the actual number of bytes read.
+ size_t getBytes(std::ifstream *file, char *buffer, size_t n);
+
+ // write some bytes to an output stream
+ void putBytes(std::ofstream *file, const std::string outputString);
+
+ // write some bytes to an output stream
+ void putBytes(std::ofstream *file, const char *buffer, size_t n);
+
+ // Read buffering - define chunk size and buffer file reading
+ //
+ int m_readChunkPtr;
+ int m_readChunkSize;
+ std::string m_readBuffer;
+
+ std::ifstream *m_inFile;
+ std::ofstream *m_outFile;
+
+ bool m_loseBuffer; // do we need to dump the read buffer
+ // and re-fill it?
+
+ unsigned int m_fileSize;
+
+};
+
+}
+
+
+#endif // _SOUNDFILE_H_
+
+
diff --git a/src/sound/WAVAudioFile.cpp b/src/sound/WAVAudioFile.cpp
new file mode 100644
index 0000000..4e3b3bd
--- /dev/null
+++ b/src/sound/WAVAudioFile.cpp
@@ -0,0 +1,255 @@
+// -*- c-basic-offset: 4 -*-
+
+/*
+ Rosegarden
+ A sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <bownie@bownie.com>
+
+ 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 "WAVAudioFile.h"
+#include "RealTime.h"
+
+#if (__GNUC__ < 3)
+#include <strstream>
+#define stringstream strstream
+#else
+#include <sstream>
+#endif
+
+using std::cout;
+using std::cerr;
+using std::endl;
+
+//#define DEBUG_DECODE 1
+
+namespace Rosegarden
+{
+
+WAVAudioFile::WAVAudioFile(const unsigned int &id,
+ const std::string &name,
+ const std::string &fileName):
+ RIFFAudioFile(id, name, fileName)
+{
+ m_type = WAV;
+}
+
+WAVAudioFile::WAVAudioFile(const std::string &fileName,
+ unsigned int channels = 1,
+ unsigned int sampleRate = 48000,
+ unsigned int bytesPerSecond = 6000,
+ unsigned int bytesPerFrame = 2,
+ unsigned int bitsPerSample = 16):
+ RIFFAudioFile(fileName, channels, sampleRate, bytesPerSecond, bytesPerFrame, bitsPerSample)
+{
+ m_type = WAV;
+}
+
+WAVAudioFile::~WAVAudioFile()
+{}
+
+bool
+WAVAudioFile::open()
+{
+ // if already open
+ if (m_inFile && (*m_inFile))
+ return true;
+
+ m_inFile = new std::ifstream(m_fileName.c_str(),
+ std::ios::in | std::ios::binary);
+
+ if (!(*m_inFile)) {
+ m_type = UNKNOWN;
+ return false;
+ }
+
+ // Get the file size and store it for comparison later
+ m_fileSize = m_fileInfo->size();
+
+ try {
+ parseHeader();
+ } catch (BadSoundFileException e) {
+ std::cerr << "ERROR: WAVAudioFile::open(): parseHeader: " << e.getMessage() << endl;
+ return false;
+ }
+
+ return true;
+}
+
+// Open the file for writing, write out the header and move
+// to the data chunk to accept samples. We fill in all the
+// totals when we close().
+//
+bool
+WAVAudioFile::write()
+{
+ // close if we're open
+ if (m_outFile) {
+ m_outFile->close();
+ delete m_outFile;
+ }
+
+ // open for writing
+ m_outFile = new std::ofstream(m_fileName.c_str(),
+ std::ios::out | std::ios::binary);
+
+ if (!(*m_outFile))
+ return false;
+
+ // write out format header chunk and prepare for sample writing
+ //
+ writeFormatChunk();
+
+ return true;
+}
+
+void
+WAVAudioFile::close()
+{
+ if (m_outFile == 0)
+ return ;
+
+ m_outFile->seekp(0, std::ios::end);
+ unsigned int totalSize = m_outFile->tellp();
+
+ // seek to first length position
+ m_outFile->seekp(4, std::ios::beg);
+
+ // write complete file size minus 8 bytes to here
+ putBytes(m_outFile, getLittleEndianFromInteger(totalSize - 8, 4));
+
+ // reseek from start forward 40
+ m_outFile->seekp(40, std::ios::beg);
+
+ // write the data chunk size to end
+ putBytes(m_outFile, getLittleEndianFromInteger(totalSize - 44, 4));
+
+ m_outFile->close();
+
+ delete m_outFile;
+ m_outFile = 0;
+}
+
+// Set the AudioFile meta data according to WAV file format specification.
+//
+void
+WAVAudioFile::parseHeader()
+{
+ // Read the format chunk and populate the file data. A plain WAV
+ // file only has this chunk. Exceptions tumble through.
+ //
+ readFormatChunk();
+
+}
+
+std::streampos
+WAVAudioFile::getDataOffset()
+{
+ return 0;
+}
+
+bool
+WAVAudioFile::decode(const unsigned char *ubuf,
+ size_t sourceBytes,
+ size_t targetSampleRate,
+ size_t targetChannels,
+ size_t nframes,
+ std::vector<float *> &target,
+ bool adding)
+{
+ size_t sourceChannels = getChannels();
+ size_t sourceSampleRate = getSampleRate();
+ size_t fileFrames = sourceBytes / getBytesPerFrame();
+
+ int bitsPerSample = getBitsPerSample();
+ if (bitsPerSample != 8 &&
+ bitsPerSample != 16 &&
+ bitsPerSample != 24 &&
+ bitsPerSample != 32) { // 32-bit is IEEE-float (enforced in RIFFAudioFile)
+ std::cerr << "WAVAudioFile::decode: unsupported " <<
+ bitsPerSample << "-bit sample size" << std::endl;
+ return false;
+ }
+
+#ifdef DEBUG_DECODE
+ std::cerr << "WAVAudioFile::decode: " << sourceBytes << " bytes -> " << nframes << " frames, SSR " << getSampleRate() << ", TSR " << targetSampleRate << ", sch " << getChannels() << ", tch " << targetChannels << std::endl;
+#endif
+
+ // If we're reading a stereo file onto a mono target, we mix the
+ // two channels. If we're reading mono to stereo, we duplicate
+ // the mono channel. Otherwise if the numbers of channels differ,
+ // we just copy across the ones that do match and zero the rest.
+
+ bool reduceToMono = (targetChannels == 1 && sourceChannels == 2);
+
+ for (size_t ch = 0; ch < sourceChannels; ++ch) {
+
+ if (!reduceToMono || ch == 0) {
+ if (ch >= targetChannels)
+ break;
+ if (!adding)
+ memset(target[ch], 0, nframes * sizeof(float));
+ }
+
+ int tch = ch; // target channel for this data
+ if (reduceToMono && ch == 1) {
+ tch = 0;
+ }
+
+ float ratio = 1.0;
+ if (sourceSampleRate != targetSampleRate) {
+ ratio = float(sourceSampleRate) / float(targetSampleRate);
+ }
+
+ for (size_t i = 0; i < nframes; ++i) {
+
+ size_t j = i;
+ if (sourceSampleRate != targetSampleRate) {
+ j = size_t(i * ratio);
+ }
+ if (j >= fileFrames)
+ j = fileFrames - 1;
+
+ float sample = convertBytesToSample
+ (&ubuf[(bitsPerSample / 8) * (ch + j * sourceChannels)]);
+
+ target[tch][i] += sample;
+ }
+ }
+
+ // Now deal with any excess target channels
+
+ for (int ch = sourceChannels; ch < targetChannels; ++ch) {
+ if (ch == 1 && targetChannels == 2) {
+ // copy mono to stereo
+ if (!adding) {
+ memcpy(target[ch], target[ch - 1], nframes * sizeof(float));
+ } else {
+ for (size_t i = 0; i < nframes; ++i) {
+ target[ch][i] += target[ch - 1][i];
+ }
+ }
+ } else {
+ if (!adding) {
+ memset(target[ch], 0, nframes * sizeof(float));
+ }
+ }
+ }
+
+ return true;
+}
+
+
+}
diff --git a/src/sound/WAVAudioFile.h b/src/sound/WAVAudioFile.h
new file mode 100644
index 0000000..ec57ec6
--- /dev/null
+++ b/src/sound/WAVAudioFile.h
@@ -0,0 +1,93 @@
+// -*- c-basic-offset: 4 -*-
+
+/*
+ Rosegarden
+ A sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <bownie@bownie.com>
+
+ 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.
+*/
+
+
+// Specialisation of a RIFF file - the WAV defines a format chunk
+// holding audio file meta data and a data chunk with interleaved
+// sample bytes.
+//
+
+#include "RIFFAudioFile.h"
+
+
+#ifndef _WAVAUDIOFILE_H_
+#define _WAVAUDIOFILE_H_
+
+namespace Rosegarden
+{
+
+class WAVAudioFile : public RIFFAudioFile
+{
+public:
+ WAVAudioFile(const unsigned int &id,
+ const std::string &name,
+ const std::string &fileName);
+
+ WAVAudioFile(const std::string &fileName,
+ unsigned int channels,
+ unsigned int sampleRate,
+ unsigned int bytesPerSecond,
+ unsigned int bytesPerSample,
+ unsigned int bitsPerSample);
+
+ ~WAVAudioFile();
+
+ // Override these methods for the WAV
+ //
+ virtual bool open();
+ virtual bool write();
+ virtual void close();
+
+ // Decode and de-interleave the given samples that were retrieved
+ // from this file or another with the same format as it. Place
+ // the results in the given float buffer. Return true for
+ // success. This function does crappy resampling if necessary.
+ //
+ virtual bool decode(const unsigned char *sourceData,
+ size_t sourceBytes,
+ size_t targetSampleRate,
+ size_t targetChannels,
+ size_t targetFrames,
+ std::vector<float *> &targetData,
+ bool addToResultBuffers = false);
+
+ // Get all header information
+ //
+ void parseHeader();
+
+ // Offset to start of sample data
+ //
+ virtual std::streampos getDataOffset();
+
+ // Peak file name
+ //
+ virtual std::string getPeakFilename()
+ { return (m_fileName + std::string(".pk")); }
+
+
+protected:
+
+};
+
+}
+
+
+#endif // _WAVAUDIOFILE_H_
diff --git a/src/test/accidentals.cpp b/src/test/accidentals.cpp
new file mode 100644
index 0000000..90d929b
--- /dev/null
+++ b/src/test/accidentals.cpp
@@ -0,0 +1,88 @@
+// -*- 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
+int 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;
+ return -1;
+ }
+ return 0;
+}
+
+int testBInEMinor()
+{
+ // a B, also in E minor, has no accidental
+ Pitch testPitch(59 % 12);
+ return assertHasAccidental(testPitch,
+ Accidentals::NoAccidental, Key("E minor"));
+}
+
+/**
+ *
+ */
+int testFInBMinor()
+{
+ Pitch testPitch(77);
+ return assertHasAccidental(testPitch,
+ Accidentals::NoAccidental, Key("B minor"));
+}
+
+int testInvalidSuggestion()
+{
+ // If we specify an invalid suggestion,
+ // getAccidental() should be robust against that.
+ Pitch testPitch = Pitch(59, Accidentals::Sharp);
+ return assertHasAccidental(testPitch,
+ Accidentals::NoAccidental, Key("E minor"));
+}
+
+int testBbinBb()
+{
+ Pitch testPitch = Pitch(10, Accidentals::NoAccidental);
+ Accidental accidental = testPitch.getAccidental(Key("Bb major"));
+ std::cout << "Bb accidental: " << accidental << std::endl;
+ if (accidental != Accidentals::Flat)
+ {
+ return -1;
+ }
+ return 0;
+}
+
+// Verifies that the height on staff for pitch 61 using flats is -1, not -2
+int testDesHeight()
+{
+ bool useSharps = false;
+
+ Pitch pitch(61);
+ int h = pitch.getHeightOnStaff(Clef(Clef::Treble, 0), useSharps);
+
+ if (h != -1)
+ {
+ std::cerr << "Error in testDesHeight: expected height -1, got " << h << std::endl;
+ return -1;
+ }
+ return 0;
+}
+
+int test_accidentals(int argc, char **argv)
+{
+ return testBInEMinor() +
+ testFInBMinor() +
+ testInvalidSuggestion() +
+ testBbinBb() +
+ testDesHeight();
+}
diff --git a/src/test/dummy.cpp b/src/test/dummy.cpp
new file mode 100644
index 0000000..66806a0
--- /dev/null
+++ b/src/test/dummy.cpp
@@ -0,0 +1,6 @@
+/** dummy unittest to test the unittesting system */
+int test_dummy (int argc, char** argv)
+{
+ // Always succeed
+ return 0;
+}
diff --git a/src/test/segmenttransposecommand.cpp b/src/test/segmenttransposecommand.cpp
new file mode 100644
index 0000000..0025529
--- /dev/null
+++ b/src/test/segmenttransposecommand.cpp
@@ -0,0 +1,161 @@
+// -*- c-basic-offset: 4 -*-
+
+#include "commands/segment/SegmentTransposeCommand.h"
+#include "base/NotationTypes.h"
+#include "base/Segment.h"
+#include "base/Selection.h"
+
+using namespace Rosegarden;
+using std::cout;
+
+/**
+ * Bb in Bb major became E# in F major, due to segment
+ * transposition
+ *
+ * Should be F
+ */
+int testSegmentBbtoF()
+{
+ Segment * segment1 = new Segment();
+ Note * n = new Note(Note::QuarterNote);
+ Event * bes = n->getAsNoteEvent(1, 10);
+ segment1->insert(bes);
+ segment1->insert(Key("Bb major").getAsEvent(0));
+ SegmentTransposeCommand * mockCommand =
+ new SegmentTransposeCommand(*segment1,
+ true, -3, -5, true);
+ mockCommand->execute();
+
+ EventSelection m_selection(*segment1, segment1->getStartTime(), segment1->getEndMarkerTime());
+ EventSelection::eventcontainer::iterator i;
+ for (i = m_selection.getSegmentEvents().begin();
+ i != m_selection.getSegmentEvents().end(); ++i) {
+ if ((*i)->isa(Note::EventType)) {
+ Pitch resultPitch(**i);
+ std::cout << "Resulting pitch is: " << resultPitch.getPerformancePitch() << std::endl;
+ std::cout << "accidental: " << resultPitch.getDisplayAccidental(Key("F major")) << std::endl;
+ std::cout << "DisplayAccidental: " << resultPitch.getDisplayAccidental(Key("F major")) << std::endl;
+ if (resultPitch.getDisplayAccidental(Key("F major")) != Accidentals::NoAccidental)
+ {
+ return -1;
+ }
+ }
+ }
+
+ return 0;
+}
+
+/**
+ * G# in E major became Bb in F major, due to segment
+ * transposition (by using the 'segment transposition' combobox)
+ *
+ * Should be A#
+ */
+int testGistoAis()
+{
+ Segment * segment1 = new Segment();
+ Note * n = new Note(Note::QuarterNote);
+ Event * gis = n->getAsNoteEvent(1, 8);
+ segment1->insert(gis);
+ segment1->insert(Key("E major").getAsEvent(0));
+ SegmentTransposeCommand * mockCommand =
+ new SegmentTransposeCommand(*segment1,
+ true, 1, 2, true);
+ mockCommand->execute();
+
+ EventSelection m_selection(*segment1, segment1->getStartTime(), segment1->getEndMarkerTime());
+ EventSelection::eventcontainer::iterator i;
+ for (i = m_selection.getSegmentEvents().begin();
+ i != m_selection.getSegmentEvents().end(); ++i) {
+ if ((*i)->isa(Note::EventType)) {
+ Pitch resultPitch(**i);
+ std::cout << "Resulting pitch is: " << resultPitch.getPerformancePitch() << std::endl;
+ std::cout << "accidental: " << resultPitch.getDisplayAccidental(Key("F# major")) << std::endl;
+ std::cout << "DisplayAccidental: " << resultPitch.getDisplayAccidental(Key("F# major")) << std::endl;
+ if (resultPitch.getDisplayAccidental(Key("F# major")) != Accidentals::NoAccidental)
+ {
+ std::cout << "Gis in E major does not become A#-in-F#-major (no-accidental) when transposed upwards by a small second" << std::endl;
+ return -1;
+ }
+ }
+ }
+
+ return 0;
+}
+
+/**
+ * A C# in the key of C# major somehow became a B# in the key of C
+ */
+int testSegmentCisToC()
+{
+ Segment * segment1 = new Segment();
+ Note * n = new Note(Note::QuarterNote);
+ Event * cis = n->getAsNoteEvent(1, 13);
+ segment1->insert(cis);
+ segment1->insert(Key("C# major").getAsEvent(0));
+ SegmentTransposeCommand * mockCommand =
+ new SegmentTransposeCommand(*segment1,
+ true, 0, -1, true);
+ mockCommand->execute();
+
+ EventSelection m_selection(*segment1, segment1->getStartTime(), segment1->getEndMarkerTime());
+ EventSelection::eventcontainer::iterator i;
+ for (i = m_selection.getSegmentEvents().begin();
+ i != m_selection.getSegmentEvents().end(); ++i) {
+ if ((*i)->isa(Note::EventType)) {
+ Pitch resultPitch(**i);
+ std::cout << "Resulting pitch is: " << resultPitch.getPerformancePitch() << std::endl;
+ std::cout << "accidental: " << resultPitch.getDisplayAccidental(Key("C major")) << std::endl;
+ std::cout << "DisplayAccidental: " << resultPitch.getDisplayAccidental(Key("C major")) << std::endl;
+ if (resultPitch.getDisplayAccidental(Key("C major")) != Accidentals::NoAccidental)
+ {
+ std::cout << "C# in C# major does not lose accidental when transposed downwards by 1 semitone" << std::endl;
+ return -1;
+ }
+ }
+ }
+
+ return 0;
+}
+
+int testUndo()
+{
+ Segment * segment1 = new Segment();
+ Segment * segment2 = new Segment();
+
+ // transpose once
+ SegmentTransposeCommand * mockCommand1a =
+ new SegmentTransposeCommand(*segment1,
+ true, -1, -2, true);
+ mockCommand1a->execute();
+ SegmentTransposeCommand * mockCommand1b =
+ new SegmentTransposeCommand(*segment2,
+ true, -1, -2, true);
+ mockCommand1b->execute();
+
+ // transpose twice
+ SegmentTransposeCommand * mockCommand2a =
+ new SegmentTransposeCommand(*segment1,
+ true, -1, -2, true);
+ mockCommand2a->execute();
+ SegmentTransposeCommand * mockCommand2b =
+ new SegmentTransposeCommand(*segment2,
+ true, -1, -2, true);
+ mockCommand2b->execute();
+
+ mockCommand2b->unexecute();
+ mockCommand2a->unexecute();
+ mockCommand1b->unexecute();
+ mockCommand1a->unexecute();
+
+ return 0;
+}
+
+int test_segmenttransposecommand(int argc, char** argv)
+{
+ return
+ testGistoAis() +
+ testSegmentCisToC() +
+ testUndo() +
+ testSegmentBbtoF();
+}
diff --git a/src/test/transpose.cpp b/src/test/transpose.cpp
new file mode 100644
index 0000000..a4198b3
--- /dev/null
+++ b/src/test/transpose.cpp
@@ -0,0 +1,154 @@
+// -*- c-basic-offset: 4 -*-
+//
+
+#include "NotationTypes.h"
+#include "gui/dialogs/IntervalDialog.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
+ */
+int 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;
+ return -1;
+ }
+
+ return 0;
+}
+
+/**
+ * transpose an C# down by an augmented prime in C# major, should yield a C (in C major)
+ */
+int testCisToC()
+{
+ std::cout << "Testing transposing C# to C... ";
+
+ Pitch cis(73, Accidentals::Sharp);
+ Pitch result = cis.transpose(Key("C# major"), -1, 0);
+
+ Accidental resultAccidental = result.getAccidental(Key("C major"));
+ int resultPitch = result.getPerformancePitch();
+ if (resultAccidental != Accidentals::NoAccidental || resultPitch != 72)
+ {
+ std::cout << "Transposing C# down by an augmented prime didn't yield C, but " << result.getNoteName(Key("C major")) << resultAccidental << std::endl;
+ return -1;
+ }
+
+ return 0;
+}
+
+/**
+ * transpose an A# up by a major second, should
+ * yield a B# (as C would be a minor triad)
+ */
+int 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;
+ return -1;
+ }
+ std::cout << "Success" << std::endl;
+
+ return 0;
+}
+
+/**
+ * Transpose G to D in the key of D major.
+ */
+int 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;
+ return -1;
+ }
+ std::cout << "Success" << std::endl;
+ return 0;
+}
+
+int testTransposeBbToF()
+{
+ Pitch bb(70, Accidentals::Flat);
+ Key besmaj("Bb major");
+ Pitch result = bb.transpose(besmaj, -5, -3);
+
+ Accidental resultAccidental = result.getAccidental(besmaj);
+ int resultPitch = result.getPerformancePitch();
+ if (resultAccidental != Accidentals::NoAccidental || resultPitch != 65)
+ {
+ return -1;
+ }
+ return 0;
+}
+
+int testIntervalString(int steps, int semitones, QString expectedString)
+{
+ QString text = IntervalDialog::getIntervalName(steps, semitones);
+ if (text != expectedString) {
+ std::cout << "When converting the interval " << steps << "," << semitones << " to string, expected '" << expectedString << "' but got '" << text << "'" << std::endl;
+ return -1;
+ }
+ return 0;
+}
+
+int testIntervalToString()
+{
+ return testIntervalString(1,1,"up a minor second")
+ + testIntervalString(0,0,"a perfect unison")
+ + testIntervalString(0,1,"up an augmented unison")
+ + testIntervalString(7,12,"up 1 octave")
+ + testIntervalString(7,13,"up an augmented octave");
+
+ QString text = IntervalDialog::getIntervalName(1, 1);
+ std::cout << "Minor second: " << text << std::endl;
+
+ text = IntervalDialog::getIntervalName(0, 0);
+ std::cout << "Perfect unison: " << text << std::endl;
+ text = IntervalDialog::getIntervalName(0, 1);
+ std::cout << "Augmented unison: " << text << std::endl;
+ text = IntervalDialog::getIntervalName(7, 12);
+ std::cout << "1 octave: " << text << std::endl;
+ text = IntervalDialog::getIntervalName(7, 13);
+ std::cout << "Octave and augmented unison: " << text << std::endl;
+ return 0;
+}
+
+int test_transpose(int argc, char **argv)
+{
+ return testAisDisplayAccidentalInCmaj() +
+ testAisToBis() +
+ testGToD() +
+ testTransposeBbToF() +
+ testIntervalToString() +
+ testCisToC();
+
+}