Added undo support

This commit is contained in:
Matthias Neeracher 2006-12-04 07:04:24 +00:00
parent 94f23e92f2
commit 9af760d1dc
13 changed files with 227 additions and 34 deletions

View File

@ -8,6 +8,7 @@
#import "VLModel.h"
#import <Cocoa/Cocoa.h>
#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

View File

@ -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,6 +168,8 @@
- (void) setKey:(int)key transpose:(BOOL)transpose
{
[self willChangeSong];
VLProperties & prop = song->fProperties.front();
if (transpose)
@ -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

18
Sources/VLKeyValueUndo.h Normal file
View File

@ -0,0 +1,18 @@
//
// VLKeyValueUndo.h
// Vocalese
//
// Created by Matthias Neeracher on 12/3/06.
// Copyright 2006 __MyCompanyName__. All rights reserved.
//
#import <Cocoa/Cocoa.h>
@interface VLKeyValueUndo : NSObject {
id owner;
NSDictionary * keysAndNames;
}
- (id)initWithOwner:(id)owner keysAndNames:(NSDictionary *)keysAndNames;
@end

54
Sources/VLKeyValueUndo.mm Normal file
View File

@ -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

View File

@ -32,9 +32,9 @@
for (size_t m=0; m<song->CountMeasures(); ++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";
}

View File

@ -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; measure<fMeasures.size(); ++measure) {
bool useSharps = fMeasures[measure].fProperties->fKey>=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);

View File

@ -233,20 +233,21 @@ typedef std::list<VLChord> VLChordList;
typedef std::list<VLLyricsNote> VLNoteList;
struct VLMeasure {
VLProperties * fProperties;
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<VLProperties> fProperties;
std::vector<VLProperties> fProperties;
std::vector<VLMeasure> fMeasures;
void AddChord(VLChord chord, size_t measure, VLFraction at);

View File

@ -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

View File

@ -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

View File

@ -13,7 +13,7 @@
VLSong * fSong;
size_t fStanza;
size_t fMeasure;
VLFraction fAt;
VLFract fAt;
}
- (VLLyricsEditable *)initWithView:(VLSheetView *)view

View File

@ -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

View File

@ -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, &noteDur, &triplet);
prop.PartialNote(at, dur, &partialDur);
prop.VisualNote(at, partialDur, &noteDur, &triplet);
if (pitch != VLNote::kNoPitch) {
VLMusicElement accidental;

View File

@ -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 = "<group>"; };
954BBD980AEDE81500BBFD5F /* VLPitchTransformer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = VLPitchTransformer.h; path = Sources/VLPitchTransformer.h; sourceTree = "<group>"; };
954BBD990AEDE81500BBFD5F /* VLPitchTransformer.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = VLPitchTransformer.mm; path = Sources/VLPitchTransformer.mm; sourceTree = "<group>"; };
955CBA4C0B2366DD001CF4A1 /* VLKeyValueUndo.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = VLKeyValueUndo.h; path = Sources/VLKeyValueUndo.h; sourceTree = "<group>"; };
955CBA4D0B2366DD001CF4A1 /* VLKeyValueUndo.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = VLKeyValueUndo.mm; path = Sources/VLKeyValueUndo.mm; sourceTree = "<group>"; };
955E58E3095658AB0045FDA5 /* VLModel.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = VLModel.h; path = Sources/VLModel.h; sourceTree = "<group>"; };
955E58E4095658AB0045FDA5 /* VLModel.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = VLModel.cpp; path = Sources/VLModel.cpp; sourceTree = "<group>"; };
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 = "<group>";
@ -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;
};