VocalEasel/Sources/VLSheetView.mm

1085 lines
30 KiB
Plaintext
Raw Permalink Normal View History

2006-09-11 02:49:56 +00:00
//
2007-04-27 06:41:34 +00:00
// File: VLSheetView.mm - Lead sheet editing view
2006-09-11 02:49:56 +00:00
//
2007-04-27 06:41:34 +00:00
// Author(s):
//
// (MN) Matthias Neeracher
//
2018-02-19 00:59:23 +00:00
// Copyright © 2005-2018 Matthias Neeracher
2006-09-11 02:49:56 +00:00
//
#import "VLSheetView.h"
#import "VLSheetViewInternal.h"
#import "VLSheetViewChords.h"
#import "VLSheetViewNotes.h"
2006-12-02 09:02:44 +00:00
#import "VLSheetViewLyrics.h"
#import "VLSheetViewSelection.h"
#import "VLSoundOut.h"
2007-04-15 05:22:30 +00:00
#import "VLGrooveController.h"
2006-09-11 02:49:56 +00:00
#import "VLPitchGrid.h"
2006-09-11 02:49:56 +00:00
#import "VLDocument.h"
#include <cmath>
@implementation VLSheetView
@synthesize
numTopLedgers = fNumTopLedgers,
numBotLedgers = fNumBotLedgers,
numStanzas = fNumStanzas;
2006-09-11 02:49:56 +00:00
static NSString * sElementNames[kMusicElements] = {
@"g-clef",
@"flat",
@"sharp",
@"natural",
@"doubleflat",
@"doublesharp",
2006-09-11 02:49:56 +00:00
@"whole-notehead",
@"half-notehead",
@"notehead",
@"whole-rest",
@"half-rest",
@"quarter-rest",
@"eighth-rest",
@"sixteenth-rest",
@"thirtysecondth-rest",
@"eighth-flag",
@"sixteenth-flag",
@"thirtysecondth-flag",
@"notecursor",
2006-10-21 09:23:37 +00:00
@"flatcursor",
@"sharpcursor",
@"naturalcursor",
@"doubleflatcursor",
@"doublesharpcursor",
2006-10-21 09:23:37 +00:00
@"restcursor",
2007-01-21 11:34:56 +00:00
@"killcursor",
2007-04-22 02:59:52 +00:00
@"extendcursor",
2007-01-21 11:34:56 +00:00
@"coda"
2006-09-11 02:49:56 +00:00
};
static float sSharpPos[] = {
2006-10-02 05:29:37 +00:00
4.0f*kLineH, // F#
2.5f*kLineH, // C#
4.5f*kLineH, // G#
3.0f*kLineH, // D#
1.5f*kLineH, // A#
3.5f*kLineH, // E#
2.0f*kLineH, // B#
2006-09-11 02:49:56 +00:00
};
static float sFlatPos[] = {
2006-10-02 05:29:37 +00:00
2.0f*kLineH, // Bb
3.5f*kLineH, // Eb
1.5f*kLineH, // Ab
3.0f*kLineH, // Db
1.0f*kLineH, // Gb
2.5f*kLineH, // Cb
0.5f*kLineH, // Fb
2006-09-11 02:49:56 +00:00
};
- (id)initWithFrame:(NSRect)frame
{
self = [super initWithFrame:frame];
if (self) {
2006-12-02 23:05:12 +00:00
NSBundle * b = [NSBundle mainBundle];
fMusic = new NSImage * [kMusicElements];
for (int i=0; i<kMusicElements; ++i) {
NSString * name =
[b pathForResource:sElementNames[i] ofType:@"eps"
inDirectory:@"Music"];
fMusic[i] = [[NSImage alloc] initByReferencingFile: name];
2018-08-26 23:16:54 +00:00
if (![sElementNames[i] containsString:@"cursor"]) {
[fMusic[i] lockFocus];
[NSColor.textColor set];
NSRectFillUsingOperation(
NSMakeRect(0, 0, fMusic[i].size.width, fMusic[i].size.height),
NSCompositeSourceAtop);
[fMusic[i] unlockFocus];
}
2006-12-02 23:05:12 +00:00
NSSize sz = [fMusic[i] size];
sz.width *= kImgScale;
sz.height*= kImgScale;
[fMusic[i] setSize:sz];
2006-09-11 02:49:56 +00:00
}
fNeedsRecalc = kFirstRecalc;
fClickMode = ' ';
fDisplayScale = 1.0f;
fCursorVertPos = 0;
fCursorVisual = 0;
2006-12-28 05:03:28 +00:00
fSelStart = 0;
2011-09-11 02:39:54 +00:00
fSelEnd = kNoMeasure;
fNumTopLedgers = 0;
fNumBotLedgers = 2;
fNumStanzas = 2;
2007-05-04 05:21:16 +00:00
fLastMeasures = 0;
fUndo = [[VLKeyValueUndo alloc]
initWithOwner:self
keysAndNames:[NSDictionary dictionaryWithObjectsAndKeys:
@"", @"numTopLedgers",
@"", @"numBotLedgers",
@"", @"numStanzas",
nil]];
2006-09-11 02:49:56 +00:00
}
return self;
}
- (void)dealloc
{
[self removeObservers:[self document]];
delete [] fMusic;
[fUndo release];
[super dealloc];
}
- (BOOL)acceptsFirstResponder
{
return YES;
}
2006-09-11 02:49:56 +00:00
- (VLDocument *) document
{
return [[[self window] windowController] document];
}
- (VLEditable *) editTarget
{
return [[[self window] windowController] editTarget];
}
- (void) setEditTarget:(VLEditable *)editable
{
[[[self window] windowController] setEditTarget:editable];
}
2011-09-10 23:15:21 +00:00
- (void) updateEditTarget
{
[fFieldEditor takeStringValueFrom:[self editTarget]];
}
2006-09-11 02:49:56 +00:00
- (VLSong *) song
{
return [[self document] song];
}
- (NSImage *) musicElement:(VLMusicElement)elt
{
2006-12-02 23:05:12 +00:00
return fMusic[elt];
2006-09-11 02:49:56 +00:00
}
2006-10-02 05:29:37 +00:00
- (float) systemY:(int)system
{
NSRect b = [self bounds];
return kSystemBaseline+b.origin.y+b.size.height-(system+1)*kSystemH;
2006-10-02 05:29:37 +00:00
}
2011-09-05 22:22:57 +00:00
- (int) systemForPoint:(NSPoint *)loc
{
NSRect b = [self bounds];
CGFloat top = b.origin.y+b.size.height;
int system = (top-loc->y) / kSystemH;
loc->y -= top-(system+1)*kSystemH;
return system;
}
- (int) gridInSection:(int)section withPitch:(int)pitch visual:(uint16_t)visual
{
int key = [self song]->fProperties[section].fKey;
return VLPitchToGrid(pitch, visual, key);
}
2006-10-21 09:23:37 +00:00
- (float) noteYInGrid:(int)vertPos
{
return (vertPos*0.5f - 1.0) * kLineH;
}
- (float) noteYInSection:(int)section withPitch:(int)pitch visual:(uint16_t *)visual
2006-10-21 09:23:37 +00:00
{
int key = [self song]->fProperties[section].fKey;
int grid = VLPitchToGrid(pitch, *visual, key);
return [self noteYInGrid:grid];
2006-10-21 09:23:37 +00:00
}
- (float) noteYInSection:(int)section withPitch:(int)pitch
2006-09-11 02:49:56 +00:00
{
int key = [self song]->fProperties[section].fKey;
uint16_t visual = 0;
int grid = VLPitchToGrid(pitch, visual, key);
return [self noteYInGrid:grid];
}
2006-10-21 09:23:37 +00:00
- (VLMusicElement)accidentalForVisual:(uint16_t)visual
{
switch (visual & VLNote::kAccidentalsMask) {
case VLNote::kWantSharp:
return kMusicSharp;
case VLNote::kWantFlat:
return kMusicFlat;
case VLNote::kWant2Sharp:
return kMusic2Sharp;
case VLNote::kWant2Flat:
return kMusic2Flat;
case VLNote::kWantNatural:
return kMusicNatural;
default:
return kMusicNothing;
}
}
2006-10-21 09:23:37 +00:00
- (float) noteYInMeasure:(int)measure withGrid:(int)vertPos
{
2007-12-23 12:45:17 +00:00
return [self systemY:fLayout->SystemForMeasure(measure)]
+ [self noteYInGrid:vertPos];
}
2011-09-11 02:03:22 +00:00
- (float) noteXAt:(VLLocation)at
2006-09-11 02:49:56 +00:00
{
2011-09-11 02:03:22 +00:00
return fLayout->NotePosition(at);
2006-09-11 02:49:56 +00:00
}
2006-10-09 07:28:49 +00:00
- (void) scrollMeasureToVisible:(int)measure
{
2007-12-23 12:45:17 +00:00
const int system = fLayout->SystemForMeasure(measure);
const VLSystemLayout & kLayout = (*fLayout)[system];
NSRect r = NSMakeRect(
fLayout->MeasurePosition(measure),
[self systemY:system]-kSystemBaseline,
kLayout.MeasureWidth(), kSystemH);
2006-10-09 07:28:49 +00:00
[self scrollRectToVisible:r];
}
- (void) setTrackingRect
{
2008-01-29 03:02:25 +00:00
NSRect r = [self visibleRect];
NSPoint mouse =
[self convertPoint:[[self window] mouseLocationOutsideOfEventStream]
fromView: nil];
BOOL within = [self mouse:mouse inRect:r];
fCursorTracking = [self addTrackingRect:r owner:self
userData:nil assumeInside:within];
[[self window] setAcceptsMouseMovedEvents:within];
}
- (void) clearTrackingRect
{
[self removeTrackingRect:fCursorTracking];
}
-(void)resetCursorRects
{
[super resetCursorRects];
[self clearTrackingRect];
[self setTrackingRect];
}
-(void)viewWillMoveToWindow:(NSWindow *)win
{
2008-01-29 03:02:25 +00:00
if (!win && [self window])
[self clearTrackingRect];
}
-(void)viewDidMoveToWindow
{
if ([self window]) {
[self setTrackingRect];
[[self window] makeFirstResponder:self];
}
}
2006-09-11 02:49:56 +00:00
- (void) recalculateDimensions
{
NSScrollView * scroll = [self enclosingScrollView];
2006-09-11 02:49:56 +00:00
NSSize sz = [scroll contentSize];
2006-10-02 05:29:37 +00:00
2007-12-23 12:45:17 +00:00
delete fLayout;
2007-12-25 19:44:49 +00:00
fLayout = new VLLayout(*[self song], sz.width / fDisplayScale);
2012-05-19 22:16:40 +00:00
sz.height = std::max<float>(sz.height, std::max(2.0f, fLayout->NumSystems()+0.25f)*kSystemH*fDisplayScale);
2006-09-11 02:49:56 +00:00
2006-11-06 15:49:50 +00:00
NSSize boundsSz = {sz.width / fDisplayScale, sz.height / fDisplayScale};
2006-10-02 05:29:37 +00:00
2006-11-06 15:49:50 +00:00
[self setFrameSize:sz];
[self setBoundsSize:boundsSz];
2006-10-02 05:29:37 +00:00
[self setNeedsDisplay:YES];
if (fNeedsRecalc == kFirstRecalc) {
NSView *dv = [scroll documentView];
NSView *cv = [scroll contentView];
[dv scrollPoint:
NSMakePoint(0.0, NSMaxY([dv frame])-NSHeight([cv bounds]))];
}
2007-12-23 12:45:17 +00:00
fLastMeasures = [self song]->CountMeasures();
fNeedsRecalc = kNoRecalc;
2006-09-11 02:49:56 +00:00
}
- (void)needsRecalculation
{
fNeedsRecalc = kRecalc;
[self setNeedsDisplay:YES];
}
2007-12-23 23:07:27 +00:00
const char * sBreak[3] = {"", "\xE2\xA4\xBE", "\xE2\x8E\x98"};
- (void)drawGridForSystem:(int)system
2006-09-11 02:49:56 +00:00
{
2006-10-02 05:29:37 +00:00
static NSDictionary * sMeasNoFont = nil;
2007-12-23 23:07:27 +00:00
static NSDictionary * sBreakFont = nil;
2006-10-02 05:29:37 +00:00
if (!sMeasNoFont)
sMeasNoFont =
[[NSDictionary alloc] initWithObjectsAndKeys:
2018-08-26 23:16:54 +00:00
[NSFont fontWithName: @"Helvetica" size: 10], NSFontAttributeName,
NSColor.textColor, NSForegroundColorAttributeName,
2006-10-02 05:29:37 +00:00
nil];
2007-12-23 23:07:27 +00:00
if (!sBreakFont)
sBreakFont =
[[NSDictionary alloc] initWithObjectsAndKeys:
2018-08-26 23:16:54 +00:00
[NSFont fontWithName: @"Symbol" size: 30], NSFontAttributeName,
NSColor.textColor, NSForegroundColorAttributeName,
nil];
2006-10-02 05:29:37 +00:00
2007-12-23 12:45:17 +00:00
const VLSystemLayout & kLayout = (*fLayout)[system];
const VLSong * song = [self song];
2007-12-23 13:14:09 +00:00
const VLProperties & kProp = song->Properties(fLayout->FirstMeasure(system));
2007-12-23 12:45:17 +00:00
const float kSystemY = [self systemY:system];
const float kLineW = (*fLayout)[system].SystemWidth();
const float kMeasureW = kLayout.MeasureWidth();
2006-09-11 02:49:56 +00:00
NSBezierPath * bz = [NSBezierPath bezierPath];
//
// Draw lines
//
2018-08-26 23:16:54 +00:00
[NSColor.textColor set];
2006-09-11 02:49:56 +00:00
[bz setLineWidth:0.0];
for (int line = 0; line<5; ++line) {
const float y = kSystemY+line*kLineH;
[bz moveToPoint: NSMakePoint(kLineX, y)];
[bz lineToPoint: NSMakePoint(kLineX+kLineW, y)];
2006-09-11 02:49:56 +00:00
}
[bz stroke];
[bz removeAllPoints];
//
// Draw measure lines
//
[bz setLineWidth:2.0];
2007-12-23 12:45:17 +00:00
int m = fLayout->FirstMeasure(system);
for (int measure = 0; measure<=kLayout.NumMeasures(); ++measure, ++m) {
const float kDblLineOff = 1.5f;
const float kThick = 2.5f;
const float kThin = 1.0f;
const float kDotOff = 4.5f;
const float kDotRadius = 2.0f;
const float kVoltaTextOff = 7.0f;
2007-12-23 12:45:17 +00:00
const float x = kLayout.MeasurePosition(measure);
const float yy = kSystemY+4.0f*kLineH;
bool repeat;
size_t volta;
bool dotsPrecede= measure != 0 &&
(song->DoesEndRepeat(m)
|| (song->DoesEndEnding(m, &repeat) && repeat));
2007-12-23 12:45:17 +00:00
bool dotsFollow = measure<kLayout.NumMeasures() && song->DoesBeginRepeat(m);
if (!dotsPrecede && !dotsFollow) {
//
// Regular
//
[bz moveToPoint: NSMakePoint(x, kSystemY)];
[bz lineToPoint: NSMakePoint(x, yy)];
[bz stroke];
[bz removeAllPoints];
} else {
[bz stroke];
[bz removeAllPoints];
[bz setLineWidth: dotsFollow ? kThick : kThin];
[bz moveToPoint: NSMakePoint(x-kDblLineOff, kSystemY)];
[bz lineToPoint: NSMakePoint(x-kDblLineOff, yy)];
[bz stroke];
[bz removeAllPoints];
[bz setLineWidth: dotsPrecede ? kThick : kThin];
[bz moveToPoint: NSMakePoint(x+kDblLineOff, kSystemY)];
[bz lineToPoint: NSMakePoint(x+kDblLineOff, yy)];
[bz stroke];
[bz removeAllPoints];
[bz setLineWidth:2.0];
if (dotsPrecede) {
[bz appendBezierPathWithOvalInRect:
NSMakeRect(x-kDotOff-kDotRadius,
kSystemY+1.5*kLineH-kDotRadius,
2.0f*kDotRadius, 2.0f*kDotRadius)];
[bz appendBezierPathWithOvalInRect:
NSMakeRect(x-kDotOff-kDotRadius,
kSystemY+2.5*kLineH-kDotRadius,
2.0f*kDotRadius, 2.0f*kDotRadius)];
}
if (dotsFollow) {
[bz appendBezierPathWithOvalInRect:
NSMakeRect(x+kDotOff-kDotRadius,
kSystemY+1.5*kLineH-kDotRadius,
2.0f*kDotRadius, 2.0f*kDotRadius)];
[bz appendBezierPathWithOvalInRect:
NSMakeRect(x+kDotOff-kDotRadius,
kSystemY+2.5*kLineH-kDotRadius,
2.0f*kDotRadius, 2.0f*kDotRadius)];
}
[bz fill];
[bz removeAllPoints];
}
2007-12-23 12:45:17 +00:00
if (measure<kLayout.NumMeasures()) {
if (song->DoesBeginEnding(m, 0, &volta)) {
[bz setLineWidth:kThin];
[bz moveToPoint: NSMakePoint(x+kDblLineOff, yy+0.5f*kLineH)];
[bz lineToPoint: NSMakePoint(x+kDblLineOff, yy+2.0f*kLineH)];
2007-12-23 12:45:17 +00:00
[bz lineToPoint: NSMakePoint(x+0.5f*kMeasureW, yy+2.0f*kLineH)];
[bz stroke];
[bz removeAllPoints];
[bz setLineWidth:2.0];
NSString * vs = nil;
for (size_t v=0; v<8; ++v)
if (volta & (1<<v))
if (vs)
vs = [NSString stringWithFormat:@"%@, %d", vs, v+1];
else
vs = [NSString stringWithFormat:@"%d", v+1];
[vs drawAtPoint: NSMakePoint(x+kVoltaTextOff, kSystemY+kMeasNoY)
withAttributes: sMeasNoFont];
}
if (song->DoesEndEnding(m+1, &repeat)) {
[bz setLineWidth:kThin];
2007-12-23 12:45:17 +00:00
[bz moveToPoint: NSMakePoint(x+0.5f*kMeasureW, yy+2.0f*kLineH)];
[bz lineToPoint: NSMakePoint(x+kMeasureW-kDblLineOff, yy+2.0f*kLineH)];
if (repeat)
2007-12-23 12:45:17 +00:00
[bz lineToPoint: NSMakePoint(x+kMeasureW-kDblLineOff, yy+0.5f*kLineH)];
[bz stroke];
[bz removeAllPoints];
[bz setLineWidth:2.0];
}
2007-01-21 11:34:56 +00:00
if (song->fGoToCoda == m || song->fCoda == m)
[[self musicElement:kMusicCoda] drawAllAtPoint:NSMakePoint(x+kCodaX, yy+kCodaY)];
}
2006-09-11 02:49:56 +00:00
}
//
// Draw division lines
//
[bz setLineWidth:0.0];
2018-08-26 23:16:54 +00:00
[NSColor.systemGrayColor set];
2007-12-23 12:45:17 +00:00
for (int measure = 0; measure<kLayout.NumMeasures(); ++measure) {
const float mx = kLayout.MeasurePosition(measure);
const float y0 = kSystemY-(fNumBotLedgers+1)*kLineH;
const float yy = kSystemY+(fNumTopLedgers+5)*kLineH;
2007-12-23 12:45:17 +00:00
for (int group = 0; group < kLayout.NumGroups(); ++group) {
for (int div = 0; div < kLayout.DivPerGroup(); ++div) {
const float x = mx+(group*(kLayout.DivPerGroup()+1)+div+1)*kNoteW;
[bz moveToPoint: NSMakePoint(x, y0)];
[bz lineToPoint: NSMakePoint(x, yy)];
2006-09-11 02:49:56 +00:00
}
}
}
2006-09-11 02:49:56 +00:00
[bz stroke];
//
// Draw clef
//
[[self musicElement:kMusicGClef] drawAllAtPoint:NSMakePoint(kClefX, kSystemY+kClefY)];
//
// Draw measure #
//
2007-12-23 12:45:17 +00:00
[[NSString stringWithFormat:@"%d", fLayout->FirstMeasure(system)+1]
drawAtPoint: NSMakePoint(kMeasNoX, kSystemY+kMeasNoY)
withAttributes: sMeasNoFont];
//
// Draw key (sharps & flats)
//
2007-12-23 12:45:17 +00:00
if (kProp.fKey > 0) {
float x = kClefX+kClefW;
2007-12-23 12:45:17 +00:00
for (int i=0; i<kProp.fKey; ++i) {
[[self musicElement:kMusicSharp] drawAllAtPoint:NSMakePoint(x, kSystemY+sSharpPos[i]+kSharpY)];
x += kAccW;
}
2007-12-23 12:45:17 +00:00
} else if (kProp.fKey < 0) {
float x = kClefX+kClefW;
2007-12-23 12:45:17 +00:00
for (int i=0; -i>kProp.fKey; ++i) {
[[self musicElement: kMusicFlat] drawAllAtPoint:NSMakePoint(x, kSystemY+sFlatPos[i]+kFlatY)];
x += kAccW;
2006-09-11 02:49:56 +00:00
}
}
2007-12-23 23:07:27 +00:00
//
// Draw break character
//
int breakType = 0;
int nextMeasure = fLayout->FirstMeasure(system+1);
if (nextMeasure < song->fMeasures.size())
breakType = song->fMeasures[nextMeasure].fBreak;
if (breakType)
[[NSString stringWithUTF8String:sBreak[breakType]]
drawAtPoint: NSMakePoint(kLineX+kLineW+kBreakX, kSystemY+kBreakY)
withAttributes: sBreakFont];
}
2006-09-11 02:49:56 +00:00
2008-05-29 18:54:30 +00:00
- (NSColor *)notesBackgroundColorForSystem:(int)system
{
NSArray * colors = [NSColor controlAlternatingRowBackgroundColors];
return [colors objectAtIndex:0];
}
- (NSColor *)textBackgroundColorForSystem:(int)system
{
2007-12-24 11:15:52 +00:00
const VLSong * song = [self song];
const bool kAltColors = song->fMeasures[fLayout->FirstMeasure(system)].fPropIdx & 1;
NSArray * colors = [NSColor controlAlternatingRowBackgroundColors];
2008-05-29 18:54:30 +00:00
NSColor * color= [colors objectAtIndex:1];
2007-12-24 11:15:52 +00:00
if (kAltColors) {
2011-08-27 22:12:32 +00:00
CGFloat hue, saturation, brightness, alpha;
2007-12-24 11:15:52 +00:00
2008-05-29 18:54:30 +00:00
[[color colorUsingColorSpaceName:NSCalibratedRGBColorSpace] getHue:&hue saturation:&saturation
2007-12-24 11:15:52 +00:00
brightness:&brightness alpha:&alpha];
if (saturation) // Color
hue = fmod(hue-0.5f, 1.0f);
else // Black & white
brightness -= 0.05f;
2008-05-29 18:54:30 +00:00
color = [NSColor colorWithCalibratedHue:hue saturation:saturation
brightness:brightness alpha:alpha];
2007-12-24 11:15:52 +00:00
}
2008-05-29 18:54:30 +00:00
return color;
}
- (void)drawBackgroundForSystem:(int)system
{
const float kSystemY = [self systemY:system];
const float kLineW = (*fLayout)[system].SystemWidth();
[NSGraphicsContext saveGraphicsState];
2008-05-29 18:54:30 +00:00
[[self textBackgroundColorForSystem:system] setFill];
[NSBezierPath fillRect:
NSMakeRect(kLineX, kSystemY-kSystemBaseline,
kLineW, fNumStanzas*kLyricsH)];
[NSBezierPath fillRect:
NSMakeRect(kLineX, kSystemY+kChordY, kLineW, kChordH)];
2008-05-29 18:54:30 +00:00
[[self notesBackgroundColorForSystem:system] setFill];
[NSBezierPath fillRect:
NSMakeRect(kLineX, kSystemY-kSystemBaseline+fNumStanzas*kLyricsH,
kLineW, kSystemBaseline+kChordY-fNumStanzas*kLyricsH)];
[NSGraphicsContext restoreGraphicsState];
}
2006-12-28 05:03:28 +00:00
- (void)highlightSelectionForSystem:(int)system
{
2011-09-11 02:39:54 +00:00
uint32_t startMeas =
std::max<uint32_t>(fSelStart-fLayout->FirstMeasure(system), 0);
uint32_t endMeas =
std::min<uint32_t>(fSelEnd-fLayout->FirstMeasure(system), (*fLayout)[system].NumMeasures());
const float kRawSystemY = [self systemY:system]-kSystemBaseline;
2007-12-23 12:45:17 +00:00
const VLSystemLayout & kLayout = (*fLayout)[system];
2006-12-28 05:03:28 +00:00
[NSGraphicsContext saveGraphicsState];
[[NSColor selectedTextBackgroundColor] setFill];
if (fSelStart == fSelEnd)
[NSBezierPath fillRect:
2007-12-23 12:45:17 +00:00
NSMakeRect(kLayout.MeasurePosition(startMeas)-kMeasTol, kRawSystemY,
2006-12-28 05:03:28 +00:00
2.0f*kMeasTol, kSystemH)];
else
[NSBezierPath fillRect:
2007-12-23 12:45:17 +00:00
NSMakeRect(kLayout.MeasurePosition(startMeas), kRawSystemY,
(endMeas-startMeas)*kLayout.MeasureWidth(), kSystemH)];
2006-12-28 05:03:28 +00:00
[NSGraphicsContext restoreGraphicsState];
}
- (void)drawRect:(NSRect)rect
{
if (![self song])
return;
2007-05-04 05:21:16 +00:00
if (fNeedsRecalc || [self inLiveResize] || [self song]->CountMeasures() != fLastMeasures) {
[self recalculateDimensions];
rect = [self bounds];
}
[NSGraphicsContext saveGraphicsState];
2018-08-26 23:16:54 +00:00
[NSColor.windowBackgroundColor setFill];
[NSBezierPath fillRect:rect];
[NSGraphicsContext restoreGraphicsState];
2006-12-02 03:35:21 +00:00
size_t stanzas = [self song]->CountStanzas();
2007-12-23 12:45:17 +00:00
for (int system = 0; system<fLayout->NumSystems(); ++system) {
const float kSystemY = [self systemY:system];
2007-12-23 12:45:17 +00:00
NSRect systemRect = NSMakeRect(kLineX, kSystemY-kSystemBaseline, (*fLayout)[system].SystemWidth(), kSystemH);
if (!NSIntersectsRect(rect, systemRect))
continue; // This system does not need to be drawn
[self drawBackgroundForSystem:system];
2006-12-28 05:03:28 +00:00
//
// When highlighting, draw highlight FIRST and then draw our stuff
// on top.
//
2011-09-11 02:39:54 +00:00
if (fSelEnd != kNoMeasure && fSelStart <= fSelEnd
2011-09-13 01:43:38 +00:00
&& fLayout->FirstMeasure(system+1)+(fSelStart==fSelEnd) > fSelStart
2007-12-23 12:45:17 +00:00
&& fLayout->FirstMeasure(system) < fSelEnd+(fSelStart==fSelEnd)
2006-12-28 05:03:28 +00:00
)
[self highlightSelectionForSystem:system];
[self drawGridForSystem:system];
[self drawNotesForSystem:system];
[self drawChordsForSystem:system];
2006-12-02 03:35:21 +00:00
for (size_t stanza=0; stanza++<stanzas;)
[self drawLyricsForSystem:system stanza:stanza];
}
[[self editTarget] highlightCursor];
2006-09-11 02:49:56 +00:00
}
2007-04-15 05:22:30 +00:00
- (void)setKey:(NSAlert *)alert returnCode:(int)returnCode contextInfo:(id)sender
{
if (returnCode == NSAlertAlternateReturn)
return;
int key = [[sender selectedItem] tag];
[[self document] setKey:key transpose:returnCode==NSAlertDefaultReturn
inSections:[self sectionsInSelection]];
2007-04-15 05:22:30 +00:00
fNeedsRecalc = kRecalc;
[self setNeedsDisplay: YES];
}
2006-09-11 02:49:56 +00:00
- (IBAction) setKey:(id)sender
{
if ([self song]->IsNonEmpty()) {
int newKey = [[sender selectedItem] tag] >> 8;
NSRange sections= [self sectionsInSelection];
VLSong *song = [self song];
bool plural = sections.length > 1;
while (sections.length-- > 0)
if (song->fProperties[sections.location++].fKey != newKey) {
[[NSAlert alertWithMessageText:@"Transpose Song?"
defaultButton:@"Transpose"
alternateButton:@"Cancel"
otherButton:@"Change Key"
informativeTextWithFormat:
@"Do you want to transpose the %@ into the new key?",
2011-09-11 02:39:54 +00:00
(fSelEnd != kNoMeasure && song->fProperties.size() > 1)
? (plural ? @"sections" : @"section") : @"song"
]
beginSheetModalForWindow:[self window]
modalDelegate:self
didEndSelector:@selector(setKey:returnCode:contextInfo:)
contextInfo:sender];
return;
}
}
[self setKey:nil returnCode:NSAlertOtherReturn contextInfo:sender];
2006-10-14 10:09:42 +00:00
}
2008-04-12 21:33:43 +00:00
- (IBAction) transposeOctave:(id)sender
{
[[self document] changeOctave:[sender tag] > 0
inSections:[self sectionsInSelection]];
}
2006-09-11 02:49:56 +00:00
- (IBAction) setTime:(id)sender
{
int time = [[sender selectedItem] tag];
[[self document] setTimeNum: time >> 8 denom: time & 0xFF
inSections:[self sectionsInSelection]];
fNeedsRecalc = kRecalc;
2006-09-11 02:49:56 +00:00
[self setNeedsDisplay: YES];
}
- (IBAction) setDivisions:(id)sender
{
int div = [[sender selectedItem] tag];
[[self document] setDivisions: div inSections:[self sectionsInSelection]];
fNeedsRecalc = kRecalc;
2006-09-11 02:49:56 +00:00
[self setNeedsDisplay: YES];
}
- (IBAction)hideFieldEditor:(id)sender
{
[fFieldEditor setAction:nil];
}
const float kSemiFloor = -1.0f*kLineH;
2006-10-21 09:23:37 +00:00
- (void) accidentalFromEvent:(NSEvent *)event
{
switch ([event modifierFlags] & (NSShiftKeyMask|NSAlternateKeyMask|NSCommandKeyMask)) {
case NSShiftKeyMask:
fCursorVisual = kCursorExtend;
break;
case NSShiftKeyMask|NSAlternateKeyMask:
fCursorVisual = VLNote::kWant2Flat; // Gbb
break;
case NSAlternateKeyMask:
fCursorVisual = VLNote::kWantFlat; // Gb
break;
case NSShiftKeyMask|NSCommandKeyMask:
fCursorVisual = VLNote::kWant2Sharp; // G##
break;
case NSCommandKeyMask:
fCursorVisual = VLNote::kWantSharp; // G#
break;
case NSAlternateKeyMask|NSCommandKeyMask:
fCursorVisual = VLNote::kWantNatural; // G
break;
default:
fCursorVisual = 0;
break;
}
2006-10-21 09:23:37 +00:00
}
- (VLRegion) findRegionForEvent:(NSEvent *) event
{
fCursorVertPos = kCursorNoPitch;
2007-12-23 23:07:27 +00:00
NSPoint loc = [event locationInWindow];
loc = [self convertPoint:loc fromView:nil];
2011-09-05 22:22:57 +00:00
int system = [self systemForPoint:&loc];
2011-09-05 22:22:57 +00:00
if (system < 0 || system > fLayout->NumSystems())
2006-10-03 17:52:54 +00:00
return fCursorRegion = kRegionNowhere;
2011-09-05 22:22:57 +00:00
const VLSong * song = [self song];
2007-12-23 12:45:17 +00:00
const VLSystemLayout & kLayout = (*fLayout)[system];
const float kMeasureW = kLayout.MeasureWidth();
2007-12-23 12:45:17 +00:00
loc.x -= kLayout.ClefKeyWidth();
if (loc.y > kSystemBaseline && loc.y < kSystemBaseline+4.0f*kLineH
2007-12-23 12:45:17 +00:00
&& fmodf(loc.x+kMeasTol, kMeasureW) < 2*kMeasTol
2006-12-28 05:03:28 +00:00
) {
2007-12-23 12:45:17 +00:00
int measure = static_cast<int>((loc.x+kMeasTol)/kMeasureW);
2006-12-28 05:03:28 +00:00
2007-12-23 12:45:17 +00:00
if (measure < 0 || measure > kLayout.NumMeasures())
2006-12-28 05:03:28 +00:00
return fCursorRegion = kRegionNowhere;
2011-09-11 02:03:22 +00:00
fCursorLocation.fMeasure = measure+fLayout->FirstMeasure(system);
2006-12-28 05:03:28 +00:00
2011-09-11 02:03:22 +00:00
if (fCursorLocation.fMeasure > [self song]->fMeasures.size())
2006-12-28 05:03:28 +00:00
return fCursorRegion = kRegionNowhere;
else
return fCursorRegion = kRegionMeasure;
}
2007-12-23 12:45:17 +00:00
if (loc.x < 0.0f || loc.x >= kLayout.NumMeasures()*kMeasureW)
2006-10-03 17:52:54 +00:00
return fCursorRegion = kRegionNowhere;
2007-12-23 12:45:17 +00:00
int measure = static_cast<int>(loc.x / kMeasureW);
loc.x -= measure*kMeasureW;
int group = static_cast<int>(loc.x / ((kLayout.DivPerGroup()+1)*kNoteW));
loc.x -= group*(kLayout.DivPerGroup()+1)*kNoteW;
int div = static_cast<int>(roundf(loc.x / kNoteW))-1;
2007-12-23 12:45:17 +00:00
div = std::min(std::max(div, 0), kLayout.DivPerGroup()-1);
2011-09-11 02:03:22 +00:00
fCursorLocation.fMeasure = measure+fLayout->FirstMeasure(system);
if (fCursorLocation.fMeasure > [self song]->fMeasures.size())
2006-10-03 17:52:54 +00:00
return fCursorRegion = kRegionNowhere;
2011-09-11 02:03:22 +00:00
fCursorLocation.fAt = VLFraction(div+group*kLayout.DivPerGroup(), 4*song->Properties(fCursorLocation.fMeasure).fDivisions);
2007-12-23 13:14:09 +00:00
if (loc.y >= kSystemBaseline+kChordY) {
2006-10-03 17:52:54 +00:00
//
// Chord, round to quarters
//
2011-09-11 02:03:22 +00:00
int scale = fCursorLocation.fAt.fDenom / 4;
fCursorLocation.fAt = VLFraction(fCursorLocation.fAt.fNum / scale, 4);
2006-10-03 17:52:54 +00:00
return fCursorRegion = kRegionChord;
} else if (loc.y < kSystemBaseline+kLyricsY) {
fCursorStanza = static_cast<size_t>((kSystemBaseline+kLyricsY-loc.y) / kLyricsH)
2006-12-02 09:02:44 +00:00
+ 1;
2006-10-03 17:52:54 +00:00
return fCursorRegion = kRegionLyrics;
}
loc.y -= kSystemBaseline+kSemiFloor;
fCursorVertPos = static_cast<int>(roundf(loc.y / (0.5f*kLineH)));
2006-10-21 09:23:37 +00:00
[self accidentalFromEvent:event];
2006-10-03 17:52:54 +00:00
return fCursorRegion = kRegionNote;
}
- (void) mouseMoved:(NSEvent *)event
{
if ([event modifierFlags] & NSAlphaShiftKeyMask)
return; // Keyboard mode, ignore mouse
2018-02-19 00:59:23 +00:00
if ([event modifierFlags] & NSControlKeyMask) {
[[NSCursor contextualMenuCursor] set];
} else {
[[NSCursor arrowCursor] set];
}
bool hadCursor = fCursorRegion == kRegionNote;
[self findRegionForEvent:event];
bool hasCursor = fCursorRegion == kRegionNote;
[self setNeedsDisplay:(hadCursor || hasCursor)];
}
2006-10-21 09:23:37 +00:00
- (void)flagsChanged:(NSEvent *)event
{
if (fCursorRegion == kRegionNote) {
2006-10-21 09:23:37 +00:00
[self accidentalFromEvent:event];
[self setNeedsDisplay:YES];
}
}
- (void) mouseEntered:(NSEvent *)event
{
[[self window] setAcceptsMouseMovedEvents:YES];
[self mouseMoved:event];
}
- (void) mouseExited:(NSEvent *)event
{
fCursorRegion = kRegionNowhere;
2008-01-29 03:02:25 +00:00
[[self window] setAcceptsMouseMovedEvents:NO];
[self setNeedsDisplay:YES];
2018-02-19 00:59:23 +00:00
[[NSCursor arrowCursor] set];
}
- (void) rightMouseDown:(NSEvent *)event
{
[[self document] endSong];
VLRegion region = [self findRegionForEvent:event];
switch (region) {
case kRegionNote:
[NSMenu popUpContextMenu:fNoteActionMenu withEvent:event forView:self];
break;
default:
break;
}
}
- (void) mouseDown:(NSEvent *)event
{
2018-02-19 00:59:23 +00:00
if ([event modifierFlags] & NSControlKeyMask) {
[self rightMouseDown:event];
return;
}
2012-08-18 22:30:50 +00:00
[[self document] endSong];
2011-09-11 02:39:54 +00:00
BOOL extend = ([event modifierFlags] & NSShiftKeyMask) != 0;
VLRegion region = [self findRegionForEvent:event];
if (extend && [[self editTarget] canExtendSelection:region])
[[self editTarget] extendSelection:fCursorLocation];
2018-02-19 00:59:23 +00:00
else
2011-09-11 02:39:54 +00:00
switch (region) {
case kRegionNote:
[self setEditTarget:nil];
[self addNoteAtCursor];
break;
case kRegionChord:
[self editChord];
break;
case kRegionLyrics:
2011-09-10 23:15:21 +00:00
[self editLyrics];
2011-09-11 02:39:54 +00:00
break;
case kRegionMeasure:
[self editSelection];
break;
default:
[self setEditTarget:nil];
break;
2011-09-10 23:15:21 +00:00
}
2006-12-28 05:03:28 +00:00
}
- (void) mouseDragged:(NSEvent *)event
{
2011-09-10 23:15:21 +00:00
[super mouseDragged:event];
2006-12-28 05:03:28 +00:00
[self autoscroll:event];
2011-09-11 02:39:54 +00:00
if ([[self editTarget] canExtendSelection:[self findRegionForEvent:event]])
2011-09-11 02:03:22 +00:00
[[self editTarget] extendSelection:fCursorLocation];
}
- (void) keyDown:(NSEvent *)event
{
NSString * k = [event charactersIgnoringModifiers];
switch ([k characterAtIndex:0]) {
case 'r':
if (fClickMode == 'r')
fClickMode = ' ';
else
fClickMode = 'r';
[self setNeedsDisplay:YES];
break;
case 'k':
if (fClickMode == 'k')
fClickMode = ' ';
else
fClickMode = 'k';
[self setNeedsDisplay:YES];
2011-09-06 01:26:08 +00:00
break;
2011-09-26 02:49:09 +00:00
case '?':
[self playNoteAtCursor];
break;
case 0xF702: // Left arrow
[self moveCursorToPrevNote];
break;
case 0xF703: // Right arrow
[self moveCursorToNextNote];
break;
}
}
2006-10-03 17:52:54 +00:00
- (BOOL)control:(NSControl *)control textShouldEndEditing:(NSText *)fieldEditor
{
return [[self editTarget] validValue:[fFieldEditor stringValue]];
2006-10-03 17:52:54 +00:00
}
2008-03-30 21:42:21 +00:00
- (void)updateFirstResponder
{
NSWindow * win = [self window];
NSResponder * hasResponder = [win firstResponder];
if ([self editTarget] && ![[self editTarget] hidden])
2008-04-12 22:35:53 +00:00
if (hasResponder != [win fieldEditor:NO forObject:nil]
2011-09-07 03:21:20 +00:00
|| [(id)hasResponder delegate] != fFieldEditor
2008-04-12 22:35:53 +00:00
)
2008-03-30 21:42:21 +00:00
[win makeFirstResponder:fFieldEditor];
}
- (void)controlTextDidEndEditing:(NSNotification *)note
{
VLEditable * editable = [self editTarget];
switch ([[[note userInfo] objectForKey:@"NSTextMovement"] intValue]) {
case NSTabTextMovement:
[editable moveToNext];
2011-09-13 03:12:59 +00:00
[self updateEditTarget];
break;
case NSBacktabTextMovement:
[editable moveToPrev];
2011-09-13 03:12:59 +00:00
[self updateEditTarget];
break;
2011-09-10 23:23:38 +00:00
case NSReturnTextMovement:
[self setEditTarget:nil];
// Fall through
default:
2008-05-29 18:54:30 +00:00
fHighlightStanza = 0xFFFFFFFF;
2011-09-10 23:15:21 +00:00
editable = nil;
}
if (editable)
[fFieldEditor selectText:self];
2008-03-30 21:42:21 +00:00
else
[[self window] makeFirstResponder:self];
[self performSelectorOnMainThread:@selector(updateFirstResponder)
withObject:nil waitUntilDone:NO];
2006-10-09 07:28:49 +00:00
[self setNeedsDisplay: YES];
}
2006-11-06 15:49:50 +00:00
- (void) setScaleFactor:(float)scale
{
fDisplayScale= scale;
fNeedsRecalc = kRecalc;
[self setNeedsDisplay: YES];
}
- (IBAction) zoomIn: (id) sender
{
2006-12-02 23:05:12 +00:00
[self setScaleFactor: fDisplayScale * sqrt(sqrt(2.0))];
2006-11-06 15:49:50 +00:00
}
- (IBAction) zoomOut: (id) sender
{
2006-12-02 23:05:12 +00:00
[self setScaleFactor: fDisplayScale / sqrt(sqrt(2.0))];
2006-11-06 15:49:50 +00:00
}
2006-12-04 07:04:24 +00:00
- (void)awakeFromNib
{
VLDocument * doc = [self document];
[doc addObserver:self forKeyPath:@"song" options:0 context:nil];
[doc addObserver:self forKeyPath:@"songKey" options:0 context:nil];
2008-01-16 13:04:01 +00:00
[doc addObserver:self forKeyPath:@"songTime" options:0 context:nil];
[doc addObserver:self forKeyPath:@"songDivisions" options:0 context:nil];
[doc addObserver:self forKeyPath:@"songGroove" options:0 context:nil];
VLSong * song = [self song];
fNumTopLedgers = std::max<int>(song->CountTopLedgers(), 1);
fNumBotLedgers = std::max<int>(song->CountBotLedgers(), 1);
fNumStanzas = std::max<int>(song->CountStanzas(), 2);
2008-01-16 13:04:01 +00:00
2008-01-23 01:20:09 +00:00
[fGrooveMenu addItemsWithTitles:
[[NSUserDefaults standardUserDefaults] arrayForKey:@"VLGrooves"]];
2008-01-16 13:04:01 +00:00
[self updateMenus];
2011-08-29 21:22:49 +00:00
[self recalculateDimensions];
2011-09-05 22:22:38 +00:00
fNeedsRecalc = kFirstRecalc;
2006-12-04 07:04:24 +00:00
}
2007-04-16 05:35:52 +00:00
- (void)removeObservers:(id)target
{
[target removeObserver:self forKeyPath:@"song"];
[target removeObserver:self forKeyPath:@"songKey"];
2008-01-16 13:04:01 +00:00
[target removeObserver:self forKeyPath:@"songTime"];
[target removeObserver:self forKeyPath:@"songDivisions"];
2007-04-16 05:35:52 +00:00
[target removeObserver:self forKeyPath:@"songGroove"];
}
2006-12-04 07:04:24 +00:00
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)o change:(NSDictionary *)c context:(id)ctx
{
2007-04-16 04:54:02 +00:00
if ([keyPath isEqual:@"songKey"]) {
2006-12-04 07:04:24 +00:00
fNeedsRecalc = kRecalc;
2007-04-16 04:54:02 +00:00
[self setNeedsDisplay: YES];
2008-01-16 13:04:01 +00:00
[self updateKeyMenu];
2007-04-21 22:37:28 +00:00
} else if ([keyPath isEqual:@"song"]) {
2007-04-16 04:54:02 +00:00
[self setNeedsDisplay: YES];
2008-01-16 13:04:01 +00:00
} else if ([keyPath isEqual:@"songTime"]) {
[self updateTimeMenu];
} else if ([keyPath isEqual:@"songDivisions"]) {
[self updateDivisionMenu];
2007-04-16 04:54:02 +00:00
} else if ([keyPath isEqual:@"songGroove"]) {
2008-01-23 01:20:09 +00:00
[self updateGrooveMenu];
2007-04-16 04:54:02 +00:00
}
2006-12-04 07:04:24 +00:00
}
- (IBAction)endSheetWithButton:(id)sender
{
[NSApp endSheet:[sender window] returnCode:[sender tag]];
}
2007-04-15 05:22:30 +00:00
- (IBAction)selectGroove:(id)sender
{
2007-04-16 04:54:02 +00:00
if ([sender tag])
2007-04-15 05:22:30 +00:00
[[VLGrooveController alloc] initWithSheetView:self];
else
[self setGroove:[sender title]];
}
- (void)setGroove:(NSString *)groove
{
2011-07-26 22:49:39 +00:00
if (groove)
[[self document] setGroove:groove inSections:[self sectionsInSelection]];
2007-04-15 05:22:30 +00:00
}
2008-01-24 01:29:18 +00:00
- (void)playWithGroove:(NSString *)groove
{
[[self document] playWithGroove:groove inSections:[self sectionsInSelection]];
}
2006-09-11 02:49:56 +00:00
@end
@implementation NSImage (VLSheetViewDrawing)
- (void) drawAllAtPoint:(NSPoint)p operation:(NSCompositingOperation)op
{
[self drawAtPoint:p fromRect:NSZeroRect operation:op fraction:1.0f];
}
- (void) drawAllAtPoint:(NSPoint)p
{
[self drawAtPoint:p fromRect:NSZeroRect operation:NSCompositeSourceOver fraction:1.0f];
}
@end