diff --git a/Sources/VLDocument.h b/Sources/VLDocument.h index 8a0ce09..475b6d4 100644 --- a/Sources/VLDocument.h +++ b/Sources/VLDocument.h @@ -8,6 +8,7 @@ #import "VLModel.h" #import +#import "VLKeyValueUndo.h" @class VLSheetWindow; @class VLPDFWindow; @@ -29,6 +30,7 @@ VLSheetWindow * sheetWin; VLLogWindow * logWin; VLPDFWindow * pdfWin; + VLKeyValueUndo* undo; } - (VLSong *) song; @@ -49,6 +51,8 @@ - (NSString *) baseName; - (NSURL *) fileURLWithExtension:(NSString*)extension; - (NSTask *) taskWithLaunchPath:(NSString *)path arguments:(NSArray *)args; +- (void) willChangeSong; +- (void) didChangeSong; @end diff --git a/Sources/VLDocument.mm b/Sources/VLDocument.mm index af783b0..8f4bdab 100644 --- a/Sources/VLDocument.mm +++ b/Sources/VLDocument.mm @@ -16,6 +16,43 @@ #import "VLSheetWindow.h" #import "VLSoundOut.h" +@interface VLSongWrapper : NSObject { + VLSong * wrappedSong; +} + ++ (VLSongWrapper *)wrapperWithSong:(VLSong *)song; +- (VLSong *)song; + +@end + +@implementation VLSongWrapper + +- (id)initWithSong:(VLSong *)song +{ + if (self = [super init]) + wrappedSong = new VLSong(*song); + return self; +} + +- (void) dealloc +{ + delete wrappedSong; + + [super dealloc]; +} + ++ (VLSongWrapper *)wrapperWithSong:(VLSong *)song +{ + return [[[VLSongWrapper alloc] initWithSong:song] autorelease]; +} + +- (VLSong *)song +{ + return wrappedSong; +} + +@end + @implementation VLDocument - (id)init @@ -35,6 +72,17 @@ logWin = nil; tmpPath = nil; vcsWrapper = nil; + [self setHasUndoManager:YES]; + undo = + [[VLKeyValueUndo alloc] initWithOwner:self + keysAndNames: [NSDictionary dictionaryWithObjectsAndKeys: + @"", @"songTitle", + @"", @"songLyricist", + @"", @"songComposer", + @"", @"songArranger", + @"", @"songGroove", + @"", @"songTempo", + nil]]; } return self; } @@ -49,6 +97,7 @@ [songComposer release]; [songArranger release]; [vcsWrapper release]; + [undo release]; if (tmpPath) { [[NSFileManager defaultManager] removeFileAtPath:tmpPath handler:nil]; @@ -119,9 +168,11 @@ - (void) setKey:(int)key transpose:(BOOL)transpose { + [self willChangeSong]; + VLProperties & prop = song->fProperties.front(); - if (transpose) + if (transpose) song->Transpose((7*((key>>8)-prop.fKey) % 12)); prop.fKey = key >> 8; @@ -139,6 +190,8 @@ - (void) setTimeNum:(int)num denom:(int)denom { + [self willChangeSong]; + VLProperties & prop = song->fProperties.front(); prop.fTime = VLFraction(num, denom); @@ -155,6 +208,8 @@ - (void) setDivisions:(int)divisions { + [self willChangeSong]; + VLProperties & prop = song->fProperties.front(); prop.fDivisions = divisions; @@ -328,4 +383,31 @@ [[self logWin] showWindow:sender]; } +- (void) willChangeSong +{ + [self willChangeValueForKey:@"song"]; + [[self undoManager] registerUndoWithTarget:self + selector:@selector(restoreSong:) + object:[VLSongWrapper wrapperWithSong:song]]; +} + +- (void) didChangeSong +{ + [self didChangeValueForKey:@"song"]; + [self updateChangeCount:NSChangeDone]; +} + +- (void) restoreSong:(VLSongWrapper *)savedSong +{ + [self willChangeSong]; + [self willChangeValueForKey:@"songKey"]; + [self willChangeValueForKey:@"songTime"]; + [self willChangeValueForKey:@"songDivisions"]; + song->swap(*[savedSong song]); + [self didChangeValueForKey:@"songKey"]; + [self didChangeValueForKey:@"songTime"]; + [self didChangeValueForKey:@"songDivisions"]; + [self didChangeSong]; +} + @end diff --git a/Sources/VLKeyValueUndo.h b/Sources/VLKeyValueUndo.h new file mode 100644 index 0000000..57f3a41 --- /dev/null +++ b/Sources/VLKeyValueUndo.h @@ -0,0 +1,18 @@ +// +// VLKeyValueUndo.h +// Vocalese +// +// Created by Matthias Neeracher on 12/3/06. +// Copyright 2006 __MyCompanyName__. All rights reserved. +// + +#import + +@interface VLKeyValueUndo : NSObject { + id owner; + NSDictionary * keysAndNames; +} + +- (id)initWithOwner:(id)owner keysAndNames:(NSDictionary *)keysAndNames; + +@end diff --git a/Sources/VLKeyValueUndo.mm b/Sources/VLKeyValueUndo.mm new file mode 100644 index 0000000..e21c0f8 --- /dev/null +++ b/Sources/VLKeyValueUndo.mm @@ -0,0 +1,54 @@ +// +// VLKeyValueUndo.mm +// Vocalese +// +// Created by Matthias Neeracher on 12/3/06. +// Copyright 2006 __MyCompanyName__. All rights reserved. +// + +#import "VLKeyValueUndo.h" + + +@implementation VLKeyValueUndo + +- (id)initWithOwner:(id)o keysAndNames:(NSDictionary *)kn +{ + owner = o; + keysAndNames = [kn retain]; + + for (NSEnumerator * e = [keysAndNames keyEnumerator]; + NSString * key = [e nextObject]; + ) + [owner addObserver:self forKeyPath:key + options:NSKeyValueObservingOptionOld|NSKeyValueObservingOptionNew + context:[keysAndNames objectForKey:key]]; + + return self; +} + +- (void) dealloc +{ + for (NSEnumerator * e = [keysAndNames keyEnumerator]; + NSString * key = [e nextObject]; + ) + [owner removeObserver:self forKeyPath:key]; + + [keysAndNames release]; + [super dealloc]; +} + +- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context +{ + id oldVal = [change objectForKey:NSKeyValueChangeOldKey]; + id newVal = [change objectForKey:NSKeyValueChangeNewKey]; + + if (![oldVal isEqual:newVal]) { + NSUndoManager * undo = [owner undoManager]; + NSString * name = [keysAndNames objectForKey:keyPath]; + [undo registerUndoWithTarget:owner selector:@selector(setValuesForKeysWithDictionary:) + object: [NSDictionary dictionaryWithObjectsAndKeys: oldVal, keyPath, nil]]; + [undo setActionName: name]; + } +} + +@end diff --git a/Sources/VLMMADocument.mm b/Sources/VLMMADocument.mm index b428d1f..6400d93 100644 --- a/Sources/VLMMADocument.mm +++ b/Sources/VLMMADocument.mm @@ -32,9 +32,9 @@ for (size_t m=0; mCountMeasures(); ++m) { sprintf(buf, "%-5d", m+1); mmaFile += buf; - song->fMeasures[m].MMAChords(mmas); + song->fMeasures[m].MMAChords(mmas, prop); mmaFile += mmas; - song->fMeasures[m].MMANotes(mmas); + song->fMeasures[m].MMANotes(mmas, prop); mmaFile += "\t{ " + mmas + " }\n"; } diff --git a/Sources/VLModel.cpp b/Sources/VLModel.cpp index 9fea0ef..9557de3 100644 --- a/Sources/VLModel.cpp +++ b/Sources/VLModel.cpp @@ -737,11 +737,11 @@ void VLProperties::VisualNote(VLFraction at, VLFraction actualDur, } VLMeasure::VLMeasure() - : fProperties(0) + : fPropIdx(0) { } -void VLMeasure::MMANotes(std::string & notes) const +void VLMeasure::MMANotes(std::string & notes, const VLProperties & prop) const { VLFraction at(0); VLNoteList::const_iterator i = fMelody.begin(); @@ -750,7 +750,7 @@ void VLMeasure::MMANotes(std::string & notes) const notes.clear(); for (; i!=e; ++i) { std::string note; - i->MMAName(note, at, *fProperties); + i->MMAName(note, at, prop); if (notes.size()) notes += ' '; notes += note+';'; @@ -758,7 +758,7 @@ void VLMeasure::MMANotes(std::string & notes) const } } -void VLMeasure::MMAChords(std::string & chords) const +void VLMeasure::MMAChords(std::string & chords, const VLProperties & prop) const { VLChordList::const_iterator i = fChords.begin(); VLChordList::const_iterator e = fChords.end(); @@ -766,7 +766,7 @@ void VLMeasure::MMAChords(std::string & chords) const chords.clear(); for (; i!=e; ++i) { std::string chord; - i->MMAName(chord, fProperties->fKey >= 0); + i->MMAName(chord, prop.fKey >= 0); if (chords.size()) chords += ' '; chords += chord; @@ -786,12 +786,17 @@ VLSong::VLSong() rchord.fDuration = 1; for (int i=0; i<32; ++i) { - fMeasures[i].fProperties = &fProperties.front(); fMeasures[i].fChords.push_back(rchord); fMeasures[i].fMelody.push_back(rest); } } +void VLSong::swap(VLSong & other) +{ + fProperties.swap(other.fProperties); + fMeasures.swap(other.fMeasures); +} + // // Deal with chords - a bit simpler // @@ -1046,7 +1051,7 @@ void VLSong::LilypondNotes(std::string & notes) const for (; i!=e; ++i) { std::string note; - i->LilypondName(note, at, *fMeasures[measure].fProperties); + i->LilypondName(note, at, fProperties[fMeasures[measure].fPropIdx]); at += i->fDuration; notes += note+" "; } @@ -1065,7 +1070,7 @@ void VLSong::LilypondChords(std::string & chords) const { chords = ""; for (size_t measure=0; measurefKey>=0; + bool useSharps = fProperties[fMeasures[measure].fPropIdx].fKey>=0; VLChordList::const_iterator i = fMeasures[measure].fChords.begin(); VLChordList::const_iterator e = fMeasures[measure].fChords.end(); @@ -1145,7 +1150,7 @@ bool VLSong::PrevWord(size_t stanza, size_t & measure, VLFraction & at) return true; } else { - at = meas.fProperties->fTime; + at = fProperties[meas.fPropIdx].fTime; } } while (measure-- > 0); diff --git a/Sources/VLModel.h b/Sources/VLModel.h index 7fcc122..e770ea9 100644 --- a/Sources/VLModel.h +++ b/Sources/VLModel.h @@ -233,21 +233,22 @@ typedef std::list VLChordList; typedef std::list VLNoteList; struct VLMeasure { - VLProperties * fProperties; - VLChordList fChords; - VLNoteList fMelody; + int8_t fPropIdx; + VLChordList fChords; + VLNoteList fMelody; VLMeasure(); - void MMANotes(std::string & notes) const; - void MMAChords(std::string & chords) const; + void MMANotes(std::string & notes, const VLProperties & prop) const; + void MMAChords(std::string & chords, const VLProperties & prop) const; }; struct VLSong { VLSong(); + void swap(VLSong & other); - std::list fProperties; - std::vector fMeasures; + std::vector fProperties; + std::vector fMeasures; void AddChord(VLChord chord, size_t measure, VLFraction at); void AddNote(VLLyricsNote note, size_t measure, VLFraction at); diff --git a/Sources/VLSheetView.mm b/Sources/VLSheetView.mm index 3cf3d12..adb410c 100644 --- a/Sources/VLSheetView.mm +++ b/Sources/VLSheetView.mm @@ -740,4 +740,17 @@ static int8_t sSharpAcc[] = { [self setScaleFactor: fDisplayScale / sqrt(sqrt(2.0))]; } +- (void)awakeFromNib +{ + [[self document] addObserver:self forKeyPath:@"song" options:0 context:nil]; + [[self document] addObserver:self forKeyPath:@"songKey" options:0 context:nil]; +} + +- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)o change:(NSDictionary *)c context:(id)ctx +{ + if ([keyPath isEqual:@"songKey"]) + fNeedsRecalc = kRecalc; + [self setNeedsDisplay: YES]; +} + @end diff --git a/Sources/VLSheetViewChords.mm b/Sources/VLSheetViewChords.mm index 11e1ec0..2d4a109 100644 --- a/Sources/VLSheetViewChords.mm +++ b/Sources/VLSheetViewChords.mm @@ -121,10 +121,11 @@ std::string NormalizeName(NSString* rawName) } else { VLChord chord(chordName); VLSoundOut::Instance()->PlayChord(chord); + + [[fView document] willChangeSong]; fSong->AddChord(chord, fMeasure, fAt); - [fView setNeedsDisplay:YES]; + [[fView document] didChangeSong]; } - [[fView document] updateChangeCount:NSChangeDone]; } - (BOOL) validValue:(NSString *)val diff --git a/Sources/VLSheetViewLyrics.h b/Sources/VLSheetViewLyrics.h index 51a0746..b807cc8 100644 --- a/Sources/VLSheetViewLyrics.h +++ b/Sources/VLSheetViewLyrics.h @@ -13,7 +13,7 @@ VLSong * fSong; size_t fStanza; size_t fMeasure; - VLFraction fAt; + VLFract fAt; } - (VLLyricsEditable *)initWithView:(VLSheetView *)view diff --git a/Sources/VLSheetViewLyrics.mm b/Sources/VLSheetViewLyrics.mm index d42e700..9a16ff2 100644 --- a/Sources/VLSheetViewLyrics.mm +++ b/Sources/VLSheetViewLyrics.mm @@ -28,7 +28,9 @@ fMeasure= measure; fAt = at; - fSong->FindWord(fStanza, fMeasure, fAt); + VLFraction At = fAt; + fSong->FindWord(fStanza, fMeasure, At); + fAt = At; [fView setNeedsDisplay: YES]; @@ -43,7 +45,9 @@ - (void) setStringValue:(NSString *)val { + [[fView document] willChangeSong]; fSong->SetWord(fStanza, fMeasure, fAt, [val UTF8String]); + [[fView document] didChangeSong]; } - (BOOL) validValue:(NSString *)val @@ -53,20 +57,24 @@ - (void) moveToNext { - if (!fSong->NextWord(fStanza, fMeasure, fAt)) { + VLFraction at = fAt; + if (!fSong->NextWord(fStanza, fMeasure, at)) { fMeasure = 0; - fAt = 0; - fSong->FindWord(fStanza, fMeasure, fAt); + at = 0; + fSong->FindWord(fStanza, fMeasure, at); } + fAt = at; } - (void) moveToPrev { - if (!fSong->PrevWord(fStanza, fMeasure, fAt)) { + VLFraction at = fAt; + if (!fSong->PrevWord(fStanza, fMeasure, at)) { fMeasure = fSong->CountMeasures()-1; - fAt = fSong->fMeasures[fMeasure].fProperties->fTime; - fSong->PrevWord(fStanza, fMeasure, fAt); + at = fSong->fProperties.front().fTime; + fSong->PrevWord(fStanza, fMeasure, at); } + fAt = at; } - (void) highlightCursor diff --git a/Sources/VLSheetViewNotes.mm b/Sources/VLSheetViewNotes.mm index 5e2672a..4a3e1a1 100644 --- a/Sources/VLSheetViewNotes.mm +++ b/Sources/VLSheetViewNotes.mm @@ -20,13 +20,12 @@ if (fCursorMeasure > -1) { VLNote newNote(1, fClickMode==' ' ? fCursorActualPitch : VLNote::kNoPitch); + [[self document] willChangeSong]; if (fClickMode == 'k') [self song]->DelNote(fCursorMeasure, fCursorAt); else [self song]->AddNote(VLLyricsNote(newNote), fCursorMeasure, fCursorAt); - - [self setNeedsDisplay:YES]; - [[self document] updateChangeCount:NSChangeDone]; + [[self document] didChangeSong]; if (fClickMode == ' ') VLSoundOut::Instance()->PlayNote(newNote); @@ -269,8 +268,8 @@ VLFraction partialDur; // Actual value of note drawn VLFraction noteDur; // Visual value of note bool triplet; - measure.fProperties->PartialNote(at, dur, &partialDur); - measure.fProperties->VisualNote(at, partialDur, ¬eDur, &triplet); + prop.PartialNote(at, dur, &partialDur); + prop.VisualNote(at, partialDur, ¬eDur, &triplet); if (pitch != VLNote::kNoPitch) { VLMusicElement accidental; diff --git a/Vocalese.xcodeproj/project.pbxproj b/Vocalese.xcodeproj/project.pbxproj index 223e324..1a6659e 100644 --- a/Vocalese.xcodeproj/project.pbxproj +++ b/Vocalese.xcodeproj/project.pbxproj @@ -40,6 +40,8 @@ 95498DBD0AE3812F006B5F81 /* VLSoundSched.mm in Sources */ = {isa = PBXBuildFile; fileRef = 95498DBC0AE3812F006B5F81 /* VLSoundSched.mm */; }; 954BBD860AEDDE5300BBFD5F /* VLAppController.mm in Sources */ = {isa = PBXBuildFile; fileRef = 954BBD850AEDDE5300BBFD5F /* VLAppController.mm */; }; 954BBD9A0AEDE81500BBFD5F /* VLPitchTransformer.mm in Sources */ = {isa = PBXBuildFile; fileRef = 954BBD990AEDE81500BBFD5F /* VLPitchTransformer.mm */; }; + 955CBA4E0B2366DD001CF4A1 /* VLKeyValueUndo.h in Copy MMA Library */ = {isa = PBXBuildFile; fileRef = 955CBA4C0B2366DD001CF4A1 /* VLKeyValueUndo.h */; }; + 955CBA4F0B2366DD001CF4A1 /* VLKeyValueUndo.mm in Sources */ = {isa = PBXBuildFile; fileRef = 955CBA4D0B2366DD001CF4A1 /* VLKeyValueUndo.mm */; }; 955E58E5095658AB0045FDA5 /* VLModel.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 955E58E4095658AB0045FDA5 /* VLModel.cpp */; }; 955E59610957C1400045FDA5 /* TVLChord.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 955E59600957C1400045FDA5 /* TVLChord.cpp */; }; 955E59640957C15A0045FDA5 /* VLModel.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 955E58E4095658AB0045FDA5 /* VLModel.cpp */; }; @@ -106,6 +108,7 @@ 95009B640B0ED18700EB33A4 /* CAConditionalMacros.h in Copy MMA Library */, 95009B650B0ED18700EB33A4 /* CAMath.h in Copy MMA Library */, 95E299BF0B2006F5001977D2 /* VLSheetViewLyrics.h in Copy MMA Library */, + 955CBA4E0B2366DD001CF4A1 /* VLKeyValueUndo.h in Copy MMA Library */, ); name = "Copy MMA Library"; runOnlyForDeploymentPostprocessing = 0; @@ -167,6 +170,8 @@ 954BBD850AEDDE5300BBFD5F /* VLAppController.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = VLAppController.mm; path = Sources/VLAppController.mm; sourceTree = ""; }; 954BBD980AEDE81500BBFD5F /* VLPitchTransformer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = VLPitchTransformer.h; path = Sources/VLPitchTransformer.h; sourceTree = ""; }; 954BBD990AEDE81500BBFD5F /* VLPitchTransformer.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = VLPitchTransformer.mm; path = Sources/VLPitchTransformer.mm; sourceTree = ""; }; + 955CBA4C0B2366DD001CF4A1 /* VLKeyValueUndo.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = VLKeyValueUndo.h; path = Sources/VLKeyValueUndo.h; sourceTree = ""; }; + 955CBA4D0B2366DD001CF4A1 /* VLKeyValueUndo.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = VLKeyValueUndo.mm; path = Sources/VLKeyValueUndo.mm; sourceTree = ""; }; 955E58E3095658AB0045FDA5 /* VLModel.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = VLModel.h; path = Sources/VLModel.h; sourceTree = ""; }; 955E58E4095658AB0045FDA5 /* VLModel.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = VLModel.cpp; path = Sources/VLModel.cpp; sourceTree = ""; }; 955E595C0957C0FC0045FDA5 /* TVLChord */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = TVLChord; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -356,6 +361,8 @@ 954BBD850AEDDE5300BBFD5F /* VLAppController.mm */, 954BBD980AEDE81500BBFD5F /* VLPitchTransformer.h */, 954BBD990AEDE81500BBFD5F /* VLPitchTransformer.mm */, + 955CBA4C0B2366DD001CF4A1 /* VLKeyValueUndo.h */, + 955CBA4D0B2366DD001CF4A1 /* VLKeyValueUndo.mm */, ); name = Classes; sourceTree = ""; @@ -634,6 +641,7 @@ 95009B2C0B0ECF9000EB33A4 /* CAStreamBasicDescription.cpp in Sources */, 95009B500B0ED0BB00EB33A4 /* CADebugMacros.cpp in Sources */, 95E299C00B2006F5001977D2 /* VLSheetViewLyrics.mm in Sources */, + 955CBA4F0B2366DD001CF4A1 /* VLKeyValueUndo.mm in Sources */, ); runOnlyForDeploymentPostprocessing = 0; };