VocalEasel/Sources/VLSheetViewSelection.mm

642 lines
16 KiB
Plaintext
Raw Normal View History

//
2007-04-27 06:41:34 +00:00
// File: VLSheetViewSelection.mm - Measure selection functionality
//
2007-04-27 06:41:34 +00:00
// Author(s):
//
// (MN) Matthias Neeracher
//
// Copyright © 2006-2011 Matthias Neeracher
//
#import "VLSheetView.h"
2008-05-29 18:54:30 +00:00
#import "VLSheetViewNotes.h"
#import "VLSheetViewSelection.h"
2008-05-29 18:54:30 +00:00
#import "VLSheetViewNotes.h"
#import "VLSheetViewChords.h"
#import "VLSheetViewLyrics.h"
#import "VLSheetWindow.h"
#import "VLDocument.h"
#import "VLPitchGrid.h"
2011-09-11 21:27:53 +00:00
#import "VLSoundOut.h"
#import "VLMIDIWriter.h"
2011-09-11 02:39:54 +00:00
#pragma mark VLMeasureEditable
@interface VLMeasureEditable : VLEditable {
VLSheetView * fView;
uint32_t fAnchor;
VLRegion fRegion;
}
- (VLMeasureEditable *)initWithView:(VLSheetView *)view anchor:(uint32_t)anchor;
2011-09-24 19:11:46 +00:00
- (VLMeasureEditable *)initWithView:(VLSheetView *)view anchor:(uint32_t)anchor to:(uint32_t)end;
2011-09-11 02:39:54 +00:00
- (BOOL)canExtendSelection:(VLRegion)region;
- (void)extendSelection:(VLLocation)at;
- (BOOL)hidden;
@end
@implementation VLMeasureEditable
2011-09-24 19:11:46 +00:00
- (VLMeasureEditable *)initWithView:(VLSheetView *)view anchor:(uint32_t)anchor to:(uint32_t)end
2011-09-11 02:39:54 +00:00
{
fView = view;
fAnchor = anchor;
2011-09-13 01:43:38 +00:00
dispatch_async(dispatch_get_main_queue(), ^{
2011-09-24 19:11:46 +00:00
[fView selectMeasure:fAnchor to:end];
2011-09-13 01:43:38 +00:00
});
2011-09-11 02:39:54 +00:00
return self;
}
2011-09-24 19:11:46 +00:00
- (VLMeasureEditable *)initWithView:(VLSheetView *)view anchor:(uint32_t)anchor
{
return [self initWithView:view anchor:anchor to:anchor];
}
2011-09-11 02:39:54 +00:00
- (void)dealloc
{
[fView selectMeasure:0 to:kNoMeasure];
[super dealloc];
}
- (BOOL)canExtendSelection:(VLRegion)region
{
switch ((fRegion = region)) {
case kRegionNote:
case kRegionChord:
case kRegionLyrics:
case kRegionMeasure:
return YES;
default:
return NO;
}
}
- (void)extendSelection:(VLLocation)at
{
uint32_t meas = at.fMeasure;
switch (fRegion) {
case kRegionNote:
case kRegionChord:
case kRegionLyrics:
if (at.fAt > VLFraction(0) && meas >= fAnchor)
++meas;
//
// Fall through
//
case kRegionMeasure:
meas = std::max<uint32_t>(0, std::min<uint32_t>(meas, [fView song]->CountMeasures()));
if (meas >= fAnchor) {
[fView selectMeasure:fAnchor to:meas];
} else {
[fView selectMeasure:meas to:fAnchor];
}
break;
default:
break;
}
}
- (BOOL)hidden
{
return YES;
}
@end
#pragma mark -
#pragma mark VLPlaybackEditable
2008-05-29 18:54:30 +00:00
@interface VLPlaybackEditable : VLEditable {
VLSheetView * fView;
size_t fStanza;
2011-09-11 02:03:22 +00:00
VLLocation fNote;
int fNoteVert;
uint16_t fNoteVisual;
2011-09-11 02:03:22 +00:00
VLLocation fChord;
2008-05-29 18:54:30 +00:00
}
- (VLPlaybackEditable *)initWithView:(VLSheetView *)view;
2011-09-04 23:38:01 +00:00
- (void) userEvent:(const VLMIDIUserEvent *)event;
2008-05-29 18:54:30 +00:00
- (void) highlightCursor;
2011-09-10 23:54:34 +00:00
- (BOOL) hidden;
- (void) deleteAtEndOfPlayback;
2008-05-29 18:54:30 +00:00
@end
@implementation VLPlaybackEditable
- (VLPlaybackEditable *)initWithView:(VLSheetView *)view
{
fView = view;
fStanza = 1;
2011-09-11 02:03:22 +00:00
fNote.fMeasure = 0x80000000;
fChord.fMeasure = 0x80000000;
2008-05-29 18:54:30 +00:00
return self;
}
2011-09-11 21:27:53 +00:00
- (void)dealloc
{
[fView setNeedsDisplay:YES];
}
2011-09-04 23:38:01 +00:00
- (void) userEvent:(const VLMIDIUserEvent *) event
2008-05-29 18:54:30 +00:00
{
if (event->fPitch) {
2011-09-11 02:03:22 +00:00
fNote = event->fAt;
fNoteVisual = event->fVisual & VLNote::kAccidentalsMask;
if (event->fPitch == VLNote::kNoPitch)
fNoteVert = kCursorNoPitch;
else
2011-09-11 02:03:22 +00:00
fNoteVert = VLPitchToGrid(event->fPitch, fNoteVisual, [fView song]->Properties(fNote.fMeasure).fKey);
2008-07-05 13:55:41 +00:00
fStanza = event->fStanza;
2011-09-11 02:03:22 +00:00
VLLocation end = fNote;
end.fAt = end.fAt+VLFraction(1,128);
[fView highlightTextInStanza:fStanza start:fNote end:end];
2008-05-29 18:54:30 +00:00
} else {
2011-09-11 02:03:22 +00:00
fChord = event->fAt;
2008-05-29 18:54:30 +00:00
}
2011-09-11 02:03:22 +00:00
[fView scrollMeasureToVisible:event->fAt.fMeasure+1];
2008-05-29 18:54:30 +00:00
[fView setNeedsDisplay:YES];
}
- (void) highlightCursor
{
2011-09-11 02:03:22 +00:00
if (fNote.fMeasure != 0x80000000 && fNoteVert != kCursorNoPitch)
[fView drawNoteCursor:fNoteVert at:fNote visual:fNoteVisual];
if (fChord.fMeasure != 0x80000000)
[fView highlightChord:fChord];
2008-05-29 18:54:30 +00:00
}
2011-09-10 23:54:34 +00:00
- (BOOL) hidden
{
return YES;
}
- (void) deleteAtEndOfPlayback
{
[fView setEditTarget:nil];
}
2008-05-29 18:54:30 +00:00
@end
2008-01-16 13:04:01 +00:00
@interface NSMenuItem (VLSetStateToOff)
- (void) VLSetStateToOff;
@end
@implementation NSMenuItem (VLSetStateToOff)
- (void) VLSetStateToOff
{
[self setState:NSOffState];
}
@end
2007-01-02 07:09:06 +00:00
//
// We're too lazy to properly serialize our private pasteboard format.
//
static VLSong sPasteboard;
2008-05-29 18:54:30 +00:00
extern "C" void
VLSequenceCallback(
void * inClientData,
MusicSequence inSequence, MusicTrack inTrack,
MusicTimeStamp inEventTime, const MusicEventUserData *inEventData,
MusicTimeStamp inStartSliceBeat, MusicTimeStamp inEndSliceBeat)
{
2011-09-04 23:38:01 +00:00
dispatch_async(dispatch_get_main_queue(), ^{
[(id)inClientData userEvent:(const VLMIDIUserEvent *)inEventData];
});
2008-05-29 18:54:30 +00:00
}
@implementation VLSheetView (Selection)
2011-09-11 02:39:54 +00:00
- (void)editSelection
{
2011-09-11 02:39:54 +00:00
[self setEditTarget:[[VLMeasureEditable alloc]
initWithView:self anchor:fCursorLocation.fMeasure]];
}
2011-09-11 02:39:54 +00:00
- (void)selectMeasure:(uint32_t)startMeas to:(uint32_t)endMeas
{
fSelStart = startMeas;
fSelEnd = endMeas;
fCursorRegion = kRegionMeasure;
2011-09-11 21:43:13 +00:00
VLSoundOut::Instance()->ResetSelection();
2011-09-11 02:39:54 +00:00
[self updateMenus];
[self setNeedsDisplay:YES];
}
- (NSRange)sectionsInSelection
{
NSRange sections;
int firstSection;
int lastSection;
VLSong * song = [self song];
2011-09-11 02:39:54 +00:00
if (fSelEnd != kNoMeasure) {
firstSection = song->fMeasures[fSelStart].fPropIdx;
2008-03-24 22:57:06 +00:00
lastSection = fSelEnd==fSelStart ? firstSection : song->fMeasures[fSelEnd-1].fPropIdx;
} else {
firstSection = 0;
lastSection = song->fMeasures.back().fPropIdx;
}
sections.location = firstSection;
sections.length = lastSection-firstSection+1;
return sections;
}
2007-01-21 11:34:56 +00:00
- (BOOL)validateMenuItem:(id) item
{
SEL action = [item action];
if (action == @selector(insertJumpToCoda:))
if (fSelStart == fSelEnd) {
[item setState:[self song]->fGoToCoda==fSelStart];
return YES;
} else
return NO;
else if (action == @selector(insertStartCoda:))
if (fSelStart == fSelEnd) {
[item setState:[self song]->fCoda==fSelStart];
2007-12-24 00:10:23 +00:00
return YES;
} else
return NO;
else if (action == @selector(insertBreak:))
if (fSelStart == fSelEnd && fSelStart > 0) {
2007-12-25 13:12:07 +00:00
VLSong * song = [self song];
bool checked = fSelStart < song->fMeasures.size();
if ([item tag] == 256)
checked = checked && song->DoesBeginSection(fSelStart);
else
checked = checked && song->fMeasures[fSelStart].fBreak == [item tag];
[item setState:checked];
2007-12-24 00:10:23 +00:00
2007-01-21 11:34:56 +00:00
return YES;
} else
return NO;
else
return [self validateUserInterfaceItem:item];
}
- (BOOL)validateUserInterfaceItem:(id) item
{
SEL action = [item action];
2012-05-19 22:24:34 +00:00
bool hasSelection = fSelEnd != kNoMeasure && fSelStart <= fSelEnd;
bool hasSelectionRange = fSelEnd != kNoMeasure && fSelStart < fSelEnd;
if (action == @selector(cut:)
|| action == @selector(copy:)
|| action == @selector(delete:)
)
2012-05-19 22:24:34 +00:00
return hasSelectionRange;
else if (action == @selector(editRepeat:))
2012-05-19 22:24:34 +00:00
return hasSelectionRange
&& [self song]->CanBeRepeat(fSelStart, fSelEnd);
else if (action == @selector(editRepeatEnding:))
2012-05-19 22:24:34 +00:00
return hasSelectionRange
&& [self song]->CanBeEnding(fSelStart, fSelEnd);
else if (action == @selector(paste:))
2012-05-19 22:24:34 +00:00
return hasSelection;
2011-09-08 02:10:49 +00:00
else if (action == @selector(insertMeasure:))
2012-05-19 22:24:34 +00:00
return hasSelection && !hasSelectionRange;
else
return YES;
}
- (IBAction)cut:(id)sender
{
2007-01-02 07:09:06 +00:00
[self copy:sender];
[self delete:sender];
}
- (IBAction)copy:(id)sender
{
2007-01-02 07:09:06 +00:00
NSPasteboard * pb = [NSPasteboard generalPasteboard];
NSString * pbType = [[NSBundle mainBundle] bundleIdentifier];
[pb declareTypes:[NSArray arrayWithObject:pbType] owner:nil];
if ([pb setString:@"whatever" forType:pbType])
sPasteboard = [self song]->CopyMeasures(fSelStart, fSelEnd);
}
- (IBAction)paste:(id)sender
{
2007-01-02 07:09:06 +00:00
NSPasteboard * pb = [NSPasteboard generalPasteboard];
NSString * pbType = [[NSBundle mainBundle] bundleIdentifier];
if ([pb availableTypeFromArray:[NSArray arrayWithObject:pbType]]) {
[[self document] willChangeSong];
if (![sender tag]) // Delete on paste, but not on overwrite
[self song]->DeleteMeasures(fSelStart, fSelEnd);
[self song]->PasteMeasures(fSelStart, sPasteboard, [sender tag]);
2011-09-08 00:18:07 +00:00
[self setNumStanzas: std::max<int>([self song]->CountStanzas(), fNumStanzas)];
2007-01-02 07:09:06 +00:00
[[self document] didChangeSong];
[self setNeedsDisplay:YES];
}
}
2011-09-24 19:11:46 +00:00
- (IBAction)selectAll:(id)sender
{
[self setEditTarget:[[VLMeasureEditable alloc]
initWithView:self anchor:0 to:[self song]->fMeasures.size()]];
}
- (IBAction)delete:(id)sender
{
2007-01-02 07:09:06 +00:00
[[self document] willChangeSong];
2007-05-07 04:01:29 +00:00
[self song]->DeleteMeasures(fSelStart, fSelEnd, [sender tag]);
2007-01-02 07:09:06 +00:00
[[self document] didChangeSong];
[self setNeedsDisplay:YES];
}
2011-09-08 02:10:49 +00:00
- (IBAction)insertMeasure:(id)sender
{
[[self document] willChangeSong];
[self song]->InsertMeasure(fSelStart);
[[self document] didChangeSong];
[self setNeedsDisplay:YES];
}
- (IBAction)editRepeat:(id)sender
{
int volta;
[self song]->CanBeRepeat(fSelStart, fSelEnd, &volta);
[fRepeatMsg setStringValue:
[NSString stringWithFormat:@"Repeat measures %d through %d",
fSelStart+1, fSelEnd]];
[NSApp beginSheet:fRepeatSheet modalForWindow:[self window]
modalDelegate:self
didEndSelector:@selector(didEndRepeatSheet:returnCode:contextInfo:)
contextInfo:nil];
}
- (void)didEndRepeatSheet:(NSWindow *)sheet returnCode:(int)returnCode
contextInfo:(void *)ctx
{
switch (returnCode) {
case NSAlertFirstButtonReturn:
[[self document] willChangeSong];
[self song]->AddRepeat(fSelStart, fSelEnd, [[self document] repeatVolta]);
[self setNeedsDisplay:YES];
[[self document] didChangeSong];
break;
case NSAlertThirdButtonReturn:
[[self document] willChangeSong];
[self song]->DelRepeat(fSelStart, fSelEnd);
[[self document] didChangeSong];
[self setNeedsDisplay:YES];
break;
default:
break;
}
[sheet orderOut:self];
}
- (IBAction)editRepeatEnding:(id)sender
{
[self song]->CanBeEnding(fSelStart, fSelEnd, &fVolta, &fVoltaOK);
[fEndingMsg setStringValue:
[NSString stringWithFormat:@"Ending in measures %d through %d applies to repeats:",
fSelStart+1, fSelEnd]];
[NSApp beginSheet:fEndingSheet modalForWindow:[self window]
modalDelegate:self
didEndSelector:@selector(didEndEndingSheet:returnCode:contextInfo:)
contextInfo:nil];
}
- (void)didEndEndingSheet:(NSWindow *)sheet returnCode:(int)returnCode
contextInfo:(void *)ctx
{
switch (returnCode) {
case NSAlertFirstButtonReturn:
[[self document] willChangeSong];
[self song]->AddEnding(fSelStart, fSelEnd, fVolta);
[self setNeedsDisplay:YES];
[[self document] didChangeSong];
break;
case NSAlertThirdButtonReturn:
[[self document] willChangeSong];
[self song]->DelEnding(fSelStart, fSelEnd);
[[self document] didChangeSong];
[self setNeedsDisplay:YES];
break;
default:
break;
}
[sheet orderOut:self];
}
//
// Data source for endings
//
2007-04-24 18:32:32 +00:00
- (int)numberOfRowsInTableView:(NSTableView *)aTableView
{
return 1;
}
- (id)tableView:(NSTableView*)tv objectValueForTableColumn:(NSTableColumn *)col
2007-04-24 18:32:32 +00:00
row:(int)rowIndex
{
int mask = [[col identifier] intValue];
return (fVoltaOK & mask) ? [NSNumber numberWithBool:(fVolta & mask)] : nil;
}
2007-04-24 18:32:32 +00:00
- (void)tableView:(NSTableView *)tv setObjectValue:(id)val forTableColumn:(NSTableColumn *)col row:(int)rowIndex
{
int mask = [[col identifier] intValue];
if ([val boolValue])
fVolta |= mask;
else
fVolta &= ~mask;
}
2007-01-21 11:34:56 +00:00
- (IBAction)insertJumpToCoda:(id)sender
{
[[self document] willChangeSong];
VLSong * song = [self song];
if (song->fGoToCoda == fSelStart)
song->fGoToCoda = -1;
else
song->fGoToCoda = fSelStart;
[self setNeedsDisplay:YES];
[[self document] didChangeSong];
}
- (IBAction)insertStartCoda:(id)sender
{
[[self document] willChangeSong];
VLSong * song = [self song];
if (song->fCoda == fSelStart)
song->fCoda = -1;
else
song->fCoda = fSelStart;
[self setNeedsDisplay:YES];
[[self document] didChangeSong];
}
2007-12-24 00:10:23 +00:00
- (IBAction)insertBreak:(id)sender
{
[[self document] willChangeSong];
VLSong * song = [self song];
2007-12-25 13:12:07 +00:00
if ([sender tag] == 256) {
if (song->DoesBeginSection(fSelStart))
song->DelSection(fSelStart);
else
song->AddSection(fSelStart);
} else {
VLMeasure & meas = song->fMeasures[fSelStart];
if (meas.fBreak == [sender tag])
meas.fBreak = 0;
else
meas.fBreak = [sender tag];
}
2007-12-24 00:10:23 +00:00
fNeedsRecalc = kRecalc;
[self setNeedsDisplay:YES];
[[self document] didChangeSong];
}
2008-01-16 13:04:01 +00:00
inline int KeyModeTag(const VLProperties & prop)
{
return (prop.fKey << 8) | (prop.fMode & 0xFF);
}
- (void)updateKeyMenu
{
NSMenu *menu = [fKeyMenu menu];
NSRange sections = [self sectionsInSelection];
VLSong *song = [self song];
[[menu itemArray] makeObjectsPerformSelector:@selector(VLSetStateToOff)];
int firstTag = KeyModeTag(song->fProperties[sections.location]);
[fKeyMenu selectItemWithTag:firstTag];
int firstState = NSOnState;
while (--sections.length > 0) {
int thisTag = KeyModeTag(song->fProperties[++sections.location]);
if (thisTag != firstTag) {
firstState = NSMixedState;
[[menu itemWithTag:thisTag] setState:NSMixedState];
}
}
[[menu itemWithTag:firstTag] setState:firstState];
}
inline int TimeTag(const VLProperties & prop)
{
return (prop.fTime.fNum << 8) | prop.fTime.fDenom;
}
- (void)updateTimeMenu
{
NSMenu *menu = [fTimeMenu menu];
NSRange sections = [self sectionsInSelection];
VLSong *song = [self song];
[[menu itemArray] makeObjectsPerformSelector:@selector(VLSetStateToOff)];
int firstTag = TimeTag(song->fProperties[sections.location]);
[fTimeMenu selectItemWithTag:firstTag];
int firstState = NSOnState;
while (--sections.length > 0) {
int thisTag = TimeTag(song->fProperties[++sections.location]);
if (thisTag != firstTag) {
firstState = NSMixedState;
[[menu itemWithTag:thisTag] setState:NSMixedState];
}
}
[[menu itemWithTag:firstTag] setState:firstState];
}
- (void)updateDivisionMenu
{
NSMenu *menu = [fDivisionMenu menu];
NSRange sections = [self sectionsInSelection];
VLSong *song = [self song];
[[menu itemArray] makeObjectsPerformSelector:@selector(VLSetStateToOff)];
int firstTag = song->fProperties[sections.location].fDivisions;
[fDivisionMenu selectItemWithTag:firstTag];
int firstState = NSOnState;
while (--sections.length > 0) {
int thisTag = song->fProperties[++sections.location].fDivisions;
if (thisTag != firstTag) {
firstState = NSMixedState;
[[menu itemWithTag:thisTag] setState:NSMixedState];
}
}
[[menu itemWithTag:firstTag] setState:firstState];
}
2008-01-23 01:20:09 +00:00
- (void)updateGrooveMenu
{
NSRange sections = [self sectionsInSelection];
VLSong * song = [self song];
NSMutableArray* grooves = [NSMutableArray array];
while (sections.length-- > 0) {
NSString * groove =
[NSString stringWithUTF8String:
song->fProperties[sections.location++].fGroove.c_str()];
if (![grooves containsObject:groove])
[grooves addObject:groove];
}
int selected = [grooves count];
int history = [fGrooveMenu numberOfItems]-2;
while (history-- > 0) {
NSString * groove = [fGrooveMenu itemTitleAtIndex:2];
[fGrooveMenu removeItemAtIndex:2];
if (![grooves containsObject:groove])
[grooves addObject:groove];
}
[fGrooveMenu addItemsWithTitles:grooves];
[fGrooveMenu selectItemAtIndex:2];
if (selected > 1)
while (selected-- > 0)
[[fGrooveMenu itemAtIndex:selected+2] setState:NSMixedState];
[[NSUserDefaults standardUserDefaults] setObject:grooves forKey:@"VLGrooves"];
}
2008-01-16 13:04:01 +00:00
- (void)updateMenus
{
[self updateKeyMenu];
[self updateTimeMenu];
[self updateDivisionMenu];
2008-01-23 01:20:09 +00:00
[self updateGrooveMenu];
2008-01-16 13:04:01 +00:00
}
2011-09-11 21:27:53 +00:00
- (void (^)()) willPlaySequence:(MusicSequence)music
2008-05-29 18:54:30 +00:00
{
2011-09-11 21:27:53 +00:00
uint32_t selStart = fSelStart;
uint32_t selEnd = fSelEnd;
2008-05-29 18:54:30 +00:00
VLEditable * e =
[[VLPlaybackEditable alloc] initWithView:self];
[self setEditTarget:e];
MusicSequenceSetUserCallback(music, VLSequenceCallback, e);
2011-09-11 21:27:53 +00:00
return [Block_copy(^{
if (selEnd != kNoMeasure) {
VLMIDIUtilities locator(music);
VLLocation start = {selStart, VLFraction(0)};
VLLocation end = {selEnd, VLFraction(0)};
VLSoundOut::Instance()->SetStart(locator.Find(start));
if (selEnd > selStart) {
VLSoundOut::Instance()->SetEnd(locator.Find(end));
[self selectMeasure:selStart to:selEnd];
}
}
}) autorelease];
2008-05-29 18:54:30 +00:00
}
@end