| 1 |
// |
| 2 |
// TimeChartView.m |
| 3 |
// Created by Toshi Nagata on Sat Jan 25 2003. |
| 4 |
// |
| 5 |
/* |
| 6 |
Copyright (c) 2003-2016 Toshi Nagata. All rights reserved. |
| 7 |
|
| 8 |
This program is free software; you can redistribute it and/or modify |
| 9 |
it under the terms of the GNU General Public License as published by |
| 10 |
the Free Software Foundation version 2 of the License. |
| 11 |
|
| 12 |
This program is distributed in the hope that it will be useful, |
| 13 |
but WITHOUT ANY WARRANTY; without even the implied warranty of |
| 14 |
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| 15 |
GNU General Public License for more details. |
| 16 |
*/ |
| 17 |
|
| 18 |
#import "TimeChartView.h" |
| 19 |
#import "GraphicWindowController.h" |
| 20 |
#import "MyDocument.h" |
| 21 |
#import "MyAppController.h" |
| 22 |
#import "MyMIDISequence.h" |
| 23 |
#import "MDObjects.h" |
| 24 |
#import "NSStringAdditions.h" |
| 25 |
#import <math.h> |
| 26 |
#import "MDHeaders.h" |
| 27 |
#import "NSCursorAdditions.h" |
| 28 |
#import "PlayingViewController.h" |
| 29 |
|
| 30 |
typedef struct TimeScalingRecord { |
| 31 |
MDTickType startTick; /* Start tick of the time region to be scaled */ |
| 32 |
MDTickType endTick; /* End tick of the time region to be scaled */ |
| 33 |
MDTickType newEndTick; /* End tick after scaling the time region */ |
| 34 |
int ntracks; /* Number of editable tracks */ |
| 35 |
int *trackNums; /* Track numbers */ |
| 36 |
int32_t *startPos; /* Start positions for each track to modify ticks */ |
| 37 |
MDTickType **originalTicks; /* Arrays of original ticks for each track */ |
| 38 |
IntGroup *meterPos; /* The position of meter events */ |
| 39 |
IntGroup *origMeterPos; /* The _original_ position of meter events */ |
| 40 |
} TimeScalingRecord; |
| 41 |
|
| 42 |
@implementation TimeChartView |
| 43 |
|
| 44 |
//- (BOOL)willChangeSelectionOnMouseDown |
| 45 |
//{ |
| 46 |
// return NO; |
| 47 |
//} |
| 48 |
|
| 49 |
+ (float)minHeight |
| 50 |
{ |
| 51 |
return 36.0f; |
| 52 |
} |
| 53 |
|
| 54 |
- (int)clientViewType |
| 55 |
{ |
| 56 |
return kGraphicTimeChartViewType; |
| 57 |
} |
| 58 |
|
| 59 |
//- (id)initWithFrame: (NSRect)rect |
| 60 |
//{ |
| 61 |
// self = [super initWithFrame: rect]; |
| 62 |
// if (self) { |
| 63 |
// selectMode = kGraphicClientIbeamMode; |
| 64 |
// } |
| 65 |
// return self; |
| 66 |
//} |
| 67 |
|
| 68 |
- (void)drawContentsInRect: (NSRect)aRect |
| 69 |
{ |
| 70 |
float ppt; |
| 71 |
MDTickType beginTick, endTick; |
| 72 |
float originx; |
| 73 |
float limitx; |
| 74 |
float basey, maxLabelWidth; |
| 75 |
NSPoint pt1, pt2; |
| 76 |
NSRect visibleRect = [self visibleRect]; |
| 77 |
float editingRangeStartX, editingRangeEndX; |
| 78 |
|
| 79 |
basey = visibleRect.origin.y + 0.5f; |
| 80 |
ppt = [dataSource pixelsPerTick]; |
| 81 |
limitx = [dataSource sequenceDurationInQuarter] * [dataSource pixelsPerQuarter]; |
| 82 |
|
| 83 |
[self paintEditingRange: aRect startX: &editingRangeStartX endX: &editingRangeEndX]; |
| 84 |
|
| 85 |
/* Draw horizontal axis */ |
| 86 |
[NSBezierPath strokeLineFromPoint: NSMakePoint(aRect.origin.x, basey) toPoint: NSMakePoint(aRect.origin.x + aRect.size.width, basey)]; |
| 87 |
|
| 88 |
/* Draw ticks, labels, and time signatures */ |
| 89 |
maxLabelWidth = [@"0000:00:0000" sizeWithAttributes: nil].width; |
| 90 |
originx = aRect.origin.x - maxLabelWidth; |
| 91 |
if (originx < 0.0f) |
| 92 |
originx = 0.0f; |
| 93 |
beginTick = originx / ppt; |
| 94 |
endTick = (aRect.origin.x + aRect.size.width) / ppt + 1; |
| 95 |
while (beginTick < endTick) { |
| 96 |
int mediumCount, majorCount, i, numLines; |
| 97 |
int sigNumerator, sigDenominator; |
| 98 |
MDEvent *sig1, *sig2; |
| 99 |
MDTickType sigTick, nextSigTick; |
| 100 |
float interval, startx; |
| 101 |
float widthPerBeat, widthPerMeasure; |
| 102 |
[dataSource verticalLinesFromTick: beginTick timeSignature: &sig1 nextTimeSignature: &sig2 lineIntervalInPixels: &interval mediumCount: &mediumCount majorCount: &majorCount]; |
| 103 |
sigTick = (sig1 == NULL ? 0 : MDGetTick(sig1)); |
| 104 |
nextSigTick = (sig2 == NULL ? kMDMaxTick : MDGetTick(sig2)); |
| 105 |
if (nextSigTick > endTick) |
| 106 |
nextSigTick = endTick; |
| 107 |
startx = sigTick * ppt; |
| 108 |
sigDenominator = (sig1 == NULL ? 4 : (1 << (int)(MDGetMetaDataPtr(sig1)[1]))); |
| 109 |
sigNumerator = (sig1 == NULL ? 4 : MDGetMetaDataPtr(sig1)[0]); |
| 110 |
if (sigNumerator == 0) |
| 111 |
sigNumerator = 4; |
| 112 |
[[NSString stringWithFormat: @"%d/%d", sigNumerator, sigDenominator] drawAtPoint: NSMakePoint(startx, basey + 22.0f) withAttributes: nil clippingRect: aRect]; |
| 113 |
numLines = (int)floor((nextSigTick - sigTick) * ppt / interval) + 1; |
| 114 |
i = (startx >= originx ? 0 : (int)floor((originx - startx) / interval)); |
| 115 |
[[NSColor blackColor] set]; |
| 116 |
for ( ; i < numLines; i++) { |
| 117 |
pt1 = NSMakePoint((CGFloat)(floor(startx + i * interval) + 0.5), basey); |
| 118 |
pt2.x = pt1.x; |
| 119 |
if (pt1.x > limitx) |
| 120 |
[[NSColor grayColor] set]; |
| 121 |
if (i % majorCount == 0) { |
| 122 |
/* Draw label */ |
| 123 |
NSString *label; |
| 124 |
int32_t n1, n2, n3; |
| 125 |
[dataSource convertTick: (MDTickType)floor((startx + i * interval) / ppt + 0.5) toMeasure: &n1 beat: &n2 andTick: &n3]; |
| 126 |
widthPerBeat = [(MyDocument *)[dataSource document] timebase] * ppt * 4 / sigDenominator; |
| 127 |
widthPerMeasure = widthPerBeat * sigNumerator; |
| 128 |
if (interval * majorCount >= widthPerMeasure) { |
| 129 |
label = [NSString stringWithFormat: @"%d", (int)n1]; |
| 130 |
} else if (interval * majorCount >= widthPerBeat) { |
| 131 |
// The major tick interval >= beat |
| 132 |
label = [NSString stringWithFormat: @"%d:%d", (int)n1, (int)n2]; |
| 133 |
} else { |
| 134 |
// The major tick interval < beat |
| 135 |
label = [NSString stringWithFormat: @"%d:%d:%d", (int)n1, (int)n2, (int)n3]; |
| 136 |
} |
| 137 |
[label drawAtPoint: NSMakePoint((CGFloat)floor(pt1.x), (CGFloat)floor(pt1.y + 10.0)) withAttributes: nil clippingRect: aRect]; |
| 138 |
} |
| 139 |
if (pt1.x >= aRect.origin.x && pt1.x <= aRect.origin.x + aRect.size.width) { |
| 140 |
/* Draw axis marks */ |
| 141 |
if (i % majorCount == 0) { |
| 142 |
pt2.y = pt1.y + 9.0f; |
| 143 |
} else if (i % mediumCount == 0) |
| 144 |
pt2.y = pt1.y + 6.0f; |
| 145 |
else pt2.y = pt1.y + 3.0f; |
| 146 |
[NSBezierPath strokeLineFromPoint: pt1 toPoint: pt2]; |
| 147 |
} |
| 148 |
} |
| 149 |
beginTick = nextSigTick; |
| 150 |
} |
| 151 |
/* Draw selection range symbols */ |
| 152 |
[[NSColor blackColor] set]; |
| 153 |
if (editingRangeStartX >= 0) { |
| 154 |
static NSImage *sStartEditingImage = nil; |
| 155 |
static NSImage *sEndEditingImage = nil; |
| 156 |
if (sStartEditingImage == nil) { |
| 157 |
sStartEditingImage = [[NSImage allocWithZone: [self zone]] initWithContentsOfFile: [[NSBundle mainBundle] pathForResource: @"StartEditingRange.png" ofType: nil]]; |
| 158 |
} |
| 159 |
if (sEndEditingImage == nil) { |
| 160 |
sEndEditingImage = [[NSImage allocWithZone: [self zone]] initWithContentsOfFile: [[NSBundle mainBundle] pathForResource: @"EndEditingRange.png" ofType: nil]]; |
| 161 |
} |
| 162 |
pt1.x = editingRangeStartX - 5.0f; |
| 163 |
pt1.y = visibleRect.origin.y + 1.0f; |
| 164 |
if (pt1.x >= aRect.origin.x && pt1.x < aRect.origin.x + aRect.size.width) { |
| 165 |
[sStartEditingImage drawAtPoint: pt1 fromRect: NSZeroRect operation: NSCompositeSourceAtop fraction: 1.0f]; |
| 166 |
} |
| 167 |
pt1.x = editingRangeEndX; |
| 168 |
if (pt1.x >= aRect.origin.x && pt1.x < aRect.origin.x + aRect.size.width) { |
| 169 |
[sEndEditingImage drawAtPoint: pt1 fromRect: NSZeroRect operation: NSCompositeSourceAtop fraction: 1.0f]; |
| 170 |
} |
| 171 |
} |
| 172 |
|
| 173 |
/* Draw end-of-track symbol */ |
| 174 |
if (aRect.origin.x + aRect.size.width > limitx) { |
| 175 |
float defaultLineWidth; |
| 176 |
pt1.x = pt2.x = limitx; |
| 177 |
pt1.y = basey; |
| 178 |
pt2.y = basey + 12.0f; |
| 179 |
[NSBezierPath strokeLineFromPoint: pt1 toPoint: pt2]; |
| 180 |
defaultLineWidth = [NSBezierPath defaultLineWidth]; |
| 181 |
[NSBezierPath setDefaultLineWidth: 2.0f]; |
| 182 |
pt1.x = pt2.x += 3.0f; |
| 183 |
[NSBezierPath strokeLineFromPoint: pt1 toPoint: pt2]; |
| 184 |
[NSBezierPath setDefaultLineWidth: defaultLineWidth]; |
| 185 |
} |
| 186 |
|
| 187 |
// if ([self isDragging]) |
| 188 |
[self drawSelectRegion]; |
| 189 |
|
| 190 |
} |
| 191 |
|
| 192 |
// Examine whether the mouse pointer is on one of the editing range marks |
| 193 |
// Return value: -1, start pos, 0: none, 1: end pos |
| 194 |
// pt should be in view coordinates |
| 195 |
- (int)isMouseOnEditStartPositions:(NSPoint)pt |
| 196 |
{ |
| 197 |
NSRect visibleRect = [self visibleRect]; |
| 198 |
if (pt.y >= visibleRect.origin.y && pt.y <= visibleRect.origin.y + 5) { |
| 199 |
MDTickType startTick, endTick; |
| 200 |
float startx, endx; |
| 201 |
float ppt = [dataSource pixelsPerTick]; |
| 202 |
[(MyDocument *)[dataSource document] getEditingRangeStart: &startTick end: &endTick]; |
| 203 |
if (startTick >= 0 && startTick < kMDMaxTick && endTick >= startTick) { |
| 204 |
startx = (float)floor(startTick * ppt); |
| 205 |
endx = (float)floor(endTick * ppt); |
| 206 |
if (startx - 5 <= pt.x && pt.x <= startx) |
| 207 |
return -1; |
| 208 |
if (endx <= pt.x && pt.x <= endx + 5) |
| 209 |
return 1; |
| 210 |
} |
| 211 |
} |
| 212 |
return 0; |
| 213 |
} |
| 214 |
|
| 215 |
/* See also: -[MyDocument scaleSelectedTime:] */ |
| 216 |
- (void)scaleSelectedTimeWithEvent: (NSEvent *)theEvent |
| 217 |
{ |
| 218 |
MDSequence *seq; |
| 219 |
int i, j; |
| 220 |
if (timeScaling == NULL) |
| 221 |
return; /* Do nothing */ |
| 222 |
if (theEvent == NULL) |
| 223 |
timeScaling->newEndTick = timeScaling->endTick; /* Return to the original */ |
| 224 |
else { |
| 225 |
NSPoint mousePt = [self convertPoint:[theEvent locationInWindow] fromView:nil]; |
| 226 |
float ppt = [dataSource pixelsPerTick]; |
| 227 |
timeScaling->newEndTick = mousePt.x / ppt; |
| 228 |
if (timeScaling->newEndTick < timeScaling->startTick) |
| 229 |
timeScaling->newEndTick = timeScaling->startTick; |
| 230 |
} |
| 231 |
seq = [[[dataSource document] myMIDISequence] mySequence]; |
| 232 |
for (i = 0; i < timeScaling->ntracks; i++) { |
| 233 |
int n; |
| 234 |
MDPointer *pt; |
| 235 |
MDEvent *ep; |
| 236 |
int trackNum = timeScaling->trackNums[i]; |
| 237 |
MDTrack *track = MDSequenceGetTrack(seq, trackNum); |
| 238 |
MDTrack *meterTrack = NULL; |
| 239 |
if (trackNum == 0 && IntGroupGetCount(timeScaling->meterPos) > 0) { |
| 240 |
/* Temporarily remove the meter events */ |
| 241 |
MDTrackUnmerge(track, &meterTrack, timeScaling->meterPos); |
| 242 |
} |
| 243 |
n = MDTrackGetNumberOfEvents(track) - timeScaling->startPos[i]; /* Number of events */ |
| 244 |
pt = MDPointerNew(track); |
| 245 |
MDPointerSetPosition(pt, timeScaling->startPos[i]); |
| 246 |
for (ep = MDPointerCurrent(pt), j = 0; ; ep = MDPointerForward(pt), j++) { |
| 247 |
MDTickType tick = timeScaling->originalTicks[i][j]; |
| 248 |
if (timeScaling->newEndTick != timeScaling->endTick) { |
| 249 |
if (tick < timeScaling->endTick) |
| 250 |
tick = timeScaling->startTick + (MDTickType)((double)(tick - timeScaling->startTick) * (timeScaling->newEndTick - timeScaling->startTick) / (timeScaling->endTick - timeScaling->startTick)); |
| 251 |
else |
| 252 |
tick += (timeScaling->newEndTick - timeScaling->endTick); |
| 253 |
} |
| 254 |
if (ep == NULL) { |
| 255 |
MDTrackSetDuration(track, tick); |
| 256 |
break; |
| 257 |
} else { |
| 258 |
MDSetTick(ep, tick); |
| 259 |
} |
| 260 |
} |
| 261 |
if (trackNum == 0 && IntGroupGetCount(timeScaling->meterPos) > 0) { |
| 262 |
/* Restore the meter events */ |
| 263 |
if (theEvent == NULL) { |
| 264 |
/* Restore to the original positions */ |
| 265 |
MDTrackMerge(track, meterTrack, &(timeScaling->origMeterPos)); |
| 266 |
} else { |
| 267 |
/* Restore to the appropriate positions */ |
| 268 |
IntGroupRelease(timeScaling->meterPos); |
| 269 |
timeScaling->meterPos = NULL; |
| 270 |
MDTrackMerge(track, meterTrack, &(timeScaling->meterPos)); |
| 271 |
} |
| 272 |
MDTrackRelease(meterTrack); |
| 273 |
MDSequenceResetCalibrators(seq); |
| 274 |
} |
| 275 |
} |
| 276 |
[dataSource reloadClientViews]; |
| 277 |
} |
| 278 |
|
| 279 |
- (void)doMouseDown: (NSEvent *)theEvent |
| 280 |
{ |
| 281 |
NSPoint pt = [self convertPoint:[theEvent locationInWindow] fromView:nil]; |
| 282 |
int n = [self isMouseOnEditStartPositions:pt]; |
| 283 |
if (n != 0) { |
| 284 |
/* Mofity selectPoints so that the 'other' edit start/end position |
| 285 |
looks like the dragging start points */ |
| 286 |
MDTickType startTick, endTick; |
| 287 |
float ppt = [dataSource pixelsPerTick]; |
| 288 |
[(MyDocument *)[dataSource document] getEditingRangeStart: &startTick end: &endTick]; |
| 289 |
if (n < 0) |
| 290 |
pt.x = endTick * ppt; |
| 291 |
else |
| 292 |
pt.x = startTick * ppt; |
| 293 |
[selectPoints replaceObjectAtIndex: 0 withObject: [NSValue valueWithPoint:pt]]; |
| 294 |
if (n == 1 && ([theEvent modifierFlags] & (NSAlternateKeyMask | NSShiftKeyMask)) && startTick < endTick) { |
| 295 |
/* Scale selected time: initialize the internal information */ |
| 296 |
int i, j; |
| 297 |
int32_t trackNo; |
| 298 |
MDTrack *track; |
| 299 |
MDSequence *seq = [[[dataSource document] myMIDISequence] mySequence]; |
| 300 |
timeScaling = (TimeScalingRecord *)calloc(sizeof(TimeScalingRecord), 1); |
| 301 |
timeScaling->startTick = startTick; |
| 302 |
timeScaling->endTick = endTick; |
| 303 |
j = MDSequenceGetNumberOfTracks(seq); |
| 304 |
timeScaling->trackNums = (int *)calloc(sizeof(int), j); |
| 305 |
for (i = 0; (trackNo = [self sortedTrackNumberAtIndex: i]) >= 0; i++) { |
| 306 |
if (![self isFocusTrack:trackNo]) |
| 307 |
continue; |
| 308 |
timeScaling->trackNums[timeScaling->ntracks] = trackNo; |
| 309 |
timeScaling->ntracks++; |
| 310 |
} |
| 311 |
timeScaling->trackNums = (int *)realloc(timeScaling->trackNums, sizeof(int) * timeScaling->ntracks); |
| 312 |
timeScaling->startPos = (int32_t *)calloc(sizeof(int32_t), timeScaling->ntracks); |
| 313 |
timeScaling->originalTicks = (MDTickType **)calloc(sizeof(MDTickType *), timeScaling->ntracks); |
| 314 |
for (i = 0; i < timeScaling->ntracks; i++) { |
| 315 |
MDPointer *pt; |
| 316 |
MDEvent *ep; |
| 317 |
int trackNum = timeScaling->trackNums[i]; |
| 318 |
track = MDSequenceGetTrack(seq, trackNum); |
| 319 |
pt = MDPointerNew(track); |
| 320 |
if (trackNum == 0) { |
| 321 |
/* Conductor track: the meter events should be fixed */ |
| 322 |
timeScaling->meterPos = IntGroupNew(); |
| 323 |
} |
| 324 |
if (MDPointerJumpToTick(pt, startTick)) { |
| 325 |
timeScaling->startPos[i] = MDPointerGetPosition(pt); |
| 326 |
} else { |
| 327 |
timeScaling->startPos[i] = MDTrackGetNumberOfEvents(track); |
| 328 |
} |
| 329 |
timeScaling->originalTicks[i] = (MDTickType *)calloc(sizeof(MDTickType), MDTrackGetNumberOfEvents(track) - timeScaling->startPos[i] + 1); /* +1 for end-of-track */ |
| 330 |
for (ep = MDPointerCurrent(pt), j = 0; ep != NULL; ep = MDPointerForward(pt)) { |
| 331 |
if (trackNum == 0 && MDGetKind(ep) == kMDEventTimeSignature) { |
| 332 |
/* Record the position of the meter events */ |
| 333 |
IntGroupAdd(timeScaling->meterPos, MDPointerGetPosition(pt), 1); |
| 334 |
} else { |
| 335 |
timeScaling->originalTicks[i][j++] = MDGetTick(ep); |
| 336 |
} |
| 337 |
} |
| 338 |
MDPointerRelease(pt); |
| 339 |
timeScaling->originalTicks[i][j] = MDTrackGetDuration(track); |
| 340 |
if (trackNum == 0) { |
| 341 |
timeScaling->origMeterPos = IntGroupNew(); |
| 342 |
IntGroupCopy(timeScaling->origMeterPos, timeScaling->meterPos); |
| 343 |
} |
| 344 |
} |
| 345 |
} |
| 346 |
} else { |
| 347 |
[super doMouseDown: theEvent]; |
| 348 |
} |
| 349 |
} |
| 350 |
|
| 351 |
- (int)modifyLocalGraphicTool:(int)originalGraphicTool |
| 352 |
{ |
| 353 |
if (originalGraphicTool == kGraphicRectangleSelectTool || originalGraphicTool == kGraphicPencilTool) |
| 354 |
originalGraphicTool = kGraphicIbeamSelectTool; |
| 355 |
return originalGraphicTool; |
| 356 |
} |
| 357 |
|
| 358 |
- (void)doMouseDragged: (NSEvent *)theEvent |
| 359 |
{ |
| 360 |
if (timeScaling != NULL) { |
| 361 |
[self scaleSelectedTimeWithEvent:theEvent]; |
| 362 |
[(MyDocument *)[dataSource document] setEditingRangeStart:timeScaling->startTick end:timeScaling->newEndTick]; |
| 363 |
return; |
| 364 |
} |
| 365 |
|
| 366 |
[super doMouseDragged: theEvent]; |
| 367 |
if (selectionPath != nil) { |
| 368 |
int i; |
| 369 |
GraphicClientView *view; |
| 370 |
NSRect rect; |
| 371 |
|
| 372 |
rect = [selectionPath bounds]; |
| 373 |
|
| 374 |
/* Set the selection paths for other clientViews */ |
| 375 |
for (i = 1; (view = [dataSource clientViewAtIndex: i]) != nil; i++) { |
| 376 |
NSRect viewRect = [view bounds]; |
| 377 |
rect.origin.y = viewRect.origin.y - 1.0f; |
| 378 |
rect.size.height = viewRect.size.height + 2.0f; |
| 379 |
[view setSelectRegion: [NSBezierPath bezierPathWithRect: rect]]; |
| 380 |
} |
| 381 |
} |
| 382 |
} |
| 383 |
|
| 384 |
- (void)doMouseUp: (NSEvent *)theEvent |
| 385 |
{ |
| 386 |
NSPoint pt1, pt2; |
| 387 |
MDTickType tick1, tick2; |
| 388 |
int i; |
| 389 |
int32_t trackNo; |
| 390 |
GraphicClientView *view; |
| 391 |
BOOL shiftDown = (([theEvent modifierFlags] & NSShiftKeyMask) != 0); |
| 392 |
MyDocument *document = (MyDocument *)[dataSource document]; |
| 393 |
float ppt = [dataSource pixelsPerTick]; |
| 394 |
|
| 395 |
/* Clear the selection paths for other clientViews */ |
| 396 |
for (i = 1; (view = [dataSource clientViewAtIndex: i]) != nil; i++) { |
| 397 |
[view setSelectRegion: nil]; |
| 398 |
} |
| 399 |
|
| 400 |
if (timeScaling != NULL) { |
| 401 |
/* Time scaling */ |
| 402 |
/* If this is the first call since start, ask the user whether |
| 403 |
she wants to insert tempo. */ |
| 404 |
static BOOL sFirstInvocation = YES; |
| 405 |
int insertTempo = 0; |
| 406 |
NSString *str = MyAppCallback_getObjectGlobalSettings(@"scale_selected_time_dialog.insert_tempo"); |
| 407 |
NSPoint mousePt = [self convertPoint:[theEvent locationInWindow] fromView:nil]; |
| 408 |
if (str == nil) { |
| 409 |
insertTempo = 0; |
| 410 |
} else if (strtol([str UTF8String], NULL, 0) == 0) { |
| 411 |
insertTempo = 0; |
| 412 |
} else insertTempo = 1; |
| 413 |
if (sFirstInvocation) { |
| 414 |
NSAlert *alert = [[NSAlert alloc] init]; |
| 415 |
int response; |
| 416 |
[alert setMessageText:[NSString stringWithFormat:@"Tempo events %s inserted to keep timings. OK?", (insertTempo ? "ARE" : "are NOT")]]; |
| 417 |
[alert setInformativeText:@"This setting can be changed anytime by 'Scale Selected Time' dialog."]; |
| 418 |
[alert addButtonWithTitle:[NSString stringWithFormat:@"OK, %s insert", (insertTempo ? "do" : "don't")]]; |
| 419 |
[alert addButtonWithTitle:@"Cancel"]; |
| 420 |
[alert addButtonWithTitle:[NSString stringWithFormat:@"NO, %s insert", (insertTempo ? "don't" : "do")]]; |
| 421 |
[alert setAlertStyle:NSWarningAlertStyle]; |
| 422 |
response = (int)[alert runModal]; |
| 423 |
[alert autorelease]; |
| 424 |
if (response == NSAlertThirdButtonReturn) { |
| 425 |
insertTempo = !insertTempo; |
| 426 |
MyAppCallback_setObjectGlobalSettings(@"scale_selected_time_dialog.insert_tempo", [NSString stringWithFormat:@"%d", insertTempo]); |
| 427 |
|
| 428 |
} else if (response == NSAlertSecondButtonReturn) { |
| 429 |
return; |
| 430 |
} |
| 431 |
sFirstInvocation = NO; |
| 432 |
} |
| 433 |
/* Register undo for selections and editing range */ |
| 434 |
/* [document getEditingRangeStart: &tick1 end: &tick2]; */ |
| 435 |
[[[self undoManager] prepareWithInvocationTarget:document] |
| 436 |
setEditingRangeStart:timeScaling->startTick end:timeScaling->endTick]; |
| 437 |
|
| 438 |
/* Revert temporary scaling */ |
| 439 |
[self scaleSelectedTimeWithEvent:nil]; |
| 440 |
IntGroupRelease(timeScaling->meterPos); |
| 441 |
IntGroupRelease(timeScaling->origMeterPos); |
| 442 |
timeScaling->meterPos = NULL; |
| 443 |
timeScaling->origMeterPos = NULL; |
| 444 |
|
| 445 |
/* Scale time with undo registration */ |
| 446 |
timeScaling->newEndTick = mousePt.x / ppt; |
| 447 |
if (timeScaling->newEndTick < timeScaling->startTick) |
| 448 |
return; |
| 449 |
[document scaleTimeFrom:timeScaling->startTick to:timeScaling->endTick newDuration:timeScaling->newEndTick - timeScaling->startTick insertTempo:insertTempo setSelection:NO]; |
| 450 |
tick1 = timeScaling->startTick; |
| 451 |
tick2 = timeScaling->newEndTick; |
| 452 |
for (i = 0; i < timeScaling->ntracks; i++) |
| 453 |
free(timeScaling->originalTicks[i]); |
| 454 |
free(timeScaling->originalTicks); |
| 455 |
free(timeScaling->startPos); |
| 456 |
free(timeScaling->trackNums); |
| 457 |
free(timeScaling); |
| 458 |
timeScaling = NULL; |
| 459 |
return; |
| 460 |
|
| 461 |
} else { |
| 462 |
|
| 463 |
if (isLoupeDragging) { |
| 464 |
[super doMouseUp: theEvent]; |
| 465 |
return; |
| 466 |
} |
| 467 |
|
| 468 |
/* Editing range */ |
| 469 |
pt1 = [[selectPoints objectAtIndex: 0] pointValue]; |
| 470 |
if (isDragging) { |
| 471 |
pt2 = [[selectPoints objectAtIndex: 1] pointValue]; |
| 472 |
} else pt2 = pt1; |
| 473 |
tick1 = (MDTickType)floor(pt1.x / ppt); |
| 474 |
tick2 = (MDTickType)floor(pt2.x / ppt); |
| 475 |
if (tick1 < 0) |
| 476 |
tick1 = 0; |
| 477 |
if (tick2 < 0) |
| 478 |
tick2 = 0; |
| 479 |
if (tick1 > tick2) { |
| 480 |
MDTickType tick3 = tick1; |
| 481 |
tick1 = tick2; |
| 482 |
tick2 = tick3; |
| 483 |
} |
| 484 |
} |
| 485 |
|
| 486 |
if (tick1 < tick2) { |
| 487 |
/* Select all events within this tick range */ |
| 488 |
for (i = 0; (trackNo = [self sortedTrackNumberAtIndex: i]) >= 0; i++) { |
| 489 |
MDTrack *track; |
| 490 |
MDPointer *pt; |
| 491 |
IntGroup *pset; |
| 492 |
MDSelectionObject *obj; |
| 493 |
int32_t pos1, pos2; |
| 494 |
if (![self isFocusTrack:trackNo]) |
| 495 |
continue; |
| 496 |
track = [[document myMIDISequence] getTrackAtIndex: trackNo]; |
| 497 |
if (track == NULL) |
| 498 |
continue; |
| 499 |
pt = MDPointerNew(track); |
| 500 |
if (pt == NULL) |
| 501 |
break; |
| 502 |
pset = IntGroupNew(); |
| 503 |
if (pset == NULL) |
| 504 |
break; |
| 505 |
MDPointerJumpToTick(pt, tick1); |
| 506 |
pos1 = MDPointerGetPosition(pt); |
| 507 |
MDPointerJumpToTick(pt, tick2); |
| 508 |
pos2 = MDPointerGetPosition(pt); |
| 509 |
if (pos1 < pos2) { |
| 510 |
if (IntGroupAdd(pset, pos1, pos2 - pos1) != kMDNoError) |
| 511 |
break; |
| 512 |
} |
| 513 |
obj = [[MDSelectionObject allocWithZone: [self zone]] initWithMDPointSet: pset]; |
| 514 |
obj->track = track; |
| 515 |
if (shiftDown) { |
| 516 |
[document toggleSelection: obj inTrack: trackNo sender: self]; |
| 517 |
} else { |
| 518 |
[document setSelection: obj inTrack: trackNo sender: self]; |
| 519 |
} |
| 520 |
[obj release]; |
| 521 |
IntGroupRelease(pset); |
| 522 |
MDPointerRelease(pt); |
| 523 |
} |
| 524 |
} |
| 525 |
|
| 526 |
/* Change editing range */ |
| 527 |
if (shiftDown) { |
| 528 |
MDTickType oldTick1, oldTick2; |
| 529 |
[document getEditingRangeStart: &oldTick1 end: &oldTick2]; |
| 530 |
if (oldTick1 >= 0 && oldTick1 < tick1) |
| 531 |
tick1 = oldTick1; |
| 532 |
if (oldTick2 > tick2) |
| 533 |
tick2 = oldTick2; |
| 534 |
} |
| 535 |
[document setEditingRangeStart: tick1 end: tick2]; |
| 536 |
if (tick1 == tick2 && [theEvent clickCount] >= 2) |
| 537 |
[[dataSource playingViewController] setCurrentTick: tick1]; |
| 538 |
} |
| 539 |
|
| 540 |
- (void)doMouseMoved: (NSEvent *)theEvent |
| 541 |
{ |
| 542 |
NSPoint pt; |
| 543 |
int n; |
| 544 |
NSUInteger modifierFlags; |
| 545 |
localGraphicTool = [self modifyLocalGraphicTool:[[self dataSource] graphicTool]]; |
| 546 |
if ([theEvent type] == NSFlagsChanged) { |
| 547 |
pt = [self convertPoint:[[self window] convertScreenToBase:[NSEvent mouseLocation]] fromView:nil]; |
| 548 |
} else { |
| 549 |
pt = [self convertPoint:[theEvent locationInWindow] fromView:nil]; |
| 550 |
} |
| 551 |
modifierFlags = [theEvent modifierFlags]; |
| 552 |
n = [self isMouseOnEditStartPositions:pt]; |
| 553 |
if (n != 0) { |
| 554 |
if (n == 1 && (modifierFlags & (NSAlternateKeyMask | NSShiftKeyMask))) { |
| 555 |
[[NSCursor horizontalMoveZoomCursor] set]; |
| 556 |
} else { |
| 557 |
[[NSCursor horizontalMoveCursor] set]; |
| 558 |
} |
| 559 |
} else { |
| 560 |
if ([theEvent modifierFlags] & NSAlternateKeyMask) |
| 561 |
[[NSCursor loupeCursor] set]; |
| 562 |
else [[NSCursor IBeamCursor] set]; |
| 563 |
} |
| 564 |
} |
| 565 |
|
| 566 |
- (void)doFlagsChanged:(NSEvent *)theEvent |
| 567 |
{ |
| 568 |
[self doMouseMoved:theEvent]; |
| 569 |
} |
| 570 |
|
| 571 |
@end |