Develop and Download Open Source Software

Browse Subversion Repository

Contents of /trunk/Classes/GraphicWindowController.m

Parent Directory Parent Directory | Revision Log Revision Log


Revision 201 - (show annotations) (download)
Thu Apr 14 14:18:07 2022 UTC (2 years, 1 month ago) by toshinagata1964
File size: 101900 byte(s)
Value resolution in the strip chart can be selected from 0.25,0.5,1,2,4.
1 /*
2 GraphicWindowController.m
3 */
4 /*
5 Copyright (c) 2000-2017 Toshi Nagata. All rights reserved.
6
7 This program is free software; you can redistribute it and/or modify
8 it under the terms of the GNU General Public License as published by
9 the Free Software Foundation version 2 of the License.
10
11 This program is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 GNU General Public License for more details.
15 */
16
17 #import "GraphicWindowController.h"
18 #import "NSWindowControllerAdditions.h"
19 #import "GraphicSplitterView.h"
20 #import "PianoRollView.h"
21 #import "PianoRollRulerView.h"
22 #import "StripChartView.h"
23 #import "StripChartRulerView.h"
24 #import "TimeChartView.h"
25 #import "GraphicBackgroundView.h"
26 #import "MyDocument.h"
27 #import "MyMIDISequence.h"
28 #import "ColorCell.h"
29 #import "TrackAttributeCell.h"
30 #import "MyPopUpButtonCell.h"
31 #import "MyComboBoxCell.h"
32 #import "MDObjects.h"
33 #import "MyWindow.h"
34 #import "PlayingViewController.h"
35 #import "RemapDevicePanelController.h"
36 #import "RecordPanelController.h"
37 #import "MyAppController.h"
38 #import "PasteWarningPanelController.h"
39
40 const float kMinimumTickIntervalsInPixels = 8.0f;
41
42 enum {
43 kPlusButtonTag = 1000,
44 kMinusButtonTag = 1001,
45 kShrinkButtonTag = 2000,
46 kExpandButtonTag = 2001,
47 kSelectButtonTag = 3000,
48 kIbeamButtonTag = 3001,
49 kPencilButtonTag = 3002,
50 kShapePopUpTag = 3003,
51 kModePopUpTag = 3004,
52 kInfoTextTag = 3005,
53 kEditingRangeStartTextTag = 3006,
54 kEditingRangeEndTextTag = 3007,
55 kCursorInfoTextTag = 3008,
56 kQuantizePopUpTag = 3020,
57 kLinearMenuTag = 3021,
58 kParabolaMenuTag = 3022,
59 kArcMenuTag = 3023,
60 kSigmoidMenuTag = 3024,
61 kRandomMenuTag = 3025,
62 kSetMenuTag = 3050,
63 kAddMenuTag = 3051,
64 kScaleMenuTag = 3052,
65 kLimitMaxMenuTag = 3053,
66 kLimitMinMenuTag = 3054,
67 kQuantizeMenuTag = 3100
68 };
69
70 /* IDs for track list tableView */
71 static NSString *sTableColumnIDs[] = {
72 @"number", @"edit", @"visible", @"name", @"ch", @"solo", @"mute", @"device"
73 };
74
75 enum {
76 kTrackNumberID = 0,
77 kEditableID = 1,
78 kVisibleID = 2,
79 kTrackNameID = 3,
80 kChannelID = 4,
81 kSoloID = 5,
82 kMuteID = 6,
83 kDeviceNameID = 7
84 };
85
86 static NSImage *sPencilSmallImage = NULL;
87 static NSImage *sEyeOpenImage = NULL;
88 static NSImage *sEyeCloseImage = NULL;
89 static NSImage *sSpeakerImage = NULL;
90 static NSImage *sSpeakerGrayImage = NULL;
91 static NSImage *sMuteImage = NULL;
92 static NSImage *sMuteNonImage = NULL;
93 static NSImage *sSoloImage = NULL;
94 static NSImage *sSoloNonImage = NULL;
95
96 static NSString *sNeedsReloadClientViewNotification = @"reload client views";
97 static NSString *sClearChangingColorFlagNotification = @"clear changing color flag";
98
99 static int
100 sTableColumnIDToInt(id identifier)
101 {
102 int i;
103 for (i = sizeof(sTableColumnIDs) / sizeof(sTableColumnIDs[0]) - 1; i >= 0; i--)
104 if ([sTableColumnIDs[i] isEqualToString: identifier])
105 return i;
106 return -1;
107 }
108
109 @implementation GraphicWindowController
110
111 - (float)rulerWidth
112 {
113 return 40.0f;
114 }
115
116 - (id)init {
117 self = [super initWithWindowNibName:@"GraphicWindow"];
118 [self setShouldCloseDocument: YES];
119 lastMouseClientViewIndex = -1;
120 lastTimeIndicator = -1;
121 return self;
122 }
123
124 - (void)dealloc {
125 int i;
126 if (trackingRectTag != 0)
127 [myMainView removeTrackingRect: trackingRectTag];
128 [[NSNotificationCenter defaultCenter]
129 removeObserver:self];
130 if (calib != NULL)
131 MDCalibratorRelease(calib);
132 for (i = 0; i < myClientViewsCount; i++) {
133 [records[i].client release];
134 [records[i].ruler release];
135 [records[i].splitter release];
136 }
137 if (sortedTrackNumbers != NULL)
138 free(sortedTrackNumbers);
139 if (zoomUndoBuffer != nil)
140 [zoomUndoBuffer release];
141 [super dealloc];
142 }
143
144 - (void)windowWillClose:(NSNotification *)notification
145 {
146 [playingViewController pressStopButton:self];
147 }
148
149 #pragma mark ==== NSWindowControllerAdditions overrides ====
150
151 + (BOOL)canContainMultipleTracks
152 {
153 return YES;
154 }
155
156 #pragma mark ==== Handling track lists ====
157
158 - (void)setFocusFlag: (BOOL)flag onTrack: (int)trackNum extending: (BOOL)extendFlag
159 {
160 int i;
161 MDTrackAttribute attr;
162 MyMIDISequence *seq = [[self document] myMIDISequence];
163 if (!extendFlag) {
164 for (i = [self trackCount] - 1; i >= 0; i--) {
165 attr = [seq trackAttributeAtIndex: i];
166 if (i == trackNum && flag)
167 attr |= kMDTrackAttributeEditable;
168 else
169 attr &= ~kMDTrackAttributeEditable;
170 [[self document] setTrackAttribute: attr forTrack: i];
171 }
172 } else {
173 attr = [seq trackAttributeAtIndex: trackNum];
174 if (flag)
175 attr |= kMDTrackAttributeEditable;
176 else
177 attr &= ~kMDTrackAttributeEditable;
178 [[self document] setTrackAttribute: attr forTrack: trackNum];
179 }
180 visibleTrackCount = -1; /* Needs update */
181 [self reloadClientViews];
182 }
183
184 - (BOOL)isFocusTrack: (int)trackNum
185 {
186 if (trackNum >= 0 && trackNum < [self trackCount]) {
187 MDTrackAttribute attr = [[[self document] myMIDISequence] trackAttributeAtIndex: trackNum];
188 return ((attr & kMDTrackAttributeEditable) != 0);
189 } else return NO;
190 }
191
192 - (BOOL)isTrackSelected: (int32_t)trackNo
193 {
194 return [myTableView isRowSelected: trackNo];
195 }
196
197 - (void)setIsTrackSelected: (int32_t)trackNo flag: (BOOL)flag
198 {
199 if (flag)
200 [myTableView selectRowIndexes: [NSIndexSet indexSetWithIndex: trackNo] byExtendingSelection: YES];
201 else
202 [myTableView deselectRow: trackNo];
203 }
204
205 - (int32_t)trackCount
206 {
207 return [[[self document] myMIDISequence] trackCount];
208 }
209
210
211 - (int)sortedTrackNumberAtIndex: (int)index
212 {
213 int n = [self trackCount];
214 if (sortedTrackNumbers == NULL || visibleTrackCount < 0) {
215 int i, j, k;
216 // TrackInfo info;
217 if (sortedTrackNumbers == NULL)
218 sortedTrackNumbers = (int *)malloc(sizeof(int) * n);
219 else
220 sortedTrackNumbers = (int *)realloc(sortedTrackNumbers, sizeof(int) * n);
221 j = k = 0;
222 for (i = 0; i < n; i++) {
223 MDTrackAttribute attr = [[[self document] myMIDISequence] trackAttributeAtIndex: i];
224 #if 1
225 if ((attr & kMDTrackAttributeHidden) && !(attr & kMDTrackAttributeEditable))
226 continue;
227 if (attr & kMDTrackAttributeEditable) {
228 memmove(sortedTrackNumbers + j + 1, sortedTrackNumbers + j, sizeof(int) * (k - j));
229 sortedTrackNumbers[j++] = i;
230 } else {
231 sortedTrackNumbers[k] = i;
232 }
233 k++;
234 #else
235 if (!(attr & kMDTrackAttributeHidden)) {
236 if (attr & kMDTrackAttributeEditable) {
237 memmove(sortedTrackNumbers + j + 1, sortedTrackNumbers + j, sizeof(int) * (k - j));
238 sortedTrackNumbers[j++] = i;
239 } else {
240 sortedTrackNumbers[k] = i;
241 }
242 k++;
243 }
244 #endif
245 }
246 visibleTrackCount = k;
247 }
248 if (index >= 0 && index < visibleTrackCount)
249 return sortedTrackNumbers[index];
250 else return -1;
251 }
252
253 - (int32_t)visibleTrackCount
254 {
255 if (sortedTrackNumbers == NULL || visibleTrackCount < 0)
256 [self sortedTrackNumberAtIndex: 0]; /* Rebuild the internal cache; the returned value is discarded */
257 return visibleTrackCount;
258 }
259
260 #pragma mark ==== Pixel/tick conversion ====
261
262 - (float)pixelsPerQuarter
263 {
264 return pixelsPerQuarter;
265 }
266
267 - (void)setPixelsPerQuarter: (float)newPixelsPerQuarter;
268 {
269 // NSRect visibleRect;
270 float pos;
271 if (pixelsPerQuarter == newPixelsPerQuarter)
272 return;
273 if (myClientViewsCount > 0) {
274 /* Keep scroll position at the left side */
275 pos = [self scrollPositionOfClientViews];
276 pos *= newPixelsPerQuarter / pixelsPerQuarter;
277 }
278 pixelsPerQuarter = newPixelsPerQuarter;
279 if (myClientViewsCount > 0) {
280 [self reloadClientViews];
281 [self scrollClientViewsToPosition: pos];
282 }
283 }
284
285 - (float)pixelsPerTick
286 {
287 return pixelsPerQuarter / [[self document] timebase];
288 }
289
290 - (MDTickType)quantizedTickFromPixel: (float)pixel
291 {
292 float ppt = [self pixelsPerTick];
293 float timebase = [[self document] timebase];
294 float tickQuantum = quantize * timebase;
295 MDTickType tick = pixel / ppt;
296 MDTickType basetick; /* The tick at the beginning of the bar */
297 MDTickType qtick;
298 int32_t measure, beat, mtick;
299 if (tickQuantum == 0.0f)
300 return tick;
301 MDCalibratorTickToMeasure(calib, tick, &measure, &beat, &mtick);
302 basetick = MDCalibratorMeasureToTick(calib, measure, 1, 0);
303 qtick = basetick + (MDTickType)(floor((float)(tick - basetick) / tickQuantum + 0.5) * tickQuantum);
304 return qtick;
305 }
306
307 - (float)quantizedPixelFromPixel: (float)pixel
308 {
309 if (quantize == 0.0)
310 return pixel;
311 else return [self quantizedTickFromPixel: pixel] * [self pixelsPerTick];
312 }
313
314 - (float)pixelQuantum
315 {
316 if (quantize == 0.0)
317 return 1.0f;
318 else return quantize * [self pixelsPerQuarter];
319 }
320
321 #pragma mark ==== Time Indicator ====
322
323 - (NSBezierPath *)timeIndicatorPathAtBeat: (float)beat
324 {
325 NSRect aRect;
326 NSPoint pt;
327 NSBezierPath *path;
328 int n;
329 aRect = [[records[0].client superview] bounds];
330 pt.x = beat * [self pixelsPerQuarter];
331 if (pt.x < aRect.origin.x || pt.x > aRect.origin.x + aRect.size.width)
332 return nil;
333 pt.y = 0;
334 pt = [myFloatingView convertPoint: pt fromView: records[0].client];
335 path = [NSBezierPath bezierPath];
336 for (n = 0; n < myClientViewsCount; n++) {
337 aRect = [myFloatingView convertRect: [[records[n].client superview] bounds] fromView: records[n].client];
338 pt.y = aRect.origin.y + aRect.size.height;
339 [path moveToPoint: pt];
340 pt.y = aRect.origin.y;
341 [path lineToPoint: pt];
342 }
343 return path;
344 }
345
346 - (NSBezierPath *)bouncingBallPathAtBeat: (float)beat
347 {
348 return nil;
349 }
350
351 - (void)showTimeIndicatorAtBeat: (float)beat
352 {
353 NSBezierPath *path, *bpath;
354 if (beat < 0 || ![myMainView canDraw] || ![myFloatingView canDraw])
355 return;
356 path = [self timeIndicatorPathAtBeat: beat];
357 if (path) {
358 NSRect bounds = [path bounds];
359 NSPoint origin;
360 [self hideTimeIndicator];
361 bpath = [self bouncingBallPathAtBeat: beat];
362 if (bpath)
363 bounds = NSUnionRect(bounds, [bpath bounds]);
364 bounds = NSInsetRect(bounds, -2, -1);
365 bounds = [myMainView convertRect: bounds fromView: myFloatingView];
366 origin.x = (CGFloat)floor(bounds.origin.x);
367 origin.y = (CGFloat)floor(bounds.origin.y);
368 bounds.size.width = (CGFloat)ceil(bounds.origin.x + bounds.size.width - origin.x);
369 bounds.size.height = (CGFloat)ceil(bounds.origin.y + bounds.size.height - origin.y);
370 bounds.origin = origin;
371 [myFloatingView lockFocus];
372 [path stroke];
373 if (bpath)
374 [bpath fill];
375 [myFloatingView unlockFocus];
376 timeIndicatorRect = bounds;
377 }
378 timeIndicatorPos = beat;
379 endOfSequencePos = [[[self document] myMIDISequence] sequenceDuration];
380 }
381
382 - (void)hideTimeIndicator
383 {
384 int n;
385 if (!NSIsEmptyRect(timeIndicatorRect)) {
386 if ([myFloatingView canDraw]) {
387 [myFloatingView lockFocus];
388 NSRect tRect = [myFloatingView convertRect:timeIndicatorRect fromView:myMainView];
389 for (n = 0; n < myClientViewsCount; n++) {
390 NSRect aRect = [myFloatingView convertRect: [[records[n].client superview] bounds] fromView: records[n].client];
391 aRect = NSIntersectionRect(aRect, tRect);
392 NSEraseRect(aRect);
393 }
394 [myFloatingView unlockFocus];
395 }
396 [myMainView displayRect:timeIndicatorRect];
397 }
398 timeIndicatorRect = NSZeroRect;
399 }
400
401 - (void)invalidateTimeIndicatorRect
402 {
403 int n;
404 NSView *view;
405 if (!NSIsEmptyRect(timeIndicatorRect)) {
406 /* Redraw the 'timeIndicatorRect' portion of each splitter view */
407 for (n = 0; n < myClientViewsCount; n++) {
408 view = records[n].client;
409 [view setNeedsDisplayInRect: [view convertRect: timeIndicatorRect fromView: nil]];
410 view = records[n].splitter;
411 [view setNeedsDisplayInRect: [view convertRect: timeIndicatorRect fromView: nil]];
412 }
413 }
414 }
415
416 - (void)showPlayPosition: (NSNotification *)notification
417 {
418 NSRect visibleRect, documentRect;
419 float beat = [[[notification userInfo] objectForKey: @"position"] floatValue];
420 float pos = beat * [self pixelsPerQuarter];
421 float width;
422
423 if ([myMainView isHiddenOrHasHiddenAncestor]) {
424 return;
425 }
426 {
427 /* If dragging in some client view, then don't autoscroll to play position */
428 GraphicClientView *lastView = [self lastMouseClientView];
429 if (lastView != nil && [lastView isDragging])
430 return;
431 /* If event tracking in this window, then don't autoscroll to play position */
432 if ([[[NSRunLoop currentRunLoop] currentMode] isEqualToString:NSEventTrackingRunLoopMode] && [[NSApp currentEvent] window] == [self window])
433 return;
434 }
435
436 if (pos < 0) {
437 [self hideTimeIndicator];
438 return;
439 }
440 documentRect = [records[0].client frame];
441 visibleRect = [[records[0].client superview] bounds];
442 width = documentRect.size.width - visibleRect.size.width;
443 if (pos < visibleRect.origin.x - documentRect.origin.x
444 || pos >= visibleRect.origin.x + visibleRect.size.width - documentRect.origin.x) {
445 [self hideTimeIndicator];
446 if (pos > width)
447 pos = width;
448 [myScroller setFloatValue: pos / width];
449 [self scrollClientViewsToPosition: pos];
450 }
451 [self showTimeIndicatorAtBeat: beat];
452 }
453
454 #pragma mark ==== Time marks ====
455
456 // Calculate the intervals of vertical lines.
457 // lineIntervalInPixels: the interval in pixels with which the vertical lines are to be drawn
458 // mediumCount: every mediumCount lines, a vertical line with "medium thickness" appears
459 // majorCount: every majorCount lines, a vertical line with "large thickness" appears
460 - (void)verticalLinesFromTick: (MDTickType)fromTick timeSignature: (MDEvent **)timeSignature nextTimeSignature: (MDEvent **)nextTimeSignature lineIntervalInPixels: (float *)lineIntervalInPixels mediumCount: (int *)mediumCount majorCount: (int *)majorCount
461 {
462 MDTickType sTick, nsTick;
463 float ppb;
464 float interval;
465 int mdCount, mjCount;
466 MDEvent *ep1, *ep2;
467 int sig0, sig1;
468 if (myClientViewsCount > 0 && calib != NULL) {
469 /* Get time signature at fromTick */
470 MDCalibratorJumpToTick(calib, fromTick);
471 ep1 = MDCalibratorGetEvent(calib, NULL, kMDEventTimeSignature, -1);
472 } else {
473 ep1 = NULL;
474 }
475 if (ep1 == NULL) {
476 /* Assume 4/4 */
477 sig0 = sig1 = 4;
478 sTick = 0;
479 } else {
480 const unsigned char *p = MDGetMetaDataPtr(ep1);
481 sig0 = p[0];
482 sig1 = (1 << p[1]);
483 if (sig1 == 0)
484 sig1 = 4;
485 sTick = MDGetTick(ep1);
486 }
487 if (calib != NULL && (ep2 = MDCalibratorGetNextEvent(calib, NULL, kMDEventTimeSignature, -1)) != NULL)
488 nsTick = MDGetTick(ep2);
489 else {
490 ep2 = NULL;
491 nsTick = kMDMaxTick;
492 }
493 ppb = [self pixelsPerTick] * [[self document] timebase] * 4 / sig1;
494 if (ppb * 0.125 >= kMinimumTickIntervalsInPixels) {
495 interval = ppb * 0.125f;
496 mdCount = 4;
497 mjCount = 8;
498 while (interval >= kMinimumTickIntervalsInPixels * 2) {
499 interval *= 0.5f;
500 }
501 } else if (ppb * 0.5 >= kMinimumTickIntervalsInPixels) {
502 interval = ppb * 0.5f;
503 mdCount = 2;
504 mjCount = sig0 * mdCount;
505 if (interval >= kMinimumTickIntervalsInPixels * 2) {
506 interval *= 0.5f;
507 mdCount *= 2;
508 mjCount *= 2;
509 }
510 } else if (ppb >= kMinimumTickIntervalsInPixels) {
511 interval = ppb;
512 mdCount = mjCount = sig0;
513 } else {
514 interval = ppb * sig0;
515 mjCount = 5;
516 while (interval < kMinimumTickIntervalsInPixels) {
517 interval *= 10;
518 }
519 if (interval >= kMinimumTickIntervalsInPixels * 5) {
520 interval *= 0.5f;
521 mjCount = 2;
522 }
523 mdCount = mjCount;
524 }
525 if (timeSignature)
526 *timeSignature = ep1;
527 if (nextTimeSignature)
528 *nextTimeSignature = ep2;
529 if (lineIntervalInPixels)
530 *lineIntervalInPixels = interval;
531 if (mediumCount)
532 *mediumCount = mdCount;
533 if (majorCount)
534 *majorCount = mjCount;
535 }
536
537 - (void)convertTick: (MDTickType)aTick toMeasure: (int32_t *)measure beat: (int32_t *)beat andTick: (int32_t *)tick
538 {
539 MDCalibratorTickToMeasure(calib, aTick, measure, beat, tick);
540 }
541
542 - (void)editingRangeChanged: (NSNotification *)notification
543 {
544 MDTickType startTick, endTick;
545 NSTextField *tx1, *tx2;
546 tx1 = (NSTextField *)[[[self window] contentView] viewWithTag: kEditingRangeStartTextTag];
547 tx2 = (NSTextField *)[[[self window] contentView] viewWithTag: kEditingRangeEndTextTag];
548 [(MyDocument *)[self document] getEditingRangeStart: &startTick end: &endTick];
549 if (startTick < 0 || startTick >= kMDMaxTick) {
550 [tx1 setStringValue: @""];
551 [tx2 setStringValue: @""];
552 } else {
553 int32_t startMeasure, startBeat, startSubTick;
554 int32_t endMeasure, endBeat, endSubTick;
555 MDCalibratorTickToMeasure(calib, startTick, &startMeasure, &startBeat, &startSubTick);
556 MDCalibratorTickToMeasure(calib, endTick, &endMeasure, &endBeat, &endSubTick);
557 [tx1 setStringValue: [NSString stringWithFormat: @"%4d:%2d:%4d", startMeasure, startBeat, startSubTick]];
558 [tx2 setStringValue: [NSString stringWithFormat: @"%4d:%2d:%4d", endMeasure, endBeat, endSubTick]];
559 }
560 // [tx setNeedsDisplay: YES];
561 [self setNeedsReloadClientViews];
562 }
563
564 - (IBAction)showEditingRange:(id)sender
565 {
566 MDTickType startTick, endTick;
567 float pos, width;
568 [(MyDocument *)[self document] getEditingRangeStart: &startTick end: &endTick];
569 pos = startTick * [self pixelsPerTick];
570 width = [myMainView bounds].size.width / 4;
571 if (pos < width)
572 pos = 0;
573 else pos -= width;
574 [self scrollClientViewsToPosition:pos];
575 }
576
577 #pragma mark ==== Client views ====
578
579 /* Adjust the sizes of the client views so that they are aligned from top of the
580 main view and the total height is aHeight (i.e. there is an empty space below aHeight).
581 The width is resized to fill the current main view */
582 - (void)adjustClientViewsInHeight: (float)aHeight
583 {
584 int i;
585 float proportion;
586 float totalHeight;
587 GraphicBackgroundView *containerView;
588 NSRect aFrame, frame;
589
590 if (myClientViewsCount == 0)
591 return;
592
593 aFrame = [myMainView bounds];
594 aFrame.origin.y = aFrame.size.height - aHeight;
595 aFrame.size.height = aHeight;
596
597 // Relocate the TimeChartView
598 containerView = records[0].container;
599 frame = [containerView frame];
600 frame.origin.y = (aFrame.origin.y + aFrame.size.height) - frame.size.height;
601 frame.origin.x = aFrame.origin.x;
602 frame.size.width = aFrame.size.width;
603 [containerView setFrame:frame];
604
605 if (myClientViewsCount == 1) {
606 [self setNeedsReloadClientViews];
607 return;
608 }
609
610 // Calculate the total client size (except for the TimeChartView)
611 totalHeight = 0.0f;
612 for (i = 1; i < myClientViewsCount; i++) {
613 containerView = records[i].container;
614 totalHeight += [containerView frame].size.height;
615 }
616 proportion = (aHeight - frame.size.height) / totalHeight;
617
618 // Resize other views
619 for (i = 1; i < myClientViewsCount; i++) {
620 containerView = records[i].container;
621 frame.size.height = (CGFloat)floor([containerView frame].size.height * proportion + 0.5);
622 frame.origin.y = frame.origin.y - frame.size.height;
623 if (i == myClientViewsCount - 1) {
624 frame.size.height += (frame.origin.y - aFrame.origin.y);
625 frame.origin.y = aFrame.origin.y;
626 }
627 [containerView setFrame:frame];
628 // Sometimes the clipview enclosing the ruler view is not resized correctly.
629 if (records[i].ruler != nil) {
630 NSClipView *cview = (NSClipView *)[records[i].ruler superview];
631 NSRect cframe = [cview frame];
632 cframe.size.height = frame.size.height - cframe.origin.y;
633 [cview setFrame:cframe];
634 }
635 }
636 [self setNeedsReloadClientViews];
637 }
638
639 - (void)updateTrackingRect
640 {
641 NSRect bounds;
642 NSPoint mouseLoc;
643 if (trackingRectTag != 0)
644 [myMainView removeTrackingRect: trackingRectTag];
645 bounds = [myMainView bounds];
646 mouseLoc = [myMainView convertPoint: [[self window] convertScreenToBase: [NSEvent mouseLocation]] fromView: nil];
647 trackingRectTag = [myMainView addTrackingRect: bounds owner: self userData: nil assumeInside: NSMouseInRect(mouseLoc, bounds, [myMainView isFlipped])];
648 }
649
650 - (float)scrollPositionOfClientViews
651 {
652 NSRect visibleRect, documentRect;
653 if (myClientViewsCount == 0)
654 return 0.0f;
655 visibleRect = [[records[0].client superview] bounds];
656 documentRect = [records[0].client frame];
657 return visibleRect.origin.x - documentRect.origin.x;
658 }
659
660 - (void)scrollClientViewsToPosition: (float)pos
661 {
662 NSRect visibleRect, documentRect;
663 int i;
664 float x;
665 if (myClientViewsCount == 0)
666 return;
667 if (pos < 0)
668 pos = 0;
669 for (i = 0; i < myClientViewsCount; i++) {
670 visibleRect = [[records[i].client superview] bounds];
671 documentRect = [records[i].client frame];
672 if (pos > documentRect.size.width)
673 pos = documentRect.size.width;
674 x = documentRect.origin.x + pos;
675 if (fabs(x - visibleRect.origin.x) > 0.2) {
676 visibleRect.origin.x = x;
677 [records[i].client scrollPoint: visibleRect.origin];
678 [records[i].client setNeedsDisplay:YES];
679 }
680 if (i == 0) {
681 float width = documentRect.size.width - visibleRect.size.width;
682 [myScroller setFloatValue: pos / width];
683 [myScroller setKnobProportion: visibleRect.size.width / documentRect.size.width];
684 }
685 }
686 }
687
688 - (void)scrollClientViewsToTick: (MDTickType)tick
689 {
690 [self scrollClientViewsToPosition: tick * [self pixelsPerTick]];
691 }
692
693 - (IBAction)scrollerMoved: (id)sender
694 {
695 NSRect visibleRect, documentRect;
696 NSScrollerPart hitPart;
697 float pos, width, lineWidth;
698 // NSLog(@"hitPart = %d", [sender hitPart]);
699 if (myClientViewsCount == 0 || myScroller == nil)
700 return;
701 hitPart = [sender hitPart];
702 pos = [sender floatValue];
703 documentRect = [records[0].client frame];
704 visibleRect = [[records[0].client superview] bounds];
705 if (visibleRect.size.width > documentRect.size.width)
706 return;
707 width = documentRect.size.width - visibleRect.size.width;
708 pos *= width;
709 lineWidth = 32.0f;
710 if (lineWidth >= visibleRect.size.width * 0.5f)
711 lineWidth = visibleRect.size.width * 0.5f;
712 switch (hitPart) {
713 case NSScrollerDecrementLine: pos -= lineWidth; break;
714 case NSScrollerIncrementLine: pos += lineWidth; break;
715 case NSScrollerDecrementPage: pos -= (visibleRect.size.width - lineWidth); break;
716 case NSScrollerIncrementPage: pos += (visibleRect.size.width - lineWidth); break;
717 default: break;
718 }
719 if (pos < 0)
720 pos = 0.0f;
721 if (pos > width)
722 pos = width;
723 // [myScroller setFloatValue: pos / width];
724 pos = (float)floor(pos);
725 [self scrollClientViewsToPosition: pos];
726 // visibleRect.origin.x = pos + documentRect.origin.x;
727 // [records[0].client scrollPoint: visibleRect.origin];
728 }
729
730 - (void)zoomClientViewsWithPixelsPerQuarter:(float)ppq startingPos:(float)pos
731 {
732 float oldppq = [self pixelsPerQuarter];
733 float oldpos = [self scrollPositionOfClientViews];
734 if (zoomUndoBuffer == nil) {
735 zoomUndoBuffer = [[NSMutableArray allocWithZone:[self zone]] init];
736 }
737 if (zoomUndoIndex < [zoomUndoBuffer count] / 2)
738 [zoomUndoBuffer removeObjectsInRange:NSMakeRange(zoomUndoIndex * 2, [zoomUndoBuffer count] - zoomUndoIndex * 2)];
739 [zoomUndoBuffer addObject:[NSNumber numberWithFloat:oldppq]];
740 [zoomUndoBuffer addObject:[NSNumber numberWithFloat:oldpos]];
741 zoomUndoIndex++;
742 [self setPixelsPerQuarter: ppq];
743 [self scrollClientViewsToPosition: pos];
744 [self reflectClientViews];
745 }
746
747 - (void)unzoomClientViews
748 {
749 if (zoomUndoIndex > 0) {
750 float oldppq = [self pixelsPerQuarter];
751 float oldpos = [self scrollPositionOfClientViews];
752 float ppq = [[zoomUndoBuffer objectAtIndex:zoomUndoIndex * 2 - 2] floatValue];
753 float pos = [[zoomUndoBuffer objectAtIndex:zoomUndoIndex * 2 - 1] floatValue];
754 [zoomUndoBuffer replaceObjectAtIndex:zoomUndoIndex * 2 - 2 withObject:[NSNumber numberWithFloat:oldppq]];
755 [zoomUndoBuffer replaceObjectAtIndex:zoomUndoIndex * 2 - 1 withObject:[NSNumber numberWithFloat:oldpos]];
756 zoomUndoIndex--;
757 [self setPixelsPerQuarter:ppq];
758 [self scrollClientViewsToPosition: pos];
759 [self reflectClientViews];
760 }
761 }
762
763 - (void)rezoomClientViews
764 {
765 if (zoomUndoBuffer != nil && zoomUndoIndex < [zoomUndoBuffer count] / 2) {
766 float oldppq = [self pixelsPerQuarter];
767 float oldpos = [self scrollPositionOfClientViews];
768 float ppq = [[zoomUndoBuffer objectAtIndex:zoomUndoIndex * 2] floatValue];
769 float pos = [[zoomUndoBuffer objectAtIndex:zoomUndoIndex * 2 + 1] floatValue];
770 [zoomUndoBuffer replaceObjectAtIndex:zoomUndoIndex * 2 withObject:[NSNumber numberWithFloat:oldppq]];
771 [zoomUndoBuffer replaceObjectAtIndex:zoomUndoIndex * 2 + 1 withObject:[NSNumber numberWithFloat:oldpos]];
772 zoomUndoIndex++;
773 [self setPixelsPerQuarter:ppq];
774 [self scrollClientViewsToPosition: pos];
775 [self reflectClientViews];
776 }
777 }
778
779 - (float)clientViewWidth
780 {
781 float width = (float)(([self sequenceDurationInQuarter] + 4.0) * [self pixelsPerQuarter]);
782 float minWidth = (float)([myMainView bounds].size.width - [self rulerWidth] - [NSScroller scrollerWidth]);
783 if (width > minWidth)
784 return width;
785 else return minWidth;
786 }
787
788 - (void)reloadClientViews
789 {
790 int i;
791 id firstResponder = [[self window] firstResponder];
792 for (i = 0; i < myClientViewsCount; i++) {
793 [records[i].client reloadData];
794 }
795 [self reflectClientViews];
796
797 /* Redraw focus ring */
798 for (i = 0; i < myClientViewsCount; i++) {
799 if (firstResponder == records[i].container) {
800 [firstResponder setKeyboardFocusRingNeedsDisplayInRect:[firstResponder bounds]];
801 break;
802 }
803 }
804
805 /* Remove "NeedsReloadClientView" notifications */
806 [[NSNotificationQueue defaultQueue] dequeueNotificationsMatching: [NSNotification notificationWithName: sNeedsReloadClientViewNotification object: self] coalesceMask: NSNotificationCoalescingOnName | NSNotificationCoalescingOnSender];
807 }
808
809 - (void)needsReloadClientViews: (NSNotification *)aNotification
810 {
811 [self reloadClientViews];
812 }
813
814 - (void)setNeedsReloadClientViews
815 {
816 /* The "reload client" messages are coalesced and sent only once per event loop */
817 [[NSNotificationQueue defaultQueue] enqueueNotification: [NSNotification notificationWithName: sNeedsReloadClientViewNotification object: self] postingStyle: NSPostWhenIdle coalesceMask: NSNotificationCoalescingOnName | NSNotificationCoalescingOnSender forModes: nil];
818 }
819
820 - (void)reflectClientViews
821 {
822 NSRect visibleRect, documentRect;
823 double pos, wid;
824 if (myClientViewsCount == 0 || myScroller == nil)
825 return;
826 documentRect = [records[0].client frame];
827 visibleRect = [[records[0].client superview] bounds];
828 if (visibleRect.size.width >= documentRect.size.width) {
829 /* pos = 0.0;
830 wid = 1.0; */
831 [myScroller setEnabled: NO];
832 } else {
833 pos = (visibleRect.origin.x - documentRect.origin.x) / (documentRect.size.width - visibleRect.size.width);
834 wid = visibleRect.size.width / documentRect.size.width;
835 [myScroller setEnabled: YES];
836 [myScroller setKnobProportion: (float)wid];
837 [myScroller setDoubleValue: pos];
838 }
839 }
840
841 /* Customized autoresizing of clientviews */
842 - (BOOL)backgroundView:(NSView *)aView resizedWithOldSize:(NSSize)oldSize
843 {
844 if (aView == myMainView) {
845 NSSize newSize = [myMainView bounds].size;
846 [self adjustClientViewsInHeight: newSize.height];
847 return YES;
848 } else return NO;
849 }
850
851 - (void)limitWindowSize
852 {
853 float windowHeight, currentHeight, limitHeight;
854 int i;
855 NSWindow *window = [self window];
856 NSSize minSize = [window minSize];
857 windowHeight = [window frame].size.height;
858 currentHeight = limitHeight = 0;
859 for (i = 0; i < myClientViewsCount; i++) {
860 limitHeight += [[records[i].client class] minHeight];
861 currentHeight += [[records[i].client enclosingScrollView] frame].size.height;
862 }
863 minSize.height = windowHeight - (currentHeight - limitHeight);
864 [window setMinSize: minSize];
865 // NSLog(@"limitWindowSize %@", NSStringFromSize(minSize));
866 }
867
868 - (void)createClientViewWithClasses: (id)chartClass : (id)rulerClass
869 {
870 NSScrollView *scrollView;
871 NSClipView *clipView;
872 GraphicClientView *clientView;
873 GraphicRulerView *rulerView;
874 GraphicSplitterView *splitterView;
875 GraphicBackgroundView *containerView;
876 NSRect aRect, rect;
877 float scrollerWidth, rulerWidth, height, splitterHeight, minHeight;
878 unsigned int mask;
879
880 if (myClientViewsCount >= kGraphicWindowControllerMaxNumberOfClientViews)
881 return; // Too many client views
882
883 aRect = [myMainView bounds];
884 scrollerWidth = [NSScroller scrollerWidth];
885 minHeight = [chartClass minHeight];
886
887 if (myClientViewsCount == 0) {
888 height = minHeight;
889 aRect.origin.y += (aRect.size.height - height);
890 aRect.size.height = height;
891 mask = NSViewMinYMargin;
892 splitterHeight = 0.0f;
893 } else {
894 rect = [records[myClientViewsCount - 1].container frame];
895 // scrollView = [records[myClientViewsCount - 1].client enclosingScrollView];
896 if (myClientViewsCount == 1) {
897 // rect = [scrollView frame];
898 splitterHeight = 4.0f;
899 mask = NSViewHeightSizable;
900 } else {
901 // rect = [records[myClientViewsCount - 1].splitter frame];
902 splitterHeight = 16.0f;
903 mask = NSViewMaxYMargin;
904 }
905 height = rect.origin.y - aRect.origin.y - splitterHeight;
906 if (height < minHeight) {
907 height = minHeight;
908 [self adjustClientViewsInHeight: aRect.size.height - (height + splitterHeight)];
909 }
910 aRect.size.height = height + splitterHeight;
911 }
912
913 // Create the container view
914 rect = aRect;
915 // Don't autorelease, since we are going to retain it
916 containerView = [[GraphicBackgroundView allocWithZone:[self zone]] initWithFrame:rect];
917 [myMainView addSubview:containerView];
918 [containerView setAutoresizingMask: (NSViewWidthSizable | mask)];
919 [containerView setAutoresizesSubviews:YES];
920 records[myClientViewsCount].container = containerView;
921
922 // Create the chart view
923 // NSScrollView
924 mask = NSViewWidthSizable | NSViewHeightSizable;
925 rulerWidth = [self rulerWidth];
926 rect.origin = NSMakePoint(rulerWidth, splitterHeight);
927 rect.size.width = aRect.size.width - rulerWidth;
928 rect.size.height = aRect.size.height - splitterHeight;
929 scrollView = [[[NSScrollView allocWithZone: [self zone]] initWithFrame: rect] autorelease];
930 [containerView addSubview: scrollView];
931 [scrollView setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable];
932 [scrollView setHasHorizontalScroller: NO];
933
934 // The chart view
935 rect.origin.x = rect.origin.y = 0.0f;
936 // Don't autorelease, since we are going to retain it
937 clientView = [[chartClass allocWithZone: [self zone]] initWithFrame: rect];
938 [scrollView setDocumentView: clientView];
939 if ([clientView hasVerticalScroller]) {
940 [scrollView setHasVerticalScroller: YES];
941 } else {
942 [scrollView setHasVerticalScroller: NO];
943 rect = [scrollView frame];
944 rect.size.width -= scrollerWidth;
945 [scrollView setFrame: rect];
946 }
947 [clientView setDataSource: self];
948 records[myClientViewsCount].client = clientView;
949
950 if (myClientViewsCount > 0 && rulerClass != nil) {
951 // Create the ruler view
952 // NSClipView
953 rect.origin = NSMakePoint(0, splitterHeight);
954 rect.size.width = rulerWidth;
955 rect.size.height = aRect.size.height - splitterHeight;
956 clipView = [[[NSClipView allocWithZone: [self zone]] initWithFrame: rect] autorelease];
957 [containerView addSubview: clipView];
958 [clipView setAutoresizingMask:NSViewMaxXMargin | NSViewHeightSizable];
959
960 // The chart view
961 rect.origin.x = rect.origin.y = 0.0f;
962 rulerView = [[rulerClass allocWithZone: [self zone]] initWithFrame: rect];
963 [clipView setDocumentView: rulerView];
964 [rulerView setClientView: clientView];
965 records[myClientViewsCount].ruler = rulerView;
966 }
967
968 if (myClientViewsCount > 0) {
969 // Create the splitter view
970 rect.origin = NSMakePoint(0, 0);
971 rect.size.width = aRect.size.width;
972 rect.size.height = splitterHeight;
973 splitterView = [[GraphicSplitterView allocWithZone: [self zone]] initWithFrame: rect];
974 [containerView addSubview: splitterView];
975 [splitterView setAutoresizingMask: (NSViewWidthSizable | NSViewMaxYMargin)];
976 records[myClientViewsCount].splitter = splitterView;
977 }
978
979 myClientViewsCount++;
980
981 if ([clientView isKindOfClass: [StripChartView class]]) {
982 [self setStripChartAtIndex: myClientViewsCount - 1 kind: kMDEventNote code: 0];
983 }
984
985 [clientView reloadData];
986
987 [self limitWindowSize];
988 }
989
990 - (void)collapseClientViewAtIndex: (int)index
991 {
992 int i;
993 NSRect frame;
994 float y;
995 frame = [records[index].container frame];
996 y = frame.origin.y + frame.size.height;
997 [[records[index].client enclosingScrollView] removeFromSuperview];
998 [[records[index].ruler superview] removeFromSuperview];
999 [records[index].splitter removeFromSuperview];
1000 [records[index].container removeFromSuperview];
1001 [records[index].client autorelease];
1002 [records[index].ruler autorelease];
1003 [records[index].splitter autorelease];
1004 [records[index].container autorelease];
1005 for (i = index; i < myClientViewsCount - 1; i++) {
1006 records[i] = records[i + 1];
1007 }
1008 memset(&records[i], 0, sizeof(records[i]));
1009 myClientViewsCount--;
1010 if (index < myClientViewsCount) {
1011 frame = [records[index].container frame];
1012 frame.size.height = y - frame.origin.y;
1013 [records[index].container setFrame:frame];
1014 } else if (index > 1) {
1015 frame = [records[index - 1].container frame];
1016 frame.size.height += frame.origin.y;
1017 frame.origin.y = 0;
1018 [records[index - 1].container setFrame:frame];
1019 }
1020 [self setNeedsReloadClientViews];
1021 [myMainView setNeedsDisplay: YES];
1022 [self limitWindowSize];
1023 }
1024
1025 - (void)splitterViewStartedDragging: (GraphicSplitterView *)theView
1026 {
1027 // Save the visible range of the client views
1028 int index;
1029 for (index = 1; index < myClientViewsCount; index++) {
1030 if (records[index].splitter == theView)
1031 break;
1032 }
1033 [records[index].client saveVisibleRange];
1034 if (index < myClientViewsCount - 1)
1035 [records[index + 1].client saveVisibleRange];
1036 }
1037
1038 - (void)splitterView: (GraphicSplitterView *)theView isDraggedTo: (float)y confirm: (BOOL)confirm
1039 {
1040 int index;
1041 // NSScrollView *scrollView;
1042 GraphicBackgroundView *containerView = nil;
1043 NSRect frame_above, frame_self, frame_below;
1044 float ymax, ymin;
1045 for (index = 1; index < myClientViewsCount; index++) {
1046 if (records[index].splitter == theView)
1047 break;
1048 }
1049 if (index == myClientViewsCount) {
1050 return;
1051 }
1052 frame_above = [records[index].container frame];
1053 frame_self = [records[index].splitter frame];
1054 y = (float)floor(y + 0.5); /* New position of the container frame */
1055 ymax = frame_above.origin.y + frame_above.size.height - frame_self.size.height;
1056 if (index == 1) {
1057 // Piano roll view cannot be collapsed
1058 if (y >= ymax - 32.0f)
1059 y = ymax - 32.0f;
1060 }
1061 if (y >= ymax) {
1062 y = ymax;
1063 if (confirm) {
1064 // Collapse this client
1065 [self collapseClientViewAtIndex: index];
1066 // Restore the visible range of the next client view
1067 [records[index].client restoreVisibleRange];
1068 return;
1069 }
1070 }
1071 if (confirm && y >= ymax - 32.0f) {
1072 // Avoid too narrow strip chart
1073 y = ymax - 32.0f;
1074 }
1075 if (index < myClientViewsCount - 1) {
1076 containerView = records[index + 1].container;
1077 frame_below = [containerView frame];
1078 ymin = frame_below.origin.y;
1079 if (records[index + 1].splitter != nil)
1080 ymin += [records[index + 1].splitter frame].size.height;
1081 } else {
1082 ymin = [myMainView bounds].origin.y;
1083 }
1084 if (y <= ymin) {
1085 y = ymin;
1086 if (confirm && index < myClientViewsCount - 1) {
1087 // Collapse the lowest client
1088 [self collapseClientViewAtIndex: index + 1];
1089 // Restore the visible range of this client
1090 if (index > 1)
1091 [records[index].client restoreVisibleRange];
1092 return;
1093 }
1094 }
1095 if (confirm && index < myClientViewsCount - 1 && y <= ymin + 32.0f) {
1096 // Avoid too narrow strip chart
1097 y = ymin + 32.0f;
1098 }
1099 frame_above.size.height += frame_above.origin.y - y;
1100 frame_above.origin.y = y;
1101 [records[index].container setFrame:frame_above];
1102 [records[index].container setNeedsDisplay:YES];
1103 [records[index].client reloadData];
1104 /* frame_self.origin.y = y;
1105 [records[index].splitter setFrame: frame_self];
1106 [records[index].splitter setNeedsDisplay: YES]; */
1107 if (containerView != nil) {
1108 frame_below.size.height = y - frame_below.origin.y;
1109 [containerView setFrame:frame_below];
1110 [containerView setNeedsDisplay:YES];
1111 [records[index + 1].client reloadData];
1112 } else if (confirm && y > ymin) {
1113 // Create a new client view
1114 [self createClientViewWithClasses: [StripChartView class] : [StripChartRulerView class]];
1115 // Scroll to the current position
1116 [self scrollClientViewsToPosition: [self scrollPositionOfClientViews]];
1117 }
1118 if (confirm) {
1119 // Restore the visible range of this and the next clients
1120 if (index > 1)
1121 [records[index].client restoreVisibleRange];
1122 if (index < myClientViewsCount - 1)
1123 [records[index + 1].client restoreVisibleRange];
1124 [myMainView display];
1125 } else {
1126 if (containerView == nil) {
1127 NSRect theRect = [myMainView bounds];
1128 theRect.size.height = y - ymin;
1129 [myMainView lockFocus];
1130 NSDrawWindowBackground(theRect);
1131 [myMainView unlockFocus];
1132 }
1133 [myMainView displayIfNeeded];
1134 }
1135 }
1136
1137 - (void)setStripChartAtIndex: (int)index kind: (int)kind code: (int)code
1138 {
1139 int32_t kindAndCode;
1140 MDSequence *sequence;
1141 int newKind;
1142 dprintf(1, "setStripChartAtIndex: %d %d %d\n", index, kind, code);
1143 if (![records[index].client isKindOfClass: [StripChartView class]])
1144 return;
1145 if (kind < 0 && code < 0)
1146 return;
1147 if (kind < 0)
1148 newKind = ([(StripChartView *)records[index].client kindAndCode] >> 16) & 65535;
1149 else
1150 newKind = kind;
1151 if (newKind == kMDEventControl) {
1152 if (code < 0)
1153 code = 11; // Expression
1154 } else if (newKind == kMDEventKeyPres) {
1155 if (code < 0)
1156 code = 60; // Central C
1157 } else code = -1;
1158
1159 /* if (records[index].calib != NULL)
1160 MDCalibratorRelease(records[index].calib); */
1161 sequence = [[[self document] myMIDISequence] mySequence];
1162 /*
1163 if (newKind == kMDEventTempo) {
1164 calib = MDCalibratorNew(sequence, NULL, newKind, -1);
1165 } else {
1166 for (n = 1; n < MDSequenceGetNumberOfTracks(sequence); n++) {
1167 track = MDSequenceGetTrack(sequence, n);
1168 if (n == 1) {
1169 calib = MDCalibratorNew(sequence, track, newKind, code);
1170 } else {
1171 MDCalibratorAppend(calib, track, newKind, code);
1172 }
1173 }
1174 }
1175 records[index].calib = calib;
1176 */
1177 kindAndCode = ((kind & 65535) << 16) | (code & 65535);
1178 [(StripChartView *)records[index].client setKindAndCode: kindAndCode];
1179 [records[index].splitter setKindAndCode: kindAndCode];
1180 [records[index].ruler setNeedsDisplay: YES];
1181 }
1182
1183 - (void)setStripChartAtIndex:(int)index resolution:(float)resolution
1184 {
1185 if (![records[index].client isKindOfClass: [StripChartView class]])
1186 return;
1187 [(StripChartView *)records[index].client setResolution:resolution];
1188 }
1189
1190 - (void)setStripChartAtIndex:(int)index track:(int)track
1191 {
1192 int32_t kindAndCode, kind, code;
1193 if (![records[index].client isKindOfClass: [StripChartView class]])
1194 return;
1195 kindAndCode = [(StripChartView *)records[index].client kindAndCode];
1196 kind = (kindAndCode > 16) & 65535;
1197 code = kindAndCode & 65535;
1198 if (kind == kMDEventTempo) {
1199 /* Only conductor track is allowed */
1200 track = 0;
1201 }
1202 [(StripChartView *)records[index].client setFocusTrack:track];
1203 [records[index].splitter setTrack:track];
1204 [records[index].ruler setNeedsDisplay: YES];
1205 }
1206
1207 - (IBAction)kindPopUpPressed: (id)sender
1208 {
1209 int i, kind;
1210 for (i = 1; i < myClientViewsCount; i++) {
1211 if (records[i].splitter == (GraphicSplitterView *)[sender superview]) {
1212 kind = (int)[[sender selectedItem] tag];
1213 [self setStripChartAtIndex: i kind: kind code: -1];
1214 break;
1215 }
1216 }
1217 }
1218
1219 - (void)codeMenuItemSelected: (NSMenuItem *)item inSplitterView: (GraphicSplitterView *)view
1220 {
1221 int i, code;
1222 for (i = 1; i < myClientViewsCount; i++) {
1223 if (records[i].splitter == view) {
1224 code = (int)[item tag];
1225 [self setStripChartAtIndex: i kind: -1 code: code];
1226 break;
1227 }
1228 }
1229 }
1230
1231 - (void)setResolution: (float)resolution inSplitterView: (GraphicSplitterView *)view
1232 {
1233 int i, code;
1234 for (i = 1; i < myClientViewsCount; i++) {
1235 if (records[i].splitter == view) {
1236 [self setStripChartAtIndex: i resolution: resolution];
1237 break;
1238 }
1239 }
1240 }
1241
1242 - (IBAction)trackPopUpPressedInSplitterView: (id)sender
1243 {
1244 int i, track;
1245 for (i = 1; i < myClientViewsCount; i++) {
1246 if (records[i].splitter == (GraphicSplitterView *)[sender superview]) {
1247 track = (int)[sender indexOfSelectedItem];
1248 [self setStripChartAtIndex:i track:track - 1];
1249 break;
1250 }
1251 }
1252 }
1253
1254 - (IBAction)expandHorizontally: (id)sender
1255 {
1256 float ppq = [self pixelsPerQuarter];
1257 float pos = [self scrollPositionOfClientViews];
1258 [self zoomClientViewsWithPixelsPerQuarter:ppq * 2 startingPos:pos * 2];
1259 // [self reloadClientViews];
1260 // [self reflectClientViews];
1261 /* NSRect documentRect, visibleRect;
1262 if (myClientViewsCount == 0)
1263 return;
1264 documentRect = [records[0].client frame];
1265 visibleRect = [[records[0].client superview] bounds];
1266 ppq = pixelsPerQuarter;
1267 r = documentRect.size.width / visibleRect.size.width;
1268 if (r < 1)
1269 return;
1270 else if (r < 2)
1271 ppq *= r;
1272 else ppq *= 2;
1273 [self setPixelsPerQuarter: ppq]; */
1274 }
1275
1276 - (IBAction)shrinkHorizontally: (id)sender
1277 {
1278 float ppq, r, pos;
1279 NSRect documentRect, visibleRect;
1280 if (myClientViewsCount == 0)
1281 return;
1282 documentRect = [records[0].client frame];
1283 visibleRect = [[records[0].client superview] bounds];
1284 ppq = pixelsPerQuarter;
1285 r = documentRect.size.width / visibleRect.size.width;
1286 if (r < 1)
1287 return;
1288 else if (r >= 2)
1289 r = 2;
1290 pos = [self scrollPositionOfClientViews];
1291 [self zoomClientViewsWithPixelsPerQuarter:ppq / r startingPos:pos / r];
1292 // [self reloadClientViews];
1293 // [self reflectClientViews];
1294 /* [self setPixelsPerQuarter: ppq / 2]; */
1295 }
1296
1297 - (GraphicClientView *)clientViewAtIndex: (int)index
1298 {
1299 if (index >= 0 && index < myClientViewsCount)
1300 return records[index].client;
1301 else return nil;
1302 }
1303
1304 - (GraphicSplitterView *)splitterViewAtIndex: (int)index
1305 {
1306 if (index >= 0 && index < myClientViewsCount)
1307 return records[index].splitter;
1308 else return nil;
1309 }
1310
1311 - (GraphicRulerView *)rulerViewAtIndex: (int)index
1312 {
1313 if (index >= 0 && index < myClientViewsCount)
1314 return records[index].ruler;
1315 else return nil;
1316 }
1317
1318 - (GraphicBackgroundView *)enclosingContainerForClientView:(id)view
1319 {
1320 int i;
1321 for (i = 0; i < myClientViewsCount; i++) {
1322 if (records[i].client == view || records[i].ruler == view || records[i].splitter == view)
1323 return records[i].container;
1324 }
1325 return nil;
1326 }
1327
1328
1329 /* Used by GraphicBackgroundView to send key events to the active client view */
1330 - (void)mouseEvent:(NSEvent *)theEvent receivedByClientView:(GraphicClientView *)cView
1331 {
1332 int i;
1333 for (i = 0; i < myClientViewsCount; i++) {
1334 if (records[i].client == cView) {
1335 lastMouseClientViewIndex = i;
1336 return;
1337 }
1338 }
1339 lastMouseClientViewIndex = -1;
1340 }
1341
1342 - (GraphicClientView *)lastMouseClientView
1343 {
1344 if (lastMouseClientViewIndex >= 0 && lastMouseClientViewIndex < myClientViewsCount)
1345 return records[lastMouseClientViewIndex].client;
1346 else return nil;
1347 }
1348
1349 #pragma mark ==== Window info ====
1350
1351 /* Update the device menu popup */
1352 static void
1353 sUpdateDeviceMenu(MyComboBoxCell *cell)
1354 {
1355 int i, n;
1356 id currentValue = [cell objectValueOfSelectedItem];
1357 [cell removeAllItems];
1358 [cell addItemWithObjectValue: @""];
1359 n = MDPlayerGetNumberOfDestinations();
1360 for (i = 0; i < n; i++) {
1361 char name[64];
1362 MDPlayerGetDestinationName(i, name, sizeof name);
1363 [cell addItemWithObjectValue: [NSString localizedStringWithFormat: @"%s", name]];
1364 }
1365 [cell selectItemWithObjectValue: currentValue];
1366 }
1367
1368 /* Update the device menu when MIDI setup is changed */
1369 - (void)midiSetupDidChange: (NSNotification *)aNotification
1370 {
1371 NSTableColumn *tableColumn;
1372 MyComboBoxCell *cell;
1373 tableColumn = [myTableView tableColumnWithIdentifier: sTableColumnIDs[kDeviceNameID]];
1374 cell = (MyComboBoxCell *)[tableColumn dataCell];
1375 sUpdateDeviceMenu(cell);
1376 }
1377
1378 /* Catch window resize operation */
1379 - (void)windowDidResize:(NSNotification *)aNotification
1380 {
1381 if (waitingForFirstWindowResize) {
1382 int i;
1383 for (i = 2; i < myClientViewsCount; i++) {
1384 [records[i].client restoreVisibleRange];
1385 }
1386 waitingForFirstWindowResize = NO;
1387 }
1388 }
1389
1390 - (void)windowDidLoad
1391 {
1392 NSTableColumn *tableColumn;
1393 NSCell *cell;
1394 NSFont *font;
1395 NSEnumerator *enumerator;
1396 // NSView *view;
1397 NSRect frame, bounds;
1398 int i;
1399
1400 [super windowDidLoad];
1401
1402 /* Accepts the mouse move events */
1403 [[self window] setAcceptsMouseMovedEvents: YES];
1404
1405 /* Register the notification */
1406 [[NSNotificationCenter defaultCenter]
1407 addObserver:self
1408 selector:@selector(trackModified:)
1409 name:MyDocumentTrackModifiedNotification
1410 object:[self document]];
1411
1412 [[NSNotificationCenter defaultCenter]
1413 addObserver:self
1414 selector:@selector(showPlayPosition:)
1415 name:MyDocumentPlayPositionNotification
1416 object:[self document]];
1417
1418 [[NSNotificationCenter defaultCenter]
1419 addObserver:self
1420 selector:@selector(trackInserted:)
1421 name:MyDocumentTrackInsertedNotification
1422 object:[self document]];
1423
1424 [[NSNotificationCenter defaultCenter]
1425 addObserver:self
1426 selector:@selector(trackDeleted:)
1427 name:MyDocumentTrackDeletedNotification
1428 object:[self document]];
1429
1430 [[NSNotificationCenter defaultCenter]
1431 addObserver:self
1432 selector:@selector(trackModified:)
1433 name:MyDocumentSelectionDidChangeNotification
1434 object:[self document]];
1435
1436 [[NSNotificationCenter defaultCenter]
1437 addObserver: self
1438 selector: @selector(needsReloadClientViews:)
1439 name: sNeedsReloadClientViewNotification
1440 object: self];
1441
1442 [[NSNotificationCenter defaultCenter]
1443 addObserver:self
1444 selector:@selector(editingRangeChanged:)
1445 name:MyDocumentEditingRangeDidChangeNotification
1446 object:[self document]];
1447
1448 [[NSNotificationCenter defaultCenter]
1449 addObserver:self
1450 selector:@selector(windowDidResize:)
1451 name:NSWindowDidResizeNotification object:[self window]];
1452
1453 [[NSNotificationCenter defaultCenter]
1454 addObserver: self
1455 selector: @selector(clearChangingColorFlag:)
1456 name: sClearChangingColorFlagNotification
1457 object: self];
1458
1459 [myMainView setPostsFrameChangedNotifications: YES];
1460
1461 [[NSNotificationCenter defaultCenter]
1462 addObserver:self
1463 selector:@selector(midiSetupDidChange:)
1464 name:NSWindowDidResizeNotification
1465 object:[NSApp delegate]];
1466
1467 // Post a dummy resize notification when idle
1468 // (If the system silently resizes this window, then a 'real' resize notification is posted.
1469 // Otherwise, this 'dummy' notification is processed. In either case, the flag waitingForFirstWindowResize
1470 // is negated upon first invocation to windowDidResize.)
1471 [[NSNotificationQueue defaultQueue]
1472 enqueueNotification:[NSNotification notificationWithName:NSWindowDidResizeNotification object:[self window]]
1473 postingStyle:NSPostWhenIdle];
1474
1475 // This flag will be negated on first invocation to windowDidResize
1476 waitingForFirstWindowResize = YES;
1477
1478 calib = MDCalibratorNew([[[self document] myMIDISequence] mySequence], NULL, kMDEventTimeSignature, -1);
1479
1480 /* Set pixels per tick: about 1 cm per quarter note */
1481 [self setPixelsPerQuarter: 72 / 2.54f];
1482
1483 /* Create the time chart ruler */
1484 [self createClientViewWithClasses: [TimeChartView class] : nil];
1485
1486 /* Create the piano roll view */
1487 [self createClientViewWithClasses: [PianoRollView class] : [PianoRollRulerView class]];
1488
1489 /* Create the strip chart view */
1490 [self adjustClientViewsInHeight: (float)floor([myMainView bounds].size.height * 0.75)];
1491 [self createClientViewWithClasses: [StripChartView class] : [StripChartRulerView class]];
1492 [records[2].client setVisibleRangeMin:0.0f max:1.0f];
1493
1494 /* Set the default scale factor for the piano-roll view */
1495 [records[1].client setYScale: 7.0f]; /* 7 pixels per half-tone */
1496 // [records[1].client reloadData];
1497
1498 /* Center the piano-roll view vertically */
1499 frame = [records[1].client frame];
1500 bounds = [[records[1].client superview] bounds];
1501 if (frame.size.height > bounds.size.height) {
1502 bounds.origin.y = (frame.size.height - bounds.size.height) / 2;
1503 [records[1].client scrollPoint: bounds.origin];
1504 }
1505
1506 /* Make the piano-roll view the first responder */
1507 [[self window] setInitialFirstResponder: records[1].container];
1508
1509 [myScroller setEnabled: YES];
1510
1511 /* Set myTableView as the initial first responder */
1512 //[[self window] setInitialFirstResponder: myTableView];
1513
1514 /* Set up the data cells for the TableView */
1515 {
1516 font = [NSFont systemFontOfSize: [NSFont smallSystemFontSize]];
1517
1518 /* tableColumn = [myTableView tableColumnWithIdentifier: sTableColumnIDs[kVisibleID]];
1519 cell = [[[NSButtonCell alloc] init] autorelease];
1520 [(NSButtonCell *)cell setButtonType: NSSwitchButton];
1521 [cell setControlSize: NSSmallControlSize];
1522 [tableColumn setDataCell: cell]; */
1523
1524 if (sPencilSmallImage == NULL)
1525 sPencilSmallImage = [[NSImage imageNamed: @"pencil_small.png"] retain];
1526 if (sEyeOpenImage == NULL)
1527 sEyeOpenImage = [[NSImage imageNamed: @"eye_open.png"] retain];
1528 if (sEyeCloseImage == NULL)
1529 sEyeCloseImage = [[NSImage imageNamed: @"eye_close.png"] retain];
1530 if (sSpeakerImage == NULL)
1531 sSpeakerImage = [[NSImage imageNamed: @"speaker.png"] retain];
1532 if (sSpeakerGrayImage == NULL)
1533 sSpeakerGrayImage = [[NSImage imageNamed: @"speaker_gray.png"] retain];
1534 if (sMuteImage == NULL)
1535 sMuteImage = [[NSImage imageNamed: @"mute.png"] retain];
1536 if (sMuteNonImage == NULL)
1537 sMuteNonImage = [[NSImage imageNamed: @"mute_non.png"] retain];
1538 if (sSoloImage == NULL)
1539 sSoloImage = [[NSImage imageNamed: @"solo.png"] retain];
1540 if (sSoloNonImage == NULL)
1541 sSoloNonImage = [[NSImage imageNamed: @"solo_non.png"] retain];
1542
1543 for (i = 0; i < 4; i++) {
1544 static int s[] = {kEditableID, kVisibleID, kSoloID, kMuteID};
1545 // static NSString *n[] = {@"pencil_small.png", @"eye_open.png", @"speaker.png"};
1546 NSImage *im[] = {sPencilSmallImage, sEyeOpenImage, sSoloImage, sSpeakerImage};
1547 tableColumn = [myTableView tableColumnWithIdentifier: sTableColumnIDs[s[i]]];
1548 cell = [[[ColorCell alloc] init] autorelease];
1549 [cell setTarget: self];
1550 // [cell setAction: @selector(trackTableAction:)];
1551 [cell setImage: im[i]];
1552 if (s[i] == kSoloID) {
1553 [cell setRepresentedObject:[NSColor colorWithDeviceRed:1.0f green:1.0f blue:0.3f alpha:1.0f]];
1554 [(ColorCell *)cell setStrokesColor:NO];
1555 }
1556 [tableColumn setDataCell: cell];
1557 [[tableColumn headerCell] setImage: im[i]];
1558 }
1559
1560 [myTableView setAction: @selector(trackTableAction:)];
1561 [myTableView setDoubleAction: @selector(trackTableDoubleAction:)];
1562
1563 /* tableColumn = [myTableView tableColumnWithIdentifier: @"attribute"];
1564 cell = [[[TrackAttributeCell alloc] init] autorelease];
1565 [cell setAction: @selector(trackTableAction:)];
1566 [tableColumn setDataCell: cell]; */
1567
1568 tableColumn = [myTableView tableColumnWithIdentifier: sTableColumnIDs[kChannelID]];
1569 cell = [[[MyPopUpButtonCell alloc] initTextCell: @"-" pullsDown: NO] autorelease];
1570 [cell setBordered: NO];
1571 [cell setFont: font];
1572 for (i = 1; i <= 16; i++) {
1573 [(NSPopUpButtonCell *)cell addItemWithTitle: [NSString stringWithFormat: @"%d", i]];
1574 }
1575 [tableColumn setDataCell: cell];
1576
1577 tableColumn = [myTableView tableColumnWithIdentifier: sTableColumnIDs[kDeviceNameID]];
1578 cell = [[[MyComboBoxCell alloc] init] autorelease];
1579 [cell setEditable: YES];
1580 [cell setFont: font];
1581 [cell setBordered: NO];
1582 [cell setControlSize: NSSmallControlSize];
1583 [(MyComboBoxCell *)cell setCompletes: YES];
1584 sUpdateDeviceMenu((MyComboBoxCell *)cell);
1585 [(MyComboBoxCell *)cell setControlView: myTableView];
1586 [tableColumn setDataCell: cell];
1587
1588 [myTableView setRowHeight: [[NSWindowController sharedLayoutManager] defaultLineHeightForFont:font]];
1589 enumerator = [[myTableView tableColumns] objectEnumerator];
1590 while ((tableColumn = (NSTableColumn *)[enumerator nextObject]) != nil) {
1591 [[tableColumn dataCell] setFont: font];
1592 }
1593 }
1594
1595 {
1596 MyMIDISequence *seq = [[self document] myMIDISequence];
1597 /* Select the editable flag: only the first non-conductor track is made editable */
1598 for (i = [seq trackCount] - 1; i >= 0; i--) {
1599 MDTrackAttribute attr = [seq trackAttributeAtIndex: i];
1600 if (i == 1)
1601 attr |= kMDTrackAttributeEditable;
1602 else
1603 attr &= ~kMDTrackAttributeEditable;
1604 [seq setTrackAttribute: attr atIndex: i];
1605 }
1606 }
1607
1608 /* Resize the buttons for small icons (to work around bug? of IB) */
1609 /* 2006.1.2. This looks no longer necessary. */
1610 /* view = [[[self window] contentView] viewWithTag: kPlusButtonTag];
1611 frame = [view frame];
1612 frame.size.height = 16;
1613 [view setFrame: frame];
1614 view = [[[self window] contentView] viewWithTag: kMinusButtonTag];
1615 frame = [view frame];
1616 frame.size.height = 16;
1617 [view setFrame: frame];
1618 view = [[[self window] contentView] viewWithTag: kShrinkButtonTag];
1619 frame = [view frame];
1620 frame.size.height = 16;
1621 [view setFrame: frame];
1622 view = [[[self window] contentView] viewWithTag: kExpandButtonTag];
1623 frame = [view frame];
1624 frame.size.height = 16;
1625 [view setFrame: frame]; */
1626
1627 /* Set up the popup menus */
1628 {
1629 /* Shape popup menu */
1630 id myPopUpButton = [[[self window] contentView] viewWithTag: kShapePopUpTag];
1631 id menuItem;
1632 NSMenu *menu;
1633 NSString *imageName;
1634 NSImage *anImage;
1635 int i, itag;
1636 /* Resize the popup menu; the Interface Builder did not allow me to create
1637 a popup button with shadowless bezel style, so I use the normal popup button
1638 and change it into shadowless one programatically. The problem is that
1639 the "mini" popup button has the fixed 15-pixel height. As a workaround,
1640 the height is also set programatically. */
1641 frame = [myPopUpButton frame];
1642 frame.size.height = 16;
1643 [myPopUpButton setFrame: frame];
1644 [myPopUpButton setBezelStyle: NSShadowlessSquareBezelStyle];
1645 [myPopUpButton setImagePosition: NSNoImage];
1646 /* Set images */
1647 for (i = (int)[myPopUpButton numberOfItems] - 1; i >= 0; i--) {
1648 menuItem = [myPopUpButton itemAtIndex: i];
1649 switch ([menuItem tag]) {
1650 case kLinearMenuTag: imageName = @"linear.png"; break;
1651 case kParabolaMenuTag: imageName = @"parabola.png"; break;
1652 case kArcMenuTag: imageName = @"arc.png"; break;
1653 case kSigmoidMenuTag: imageName = @"sigmoid.png"; break;
1654 case kRandomMenuTag: imageName = @"random.png"; break;
1655 default: imageName = nil; break;
1656 }
1657 if (imageName != nil) {
1658 anImage = [NSImage imageNamed: imageName];
1659 [menuItem setImage: anImage];
1660 }
1661 }
1662
1663 /* Mode popup menu */
1664 myPopUpButton = [[[self window] contentView] viewWithTag: kModePopUpTag];
1665 frame = [myPopUpButton frame];
1666 frame.size.height = 16;
1667 [myPopUpButton setFrame: frame];
1668 [myPopUpButton setBezelStyle: NSShadowlessSquareBezelStyle];
1669
1670 graphicTool = kGraphicRectangleSelectTool;
1671 graphicLineShape = kGraphicLinearShape;
1672 graphicEditingMode = kGraphicSetMode;
1673
1674 /* Quantize popup menu */
1675 myPopUpButton = [[[self window] contentView] viewWithTag: kQuantizePopUpTag];
1676 frame = [myPopUpButton frame];
1677 frame.size.height = 16;
1678 [myPopUpButton setFrame: frame];
1679 [myPopUpButton setBezelStyle: NSShadowlessSquareBezelStyle];
1680 menu = [myPopUpButton menu];
1681 [[menu itemAtIndex: 0] setImage: [NSImage imageNamed: @"NQ.png"]];
1682 // while ([menu numberOfItems] > 0)
1683 // [menu removeItemAtIndex: 0];
1684 itag = kQuantizeMenuTag + 1;
1685 for (i = 0; i < 3; i++) {
1686 static NSString *fmt[] = {@"note%d.png", @"note%dd.png", @"note%d_3.png"};
1687 int j;
1688 for (j = 1; j <= 32; j *= 2) {
1689 menuItem = [[[NSMenuItem allocWithZone: [self zone]] initWithTitle: @"" action: @selector(quantizeSelected:) keyEquivalent: @""] autorelease];
1690 [menuItem setImage: [NSImage imageNamed: [NSString stringWithFormat: fmt[i], j]]];
1691 [menuItem setTag: itag];
1692 [menu addItem: menuItem];
1693 itag++;
1694 }
1695 }
1696 }
1697
1698 /* Initialize the playing view */
1699 [playingViewController windowDidLoad];
1700
1701 [self reloadClientViews];
1702 [self updateTrackingRect];
1703
1704 [[NSNotificationCenter defaultCenter]
1705 postNotificationName: MyAppControllerMIDISetupDidChangeNotification
1706 object: [NSApp delegate] userInfo: nil];
1707 }
1708
1709 - (NSString *)windowTitleForDocumentDisplayName:(NSString *)displayName
1710 {
1711 #if 1
1712 return displayName;
1713 #else
1714 if (myTrack != NULL) {
1715 char buf[256];
1716 MDTrackGetName(myTrack, buf, sizeof buf);
1717 // NSLog(@"Track name = %s\n", buf);
1718 return [NSString stringWithFormat:@"Graphic:%@:%s", displayName, buf];
1719 } else return displayName;
1720 #endif
1721 }
1722
1723 - (MDTickType)sequenceDuration
1724 {
1725 return [[[self document] myMIDISequence] sequenceDuration];
1726 }
1727
1728 - (float)sequenceDurationInQuarter
1729 {
1730 return (float)[self sequenceDuration] / [[self document] timebase];
1731 }
1732
1733 - (void)setInfoText: (NSString *)string
1734 {
1735 [[[[self window] contentView] viewWithTag: kInfoTextTag] setStringValue: string];
1736 }
1737
1738 - (void)setCursorInfoPosition: (NSPoint)position
1739 {
1740 NSTextField *field = (NSTextField *)[[[self window] contentView] viewWithTag: kCursorInfoTextTag];
1741 NSRect frame = [field frame];
1742 [field setFrameOrigin:NSMakePoint(position.x + 6, position.y - 8 - frame.size.height)];
1743 }
1744
1745 - (void)setCursorInfoText: (NSString *)string
1746 {
1747 NSTextField *field = (NSTextField *)[[[self window] contentView] viewWithTag: kCursorInfoTextTag];
1748 if (string == nil) {
1749 [field setHidden:YES];
1750 } else {
1751 CGFloat width, height;
1752 [field setHidden:NO];
1753 [field setStringValue: string];
1754 width = [[field cell] cellSizeForBounds:[field bounds]].width;
1755 height = [[field cell] cellSizeForBounds:[field bounds]].height;
1756 [field setFrameSize:NSMakeSize(width + 1, height + 1)];
1757 }
1758 }
1759
1760 - (void)updateCursorInfoForView: (NSView *)view atPosition: (NSPoint)pos
1761 {
1762 int option;
1763 NSString *s;
1764 if ([view isKindOfClass:[GraphicClientView class]]) {
1765 NSPoint pt = [myFloatingView convertPoint: pos fromView: view];
1766 NSTextField *field = (NSTextField *)[[[self window] contentView] viewWithTag: kCursorInfoTextTag];
1767 [self setCursorInfoPosition:pt];
1768 s = [(id)view infoTextForMousePoint:pos dragging:NO option:&option];
1769 if (option == 1) {
1770 [field setFont: [NSFont boldSystemFontOfSize:9]];
1771 } else {
1772 [field setFont: [NSFont systemFontOfSize:9]];
1773 }
1774 [self setCursorInfoText:s];
1775 } else {
1776 [self setCursorInfoText:nil];
1777 }
1778 }
1779
1780 - (void)changeFirstResponderWithEvent: (NSEvent *)theEvent
1781 {
1782 /* myTableView, one of the container views, or myPlayerView can be the first responder */
1783 NSPoint pt = [theEvent locationInWindow];
1784 NSWindow *theWindow = [self window];
1785 id obj = [theWindow firstResponder];
1786
1787 if ([obj isKindOfClass: [NSActionCell class]]) {
1788 obj = [obj controlView];
1789 } else if (obj == [theWindow fieldEditor: NO forObject: nil]) {
1790 obj = [obj delegate];
1791 }
1792
1793 if (NSPointInRect(pt, [[myMainView superview] convertRect: [myMainView frame] toView: nil])) {
1794 /* Look for the container view */
1795 int i;
1796 for (i = 0; i < myClientViewsCount; i++) {
1797 GraphicBackgroundView *view = records[i].container;
1798 if (NSPointInRect(pt, [[view superview] convertRect:[view frame] toView:nil])) {
1799 if (i == 0) {
1800 /* The time chart view does not accept first responder:
1801 the piano roll view is made first responder instead */
1802 [theWindow makeFirstResponder:records[1].container];
1803 } else {
1804 [theWindow makeFirstResponder:view];
1805 }
1806 return;
1807 }
1808 }
1809 }
1810 }
1811
1812 - (id)playingViewController
1813 {
1814 return playingViewController;
1815 }
1816
1817 #pragma mark ==== Editing operation in ClientViews ====
1818
1819 - (NSColor *)colorForTrack: (int)track enabled: (BOOL)flag
1820 {
1821 return [[self document] colorForTrack: track enabled: flag];
1822 }
1823
1824 - (void)dragNotesByTick: (MDTickType)deltaTick andNote: (int)deltaNote sender: (GraphicClientView *)sender optionFlag: (BOOL)optionFlag
1825 {
1826 int i, n, track;
1827 MDSelectionObject *pointSet;
1828 MyDocument *document = [self document];
1829 n = [self visibleTrackCount];
1830 if (optionFlag && (deltaTick != 0 || deltaNote != 0)) {
1831 // Duplicate notes
1832 for (i = 0; i < n; i++) {
1833 track = [self sortedTrackNumberAtIndex: i];
1834 if (track <= 0) // Ignore events in the conductor track
1835 continue;
1836 pointSet = [document selectionOfTrack: track];
1837 if (deltaNote != 0 && deltaTick == 0) {
1838 /* Select only note events */
1839 pointSet = [document eventSetInTrack:track eventKind:kMDEventNote eventCode:-1 fromTick:0 toTick:kMDMaxTick fromData:0 toData:256 inPointSet:pointSet];
1840 }
1841 if (pointSet == nil || IntGroupGetCount([pointSet pointSet]) <= 0)
1842 continue;
1843 [document duplicateMultipleEventsAt: pointSet ofTrack: track selectInsertedEvents: YES];
1844 }
1845 }
1846 if (deltaTick != 0) {
1847 NSNumber *deltaTickNum = [NSNumber numberWithLong: deltaTick];
1848 for (i = 0; i < n; i++) {
1849 track = [self sortedTrackNumberAtIndex: i];
1850 if (track < 0)
1851 continue;
1852 pointSet = [document selectionOfTrack: track];
1853 if (pointSet == nil || IntGroupGetCount([pointSet pointSet]) <= 0)
1854 continue;
1855 [document modifyTick: deltaTickNum ofMultipleEventsAt: pointSet inTrack: track mode: MyDocumentModifyAdd destinationPositions: nil setSelection: YES];
1856 }
1857 }
1858 if (deltaNote != 0) {
1859 NSNumber *deltaNum = [NSNumber numberWithInt: deltaNote];
1860 for (i = 0; i < n; i++) {
1861 track = [self sortedTrackNumberAtIndex: i];
1862 if (track < 0)
1863 continue;
1864 pointSet = [document selectionOfTrack: track];
1865 /* Select only note events */
1866 pointSet = [document eventSetInTrack:track eventKind:kMDEventNote eventCode:-1 fromTick:0 toTick:kMDMaxTick fromData:0 toData:256 inPointSet:pointSet];
1867 if (pointSet == nil || IntGroupGetCount([pointSet pointSet]) <= 0)
1868 continue;
1869 [document modifyCodes: deltaNum ofMultipleEventsAt: pointSet inTrack: track mode: MyDocumentModifyAdd];
1870 }
1871 }
1872 }
1873
1874 - (void)dragDurationByTick: (MDTickType)deltaTick sender: (GraphicClientView *)sender
1875 {
1876 int i, n, track;
1877 MDSelectionObject *pointSet;
1878 MyDocument *document = [self document];
1879 n = [self visibleTrackCount];
1880 if (deltaTick != 0) {
1881 NSNumber *deltaTickNum = [NSNumber numberWithLong: deltaTick];
1882 for (i = 0; i < n; i++) {
1883 track = [self sortedTrackNumberAtIndex: i];
1884 if (track < 0)
1885 continue;
1886 pointSet = [document selectionOfTrack: track];
1887 if (pointSet == nil || IntGroupGetCount([pointSet pointSet]) <= 0)
1888 continue;
1889 [document modifyDurations: deltaTickNum ofMultipleEventsAt: pointSet inTrack: track mode: MyDocumentModifyAdd];
1890 }
1891 }
1892 }
1893
1894 - (void)dragEventsOfKind: (int)kind andCode: (int)code byTick: (MDTickType)deltaTick andValue: (float)deltaValue sender: (GraphicClientView *)sender optionFlag: (BOOL)optionFlag
1895 {
1896 int i, n, track;
1897 MDSelectionObject *pointSet;
1898 MyDocument *document = [self document];
1899 n = [self visibleTrackCount];
1900 if (deltaTick != 0) {
1901 NSNumber *deltaTickNum = [NSNumber numberWithLong: deltaTick];
1902 for (i = 0; i < n; i++) {
1903 track = [self sortedTrackNumberAtIndex: i];
1904 if (track < 0)
1905 continue;
1906 pointSet = [document selectionOfTrack: track];
1907 if (pointSet == nil || IntGroupGetCount([pointSet pointSet]) <= 0)
1908 continue;
1909 if (optionFlag) {
1910 if ([document duplicateMultipleEventsAt: pointSet ofTrack: track selectInsertedEvents: YES])
1911 pointSet = [document selectionOfTrack: track];
1912 }
1913 [document modifyTick: deltaTickNum ofMultipleEventsAt: pointSet inTrack: track mode: MyDocumentModifyAdd destinationPositions: nil setSelection: YES];
1914 }
1915 }
1916 if (deltaValue != 0) {
1917 NSNumber *deltaNum = [NSNumber numberWithFloat: deltaValue];
1918 for (i = 0; i < n; i++) {
1919 track = [self sortedTrackNumberAtIndex: i];
1920 if (track < 0)
1921 continue;
1922 pointSet = [document selectionOfTrack: track];
1923 if (pointSet == nil || IntGroupGetCount([pointSet pointSet]) <= 0)
1924 continue;
1925 [document modifyData: deltaNum forEventKind: kind ofMultipleEventsAt: pointSet inTrack: track mode: MyDocumentModifyAdd];
1926 }
1927 }
1928 }
1929
1930 //- (IBAction)toggleDrawer: (id)sender
1931 //{
1932 // if ([sender state] == NSOnState)
1933 // [myDrawer open];
1934 // else
1935 // [myDrawer close];
1936 //}
1937
1938 - (IBAction)toolButton: (id)sender
1939 {
1940 [sender setState: NSOnState];
1941 switch ([sender tag]) {
1942 case kIbeamButtonTag:
1943 [[[[self window] contentView] viewWithTag: kSelectButtonTag] setState: NSOffState];
1944 [[[[self window] contentView] viewWithTag: kPencilButtonTag] setState: NSOffState];
1945 graphicTool = kGraphicIbeamSelectTool;
1946 // graphicSelectionMode = kGraphicIbeamSelectionMode;
1947 break;
1948 case kSelectButtonTag:
1949 [[[[self window] contentView] viewWithTag: kIbeamButtonTag] setState: NSOffState];
1950 [[[[self window] contentView] viewWithTag: kPencilButtonTag] setState: NSOffState];
1951 graphicTool = kGraphicRectangleSelectTool;
1952 // graphicSelectionMode = kGraphicRectangleSelectionMode;
1953 break;
1954 case kPencilButtonTag:
1955 [[[[self window] contentView] viewWithTag: kIbeamButtonTag] setState: NSOffState];
1956 [[[[self window] contentView] viewWithTag: kSelectButtonTag] setState: NSOffState];
1957 graphicTool = kGraphicPencilTool;
1958 break;
1959 }
1960 }
1961
1962 - (IBAction)shapeSelected: (id)sender
1963 {
1964 switch ([sender tag]) {
1965 case kLinearMenuTag: graphicLineShape = kGraphicLinearShape; break;
1966 case kParabolaMenuTag: graphicLineShape = kGraphicParabolaShape; break;
1967 case kArcMenuTag: graphicLineShape = kGraphicArcShape; break;
1968 case kSigmoidMenuTag: graphicLineShape = kGraphicSigmoidShape; break;
1969 case kRandomMenuTag: graphicLineShape = kGraphicRandomShape; break;
1970 }
1971 }
1972
1973 - (IBAction)modeSelected: (id)sender
1974 {
1975 int index;
1976 switch ([sender tag]) {
1977 case kSetMenuTag: graphicEditingMode = kGraphicSetMode; break;
1978 case kAddMenuTag: graphicEditingMode = kGraphicAddMode; break;
1979 case kScaleMenuTag: graphicEditingMode = kGraphicScaleMode; break;
1980 case kLimitMaxMenuTag: graphicEditingMode = kGraphicLimitMaxMode; break;
1981 case kLimitMinMenuTag: graphicEditingMode = kGraphicLimitMinMode; break;
1982 }
1983 // Redraw the strip chart views
1984 for (index = 0; index < myClientViewsCount; index++) {
1985 if ([records[index].client isKindOfClass: [StripChartView class]])
1986 [records[index].client setNeedsDisplay:YES];
1987 }
1988 }
1989
1990 - (IBAction)quantizeSelected: (id)sender
1991 {
1992 int i = (int)[sender tag] - kQuantizeMenuTag;
1993 if (i <= 0) {
1994 quantize = 0;
1995 } else {
1996 int n, m;
1997 n = (i - 1) / 6;
1998 m = (i - 1) % 6;
1999 if (n == 1)
2000 quantize = 6.0f;
2001 else if (n == 2)
2002 quantize = 8.0f / 3;
2003 else quantize = 4.0f;
2004 while (m-- > 0)
2005 quantize *= 0.5f;
2006 }
2007 }
2008
2009 - (int)graphicTool
2010 {
2011 return graphicTool;
2012 }
2013
2014 - (int)graphicLineShape
2015 {
2016 return graphicLineShape;
2017 }
2018
2019 - (int)graphicEditingMode
2020 {
2021 return graphicEditingMode;
2022 }
2023
2024 - (BOOL)mouseMoved:(NSEvent *)theEvent inView:(NSView *)view
2025 {
2026 NSPoint pt;
2027 NSRect rect;
2028 if (view == nil)
2029 return NO;
2030 pt = [theEvent locationInWindow];
2031 rect = [view convertRect: [view visibleRect] toView: nil];
2032 if (NSMouseInRect(pt, rect, [view isFlipped])) {
2033 if ([view respondsToSelector: @selector(doMouseMoved:)]) {
2034 if ([view isKindOfClass:[GraphicClientView class]]) {
2035 NSPoint pt2 = [view convertPoint: pt fromView: nil];
2036 pt2.x = [self quantizedPixelFromPixel: pt2.x];
2037 [self setInfoText:[(id)view infoTextForMousePoint:pt2 dragging:NO option:NULL]];
2038 [self updateCursorInfoForView:view atPosition:pt2];
2039 [self mouseEvent:theEvent receivedByClientView:(id)view];
2040 }
2041 [(id)view doMouseMoved: theEvent];
2042 } else [[NSCursor arrowCursor] set];
2043 return YES;
2044 } else return NO;
2045 }
2046
2047 - (void)mouseMoved: (NSEvent *)theEvent
2048 {
2049 int i;
2050 NSWindow *window = [self window];
2051 // NSLog(@"GraphicWindowController.mouseMoved");
2052 if ([NSWindow windowNumberAtPoint:[window convertBaseToScreen:[theEvent locationInWindow]] belowWindowWithWindowNumber:0] != [[self window] windowNumber]) {
2053 [[NSCursor arrowCursor] set];
2054 return;
2055 }
2056
2057 [self setInfoText:@""];
2058 for (i = 0; i < myClientViewsCount; i++) {
2059 if ([self mouseMoved:theEvent inView:records[i].client]) {
2060 [self mouseEvent:theEvent receivedByClientView:records[i].client];
2061 break;
2062 }
2063 if ([self mouseMoved:theEvent inView:records[i].splitter] || [self mouseMoved:theEvent inView:records[i].ruler])
2064 break;
2065 }
2066 if (i == myClientViewsCount) {
2067 [[NSCursor arrowCursor] set];
2068 [self setCursorInfoText:nil];
2069 }
2070 }
2071
2072 - (void)mouseEntered: (NSEvent *)theEvent
2073 {
2074 // NSLog(@"mouseEntered");
2075 }
2076
2077 - (void)mouseExited: (NSEvent *)theEvent
2078 {
2079 // NSLog(@"mouseExited");
2080 [[NSCursor arrowCursor] set];
2081 if (records[1].client != nil) {
2082 [(PianoRollView *)records[1].client mouseExited:theEvent];
2083 }
2084 }
2085
2086 - (IBAction)editingRangeTextModified: (id)sender
2087 {
2088 BOOL startFlag;
2089 int32_t bar, beat, subtick;
2090 MDTickType tick, duration, endtick;
2091 const char *s;
2092 if ([sender tag] == kEditingRangeStartTextTag)
2093 startFlag = YES;
2094 else startFlag = NO;
2095 s = [[sender stringValue] UTF8String];
2096 if (s[0] == 0) {
2097 /* Empty string: clear editing range */
2098 tick = endtick = -1;
2099 } else {
2100 MDTickType tick1, tick2;
2101 if (MDEventParseTickString(s, &bar, &beat, &subtick) < 3)
2102 return;
2103 tick = MDCalibratorMeasureToTick(calib, bar, beat, subtick);
2104 duration = [[[self document] myMIDISequence] sequenceDuration];
2105 if (tick < 0)
2106 tick = 0;
2107 // if (tick > duration)
2108 // tick = duration;
2109 [[self document] getEditingRangeStart: &tick1 end: &tick2];
2110 if (startFlag) {
2111 if (tick >= tick2)
2112 endtick = tick;
2113 else endtick = tick2;
2114 } else {
2115 if (tick1 >= 0 && tick1 <= tick) {
2116 endtick = tick;
2117 tick = tick1;
2118 } else {
2119 endtick = tick;
2120 }
2121 }
2122 }
2123 [[self document] unselectAllEventsInAllTracks: self];
2124 [[self document] setEditingRangeStart: tick end: endtick];
2125 }
2126
2127 #pragma mark ==== Track list ====
2128
2129 - (int)numberOfRowsInTableView:(NSTableView *)aTableView
2130 {
2131 return [[[self document] myMIDISequence] trackCount];
2132 // return [self visibleTrackCount];
2133 }
2134
2135 - (id)tableView:(NSTableView *)aTableView
2136 objectValueForTableColumn:(NSTableColumn *)aTableColumn
2137 row:(int)rowIndex
2138 {
2139 id identifier = [aTableColumn identifier];
2140 int idnum = sTableColumnIDToInt(identifier);
2141 // int num = [self trackNumberAtIndex: rowIndex];
2142 switch (idnum) {
2143 case kTrackNumberID:
2144 if (rowIndex == 0)
2145 return @"C";
2146 else return [NSString localizedStringWithFormat:@"%d", rowIndex];
2147 case kTrackNameID:
2148 return [[[self document] myMIDISequence] trackName: rowIndex];
2149 case kChannelID: {
2150 int ch;
2151 if (rowIndex == 0)
2152 return nil;
2153 ch = [[[self document] myMIDISequence] trackChannel: rowIndex];
2154 if (ch >= 0)
2155 return [NSNumber numberWithInt: ch + 1];
2156 else return nil;
2157 }
2158 case kDeviceNameID:
2159 if (rowIndex == 0)
2160 return nil;
2161 else
2162 return [[[self document] myMIDISequence] deviceName: rowIndex];
2163 case kEditableID:
2164 case kVisibleID:
2165 case kSoloID:
2166 case kMuteID: {
2167 MDTrackAttribute attr;
2168 attr = [[[self document] myMIDISequence] trackAttributeAtIndex: rowIndex];
2169 if (idnum == kEditableID) {
2170 return (attr & kMDTrackAttributeEditable ? sPencilSmallImage : nil);
2171 } else if (idnum == kSoloID) {
2172 // return (attr & kMDTrackAttributeMute ? nil : sSpeakerImage);
2173 return (attr & kMDTrackAttributeSolo ? sSoloImage : sSoloNonImage);
2174 } else if (idnum == kMuteID) {
2175 // return (attr & kMDTrackAttributeMute ? nil : sSpeakerImage);
2176 if (attr & kMDTrackAttributeMute)
2177 return nil;
2178 else if (attr & kMDTrackAttributeMuteBySolo)
2179 return sSpeakerGrayImage;
2180 else return sSpeakerImage;
2181 } else if (idnum == kVisibleID) {
2182 return (attr & kMDTrackAttributeHidden ? sEyeCloseImage : sEyeOpenImage);
2183 } else return nil;
2184 }
2185 }
2186
2187 return nil;
2188 }
2189
2190 - (void)tableView:(NSTableView *)aTableView
2191 setObjectValue:(id)anObject
2192 forTableColumn:(NSTableColumn *)aTableColumn
2193 row:(int)rowIndex
2194 {
2195 int idnum = sTableColumnIDToInt([aTableColumn identifier]);
2196 MyDocument *doc = (MyDocument *)[self document];
2197 switch (idnum) {
2198 case kTrackNameID:
2199 [doc changeTrackName: anObject forTrack: rowIndex];
2200 break;
2201 case kChannelID: {
2202 int ch = [anObject intValue];
2203 if (ch >= 1 && ch <= 16)
2204 [doc changeTrackChannel: ch - 1 forTrack: rowIndex];
2205 break;
2206 }
2207 case kDeviceNameID:
2208 if ([anObject isKindOfClass:[NSAttributedString class]])
2209 anObject = [anObject string];
2210 [doc changeDevice: anObject forTrack: rowIndex];
2211 break;
2212 }
2213 }
2214
2215 - (void)tableView:(NSTableView *)aTableView willDisplayCell:(id)aCell forTableColumn:(NSTableColumn *)aTableColumn row:(int)rowIndex
2216 {
2217 MDTrackAttribute attr;
2218 int idnum = sTableColumnIDToInt([aTableColumn identifier]);
2219 if (idnum == kEditableID) {
2220 [aCell setRepresentedObject: [self colorForTrack: rowIndex enabled: YES]];
2221 attr = [[[self document] myMIDISequence] trackAttributeAtIndex: rowIndex];
2222 [aCell setFillsColor:((attr & kMDTrackAttributeHidden) == 0 || (attr & kMDTrackAttributeEditable) != 0)];
2223 } else if (idnum == kSoloID) {
2224 attr = [[[self document] myMIDISequence] trackAttributeAtIndex: rowIndex];
2225 [aCell setFillsColor: (attr & kMDTrackAttributeSolo) != 0];
2226 } else if (idnum == kChannelID || idnum == kDeviceNameID) {
2227 if (rowIndex == 0)
2228 [aCell setEnabled: NO];
2229 else
2230 [aCell setEnabled: YES];
2231 }
2232
2233 /* static NSImage *sEyeImage = nil;
2234 if (sEyeImage == nil)
2235 sEyeImage = [[NSImage imageNamed: @"eyes.png"] retain];
2236 // [aCell setImage: ([aCell intValue] ? sEyeImage : nil)];
2237 [aCell setImage: (rowIndex % 2 == 0 ? sEyeImage : nil)];
2238 } else if (idnum == kEditableID) {
2239 static NSImage *sPencilImage = nil;
2240 if (sPencilImage == nil)
2241 sPencilImage = [[NSImage imageNamed: @"pencil_small.png"] retain];
2242 // [aCell setImage: ([aCell intValue] ? sPencilImage : nil)];
2243 [aCell setImage: (rowIndex % 2 == 0 ? sPencilImage : nil)];
2244 } */
2245 }
2246
2247 /*
2248 - (IBAction)trackMenuItemSelected: (id)sender
2249 {
2250 int tag = [sender tag];
2251 if (tag >= 0 && tag < [[[self document] myMIDISequence] trackCount])
2252 [self addTrack: tag];
2253 [myTableView reloadData];
2254 [self reloadClientViews];
2255 }
2256
2257 - (IBAction)plusTrackButton: (id)sender
2258 {
2259 [NSMenu popUpContextMenu: [self trackMenu] withEvent: [[self window] currentEvent] forView: sender];
2260 }
2261
2262 - (IBAction)minusTrackButton: (id)sender
2263 {
2264 NSMutableArray *array = [NSMutableArray array];
2265 NSEnumerator *en = [myTableView selectedRowEnumerator];
2266 id object;
2267 while ((object = [en nextObject]) != nil) {
2268 [array addObject: [NSNumber numberWithInt: [self trackNumberAtIndex: [object intValue]]]];
2269 }
2270 // NSLog(@"array = %@", array);
2271 [self removeTracksInArray: array];
2272 [myTableView deselectAll: self];
2273 [myTableView reloadData];
2274 [self reloadClientViews];
2275 }
2276 */
2277
2278 /*
2279 - (IBAction)tableClicked: (id)sender
2280 {
2281 id column = [[sender tableColumns] objectAtIndex: [sender clickedColumn]];
2282 NSLog(@"tableClicked invoked");
2283 if ([@"color" isEqualToString: [column identifier]]) {
2284 int row = [sender clickedRow];
2285 // int32_t trackNum = [self trackNumberAtIndex: row];
2286 // BOOL shiftFlag = (([[NSApp currentEvent] modifierFlags] & NSShiftKeyMask) != 0);
2287 // if (![self isFocusTrack: trackNum]) {
2288 // [self setFocusFlag: YES onTrack: trackNum extending: shiftFlag];
2289 // } else if (shiftFlag) {
2290 // [self setFocusFlag: NO onTrack: trackNum extending: YES];
2291 // }
2292 // [self setFocusFlag: ![self isFocusTrack: trackNum] onTrack: trackNum extending: YES];
2293 [myTableView reloadData];
2294 [self reloadClientViews];
2295 }
2296 }
2297 */
2298
2299 - (void)trackTableAction:(id)sender
2300 {
2301 int column, row;
2302 NSTableColumn *tableColumn;
2303 // NSCell *cell;
2304 MyDocument *doc = (MyDocument *)[self document];
2305 MyMIDISequence *seq = [doc myMIDISequence];
2306 MDTrackAttribute attr;
2307 int idnum;
2308 NSRect frame;
2309 BOOL editableTrackWasHidden = NO;
2310 BOOL shiftFlag = (([[NSApp currentEvent] modifierFlags] & NSShiftKeyMask) != 0);
2311
2312 row = (int)[myTableView clickedRow];
2313 column = (int)[myTableView clickedColumn];
2314
2315 if (column < 0)
2316 return;
2317 tableColumn = [[myTableView tableColumns] objectAtIndex: column];
2318 idnum = sTableColumnIDToInt([tableColumn identifier]);
2319
2320 // Check whether table header is clicked
2321 // (lastMouseDownLocation is implemented in MyWindow)
2322 if (row < 0) {
2323 int attrMask, countChangedTrack;
2324 frame = [[myTableView headerView] headerRectOfColumn: column];
2325 if (!NSPointInRect([(MyWindow *)[self window] lastMouseDownLocation], [[myTableView headerView] convertRect: frame toView: nil]))
2326 return;
2327 switch (idnum) {
2328 // Set the corresponding flag in all selected rows
2329 // However, if the flag is already set for all rows, then unset the flag.
2330 case kEditableID:
2331 attrMask = kMDTrackAttributeEditable;
2332 break;
2333 case kSoloID:
2334 attrMask = kMDTrackAttributeSolo;
2335 break;
2336 case kMuteID:
2337 attrMask = kMDTrackAttributeMute;
2338 break;
2339 case kVisibleID:
2340 attrMask = kMDTrackAttributeHidden;
2341 break;
2342 default:
2343 return;
2344 }
2345 countChangedTrack = 0;
2346 // seq = [[self document] myMIDISequence];
2347 for (row = [seq trackCount] - 1; row >= 0; row--) {
2348 if (![myTableView isRowSelected: row])
2349 continue;
2350 attr = [doc trackAttributeForTrack: row];
2351 //if (idnum == kEditableID && (attr & kMDTrackAttributeHidden))
2352 // continue;
2353 if ((attr & attrMask) == 0) {
2354 attr |= attrMask;
2355 [doc setTrackAttribute: attr forTrack: row];
2356 countChangedTrack++;
2357 }
2358 }
2359 if (countChangedTrack == 0) {
2360 // All flags are already set, so unset all of them
2361 for (row = [seq trackCount] - 1; row >= 0; row--) {
2362 if (![myTableView isRowSelected: row])
2363 continue;
2364 attr = [doc trackAttributeForTrack: row];
2365 //if (idnum == kEditableID && (attr & kMDTrackAttributeHidden))
2366 // continue;
2367 attr &= ~attrMask;
2368 if (idnum == kVisibleID) {
2369 // Hidden tracks are non-editable
2370 if (attr & kMDTrackAttributeEditable) {
2371 attr &= ~kMDTrackAttributeEditable;
2372 editableTrackWasHidden = YES;
2373 }
2374 }
2375 [doc setTrackAttribute: attr forTrack: row];
2376 }
2377 }
2378 if (attrMask == kMDTrackAttributeSolo || attrMask == kMDTrackAttributeMute) {
2379 /* Update 'mute by solo' flags */
2380 MDSequenceUpdateMuteBySoloFlag([seq mySequence]);
2381 }
2382 } else {
2383 // Check whether mouseUp occurred within the same cell as mouseDown
2384 frame = [myTableView frameOfCellAtColumn: column row: row];
2385 if (!NSPointInRect([(MyWindow *)[self window] lastMouseDownLocation], [myTableView convertRect: frame toView: nil]))
2386 return;
2387 switch (idnum) {
2388 case kEditableID: {
2389 attr = [seq trackAttributeAtIndex: row];
2390 // if (attr & kMDTrackAttributeHidden)
2391 // break;
2392 if (![self isFocusTrack: row]) {
2393 [self setFocusFlag: YES onTrack: row extending: shiftFlag];
2394 } else if (shiftFlag) {
2395 [self setFocusFlag: NO onTrack: row extending: YES];
2396 }
2397 break;
2398 }
2399 case kMuteID:
2400 [doc setMuteFlagOnTrack: row flag: -1];
2401 break;
2402 case kSoloID:
2403 [doc setSoloFlagOnTrack: row flag: -1];
2404 break;
2405 case kVisibleID:
2406 attr = [doc trackAttributeForTrack: row];
2407 if (attr & kMDTrackAttributeHidden) {
2408 attr &= ~kMDTrackAttributeHidden;
2409 } else {
2410 attr |= kMDTrackAttributeHidden;
2411 #if 0
2412 if (attr & kMDTrackAttributeEditable) {
2413 // Hidden tracks are non-editable
2414 attr &= ~kMDTrackAttributeEditable;
2415 editableTrackWasHidden = YES;
2416 }
2417 #endif
2418 }
2419 [doc setTrackAttribute: attr forTrack: row];
2420 break;
2421 default:
2422 return;
2423 }
2424 }
2425
2426 #if 0
2427 if (idnum == kVisibleID) {
2428 // Check whether any editable track is left
2429 int firstVisibleRow = -1;
2430 for (row = [seq trackCount] - 1; row >= 0; row--) {
2431 attr = [seq trackAttributeAtIndex: row];
2432 if (attr & kMDTrackAttributeEditable) {
2433 firstVisibleRow = -2;
2434 break;
2435 }
2436 if (row > 0 && !(attr & kMDTrackAttributeHidden))
2437 firstVisibleRow = row;
2438 }
2439 if (firstVisibleRow > 0) {
2440 // Make this row editable
2441 attr = [seq trackAttributeAtIndex: firstVisibleRow];
2442 attr |= kMDTrackAttributeEditable;
2443 [seq setTrackAttribute: attr atIndex: firstVisibleRow];
2444 }
2445 }
2446 #endif
2447
2448 if (idnum == kEditableID || idnum == kVisibleID)
2449 visibleTrackCount = -1; // Invalidate the track list cache
2450
2451 [myTableView reloadData];
2452 [self setNeedsReloadClientViews];
2453 }
2454
2455 - (void)trackTableDoubleAction:(id)sender
2456 {
2457 int column, row, idnum;
2458 NSTableColumn *tableColumn;
2459
2460 row = (int)[myTableView clickedRow];
2461 column = (int)[myTableView clickedColumn];
2462
2463 if (column < 0)
2464 return;
2465 tableColumn = [[myTableView tableColumns] objectAtIndex: column];
2466 idnum = sTableColumnIDToInt([tableColumn identifier]);
2467 if (idnum == kTrackNumberID) {
2468 [self openEventListWindow: sender];
2469 return;
2470 }
2471 }
2472
2473 //- (float)timebase
2474 //{
2475 // return [[self document] timebase];
2476 //}
2477
2478 - (void)tableViewSelectionDidChange: (NSNotification *)aNotification
2479 {
2480 /* if ([aNotification object] == myTableView) {
2481 NSButton *minusButton = (NSButton *)[[[self window] contentView] viewWithTag: kMinusButtonTag];
2482 if ([myTableView numberOfSelectedRows] > 0)
2483 [minusButton setEnabled: YES];
2484 else [minusButton setEnabled: NO];
2485 } */
2486 /* Rebuild sortedTrackNumbers later */
2487 visibleTrackCount = -1;
2488 [lastSelectedTracks release];
2489 lastSelectedTracks = [[NSIndexSet alloc] initWithIndexSet:[myTableView selectedRowIndexes]];
2490 [self setNeedsReloadClientViews];
2491 }
2492
2493 - (BOOL)selectionShouldChangeInTableView:(NSTableView *)tableView
2494 {
2495 int column;
2496 column = (int)[myTableView clickedColumn];
2497 if (column >= 0) {
2498 NSTableColumn *tableColumn;
2499 int idnum;
2500 tableColumn = [[myTableView tableColumns] objectAtIndex: column];
2501 idnum = sTableColumnIDToInt([tableColumn identifier]);
2502 if (idnum == kEditableID || idnum == kVisibleID || idnum == kSoloID || idnum == kMuteID)
2503 return NO; // we don't change selection on clicking these columns
2504 }
2505 return YES;
2506 }
2507
2508 - (BOOL)tableView:(NSTableView *)aTableView shouldTrackCell:(NSCell *)cell forTableColumn:(NSTableColumn *)aTableColumn row:(int)rowIndex
2509 {
2510 int idnum = sTableColumnIDToInt([aTableColumn identifier]);
2511 if (idnum == kDeviceNameID) {
2512 /* Rebuild the ComboBox item lists */
2513 int i, n1, n2;
2514 NSArray *array = [(MyDocument *)[self document] getDestinationNames];
2515 // id cell = [aTableColumn dataCell];
2516 [(id)cell removeAllItems];
2517 n1 = MDPlayerGetNumberOfDestinations();
2518 n2 = (int)[array count];
2519 for (i = 0; i < n2; i++) {
2520 id obj = [array objectAtIndex:i];
2521 if (i > n1)
2522 obj = [[[NSAttributedString alloc] initWithString:obj attributes:
2523 [NSDictionary dictionaryWithObjectsAndKeys:
2524 [NSColor redColor], NSForegroundColorAttributeName, nil]] autorelease];
2525 [(id)cell addItemWithObjectValue:obj];
2526 }
2527 }
2528 return YES;
2529 }
2530
2531 - (IBAction)openEventListWindow: (id)sender
2532 {
2533 int index, n;
2534 NSMutableArray *array = [NSMutableArray array];
2535 n = [[[self document] myMIDISequence] trackCount];
2536 for (index = 0; index < n; index++) {
2537 if ([myTableView isRowSelected: index])
2538 [array addObject: [NSNumber numberWithInt: index]];
2539 }
2540 [(MyDocument *)[self document] createWindowForTracks: array ofType: gListWindowType];
2541 // [self openSelectedTracks: sender withClass: [ListWindowController class]];
2542 }
2543
2544 - (IBAction)createNewTrack: (id)sender
2545 {
2546 int index = (int)[myTableView selectedRow];
2547 if (index < 0)
2548 index = [[[self document] myMIDISequence] trackCount];
2549 [(MyDocument *)[self document] insertTrack: nil atIndex: index];
2550 }
2551
2552 - (IBAction)deleteSelectedTracks:(id)sender
2553 {
2554 int index;
2555 for (index = [[[self document] myMIDISequence] trackCount] - 1; index > 0; index--) {
2556 if ([myTableView isRowSelected: index])
2557 [(MyDocument *)[self document] deleteTrackAt: index];
2558 }
2559 /* NSArray *array = [self selectedTracks];
2560 NSEnumerator *en;
2561 id object;
2562 en = [array reverseObjectEnumerator];
2563 while ((object = [en nextObject]) != nil) {
2564 int32_t n = [object intValue];
2565 [(MyDocument *)[self document] deleteTrackAt: n];
2566 } */
2567 }
2568
2569 - (IBAction)remapDevice: (id)sender
2570 {
2571 RemapDevicePanelController *controller;
2572 NSMutableArray *selection;
2573 NSIndexSet *iset;
2574 NSUInteger idx;
2575 if ([myTableView numberOfSelectedRows] == 0)
2576 selection = nil;
2577 else {
2578 iset = [myTableView selectedRowIndexes];
2579 selection = [NSMutableArray array];
2580 for (idx = [iset firstIndex]; idx != NSNotFound; idx = [iset indexGreaterThanIndex:idx]) {
2581 [selection addObject:[NSNumber numberWithInteger:(int)idx]];
2582 }
2583 }
2584 controller = [[RemapDevicePanelController allocWithZone: [self zone]] initWithDocument: [self document] trackSelection: selection];
2585 [controller beginSheetForWindow: [self window] invokeStopModalWhenDone: NO];
2586 }
2587
2588 - (IBAction)changeTrackColor: (id)sender
2589 {
2590 NSUInteger idx;
2591 NSColorPanel *cpanel;
2592 if ([myTableView numberOfSelectedRows] != 1)
2593 return;
2594 idx = [[myTableView selectedRowIndexes] firstIndex];
2595 // Open NSColorPanel
2596 cpanel = [NSColorPanel sharedColorPanel];
2597 [cpanel setColor:[[self document] colorForTrack:(int)idx enabled:YES]];
2598 [cpanel setContinuous:YES];
2599 [cpanel setTarget:[NSApp delegate]];
2600 [cpanel setAction:@selector(tryTrackColorForCurrentDocument:)];
2601 [cpanel makeKeyAndOrderFront:self];
2602 changingTrackColor = NO;
2603 }
2604
2605 - (IBAction)tryTrackColor: (id)sender
2606 {
2607 NSUInteger idx;
2608 NSColor *color = [sender color];
2609 if ([myTableView numberOfSelectedRows] == 0)
2610 return;
2611 idx = [[myTableView selectedRowIndexes] firstIndex];
2612 [[self document] changeColor:color forTrack:(int)idx enableUndo:!changingTrackColor];
2613 changingTrackColor = YES;
2614 [myTableView reloadData];
2615 [self setNeedsReloadClientViews];
2616 // Clear changingTrackColor flag when idle
2617 [[NSNotificationQueue defaultQueue] enqueueNotification: [NSNotification notificationWithName: sClearChangingColorFlagNotification object: self] postingStyle: NSPostWhenIdle coalesceMask: NSNotificationCoalescingOnName | NSNotificationCoalescingOnSender forModes: nil];
2618 }
2619
2620 - (void)clearChangingColorFlag:(NSNotification *)aNotification
2621 {
2622 changingTrackColor = NO;
2623 }
2624
2625 #pragma mark ====== Split view control ======
2626
2627 - (CGFloat)splitView: (NSSplitView *)sender constrainMaxCoordinate: (CGFloat)proposedMax ofSubviewAt: (NSInteger)offset
2628 {
2629 CGFloat max = [sender bounds].size.width - 160.0f;
2630 return max;
2631 }
2632
2633 - (CGFloat)splitView: (NSSplitView *)sender constrainMinCoordinate: (CGFloat)proposedMin ofSubviewAt: (NSInteger)offset
2634 {
2635 CGFloat min = 100.0f;
2636 return min;
2637 }
2638
2639 - (BOOL)splitView:(NSSplitView *)sender canCollapseSubview:(NSView *)subview
2640 {
2641 return YES;
2642 }
2643
2644 #pragma mark ==== Responding to notification from other windows ====
2645
2646 - (void)trackModified: (NSNotification *)notification
2647 {
2648 int i, track;
2649 track = [[[notification userInfo] objectForKey: @"track"] intValue];
2650 for (i = 2; i < myClientViewsCount; i++) {
2651 /* If focus track is set in the strip chart view, update it */
2652 GraphicClientView *client = records[i].client;
2653 [client doTrackModified:track];
2654 }
2655 visibleTrackCount = -1;
2656 [myTableView reloadData];
2657 [myPlayerView setNeedsDisplay: YES];
2658 [self setNeedsReloadClientViews];
2659 }
2660
2661 - (void)trackInserted: (NSNotification *)notification
2662 {
2663 int i, track;
2664 track = [[[notification userInfo] objectForKey: @"track"] intValue];
2665 for (i = 2; i < myClientViewsCount; i++) {
2666 /* If focus track is set in the strip chart view, update it */
2667 GraphicClientView *client = records[i].client;
2668 if ([client respondsToSelector:@selector(focusTrack)]) {
2669 int ftrack = [(id)client focusTrack];
2670 [records[i].splitter rebuildTrackPopup];
2671 if (ftrack >= 1 && ftrack >= track) {
2672 [(id)client setFocusTrack:ftrack + 1];
2673 [records[i].splitter setTrack:ftrack + 1];
2674 }
2675 }
2676 }
2677 visibleTrackCount = -1;
2678 [myTableView reloadData];
2679 [myPlayerView setNeedsDisplay: YES];
2680 [self setNeedsReloadClientViews];
2681 }
2682
2683 - (void)trackDeleted: (NSNotification *)notification
2684 {
2685 int i, track;
2686 track = [[[notification userInfo] objectForKey: @"track"] intValue];
2687 for (i = 2; i < myClientViewsCount; i++) {
2688 /* If focus track is set in the strip chart view, update it */
2689 GraphicClientView *client = records[i].client;
2690 if ([client respondsToSelector:@selector(focusTrack)]) {
2691 int ftrack = [(id)client focusTrack];
2692 [records[i].splitter rebuildTrackPopup];
2693 if (ftrack >= 1 && ftrack >= track) {
2694 if (ftrack >= [self trackCount])
2695 ftrack--;
2696 [(id)client setFocusTrack:ftrack];
2697 [records[i].splitter setTrack:ftrack];
2698 }
2699 }
2700 }
2701 visibleTrackCount = -1;
2702 [myTableView reloadData];
2703 [myPlayerView setNeedsDisplay: YES];
2704 [self setNeedsReloadClientViews];
2705 }
2706
2707 #pragma mark ==== Pasteboard support ====
2708
2709 - (int)countTracksToCopyWithSelectionList: (MDSelectionObject **)selArray rangeStart: (MDTickType *)outStartTick rangeEnd: (MDTickType *)outEndTick
2710 {
2711 int i, numberOfSelectedTracks;
2712 MDSelectionObject *sel;
2713 MyDocument *doc = (MyDocument *)[self document];
2714 int numberOfTracks = [[doc myMIDISequence] trackCount];
2715 id firstResponder = [[self window] firstResponder];
2716
2717 if ([firstResponder isKindOfClass:[GraphicBackgroundView class]]) {
2718 int focusTrack;
2719 GraphicClientView *client = nil;
2720 /* Look for the client view contained in the firstResponder container view */
2721 for (i = 0; i < myClientViewsCount; i++) {
2722 if (records[i].container == firstResponder) {
2723 client = records[i].client;
2724 break;
2725 }
2726 }
2727 focusTrack = [client focusTrack];
2728 /* Copy all selected events in editable tracks */
2729 numberOfSelectedTracks = 0;
2730 for (i = 0; i < numberOfTracks; i++) {
2731 MDTrackAttribute attr = [[[self document] myMIDISequence] trackAttributeAtIndex: i];
2732 if (focusTrack >= 0) {
2733 if (focusTrack != i)
2734 continue;
2735 } else {
2736 if ((attr & kMDTrackAttributeEditable) == 0)
2737 continue;
2738 }
2739 sel = [doc selectionOfTrack: i];
2740 if ((sel->pointSet != NULL && IntGroupGetCount(sel->pointSet) > 0) ||
2741 (sel->startTick >= 0 && sel->endTick > sel->startTick)) {
2742 /* Handle only non-empty selections */
2743 if (selArray != NULL)
2744 selArray[i] = sel;
2745 if (sel != nil)
2746 numberOfSelectedTracks++;
2747 }
2748 }
2749 if (outStartTick != NULL && outEndTick != NULL)
2750 [doc getEditingRangeStart: outStartTick end: outEndTick];
2751
2752 return numberOfSelectedTracks;
2753
2754 } else if (firstResponder == myTableView) {
2755
2756 /* Copy all events in selected tracks */
2757 numberOfSelectedTracks = 0;
2758 for (i = 0; i < numberOfTracks; i++) {
2759 if ([myTableView isRowSelected: i]) {
2760 if (selArray != NULL)
2761 selArray[i] = (MDSelectionObject *)(-1);
2762 numberOfSelectedTracks++;
2763 }
2764 }
2765 if (outStartTick != NULL)
2766 *outStartTick = kMDNegativeTick;
2767 if (outEndTick != NULL)
2768 *outEndTick = kMDMaxTick;
2769
2770 return numberOfSelectedTracks;
2771
2772 } else return 0;
2773
2774 }
2775
2776 - (void)doCopy: (BOOL)copyFlag andDelete: (BOOL)deleteFlag
2777 {
2778 MDSelectionObject *sel, **selArray;
2779 MDTickType startTick, endTick;
2780 int i, numberOfSelectedTracks;
2781 MyDocument *doc = (MyDocument *)[self document];
2782 int numberOfTracks = [[doc myMIDISequence] trackCount];
2783
2784 selArray = (MDSelectionObject **)calloc(sizeof(MDSelectionObject *), numberOfTracks);
2785 if (selArray == NULL)
2786 return;
2787
2788 numberOfSelectedTracks = [self countTracksToCopyWithSelectionList: selArray rangeStart: &startTick rangeEnd: &endTick];
2789 if (numberOfSelectedTracks == 0) {
2790 free(selArray);
2791 return;
2792 }
2793
2794 if (copyFlag)
2795 [doc copyWithSelections: selArray rangeStart: startTick rangeEnd: endTick];
2796
2797 if (deleteFlag) {
2798 id firstResponder = [[self window] firstResponder];
2799 for (i = numberOfTracks - 1; i >= 0; i--) {
2800 if (selArray[i] == nil)
2801 continue;
2802 if (firstResponder == myTableView) {
2803 [doc deleteTrackAt: i];
2804 } else {
2805 sel = [doc selectionOfTrack: i];
2806 [doc deleteMultipleEventsAt: sel fromTrack: i deletedEvents: NULL];
2807 }
2808 }
2809 }
2810
2811 free(selArray);
2812 }
2813
2814 /* Paste multiple tracks to multiple tracks
2815 Rules:
2816 (1) Events from the conductor track should go to the conductor track,
2817 and those from non-conductor tracks should go to non-conductor tracks.
2818 (2) If the number of tracks in the pasteboard is larger than the number of
2819 editable tracks, then new tracks are created after the last track.
2820 (3) If the first responder is strip chart view, then only the event type that
2821 is visible are pasted. The exception to this is when the TEMPO event is
2822 selected, in which all conductor events are pasted. */
2823 - (void)doPasteWithMergeFlag: (BOOL)mergeFlag
2824 {
2825 MyDocument *doc = (MyDocument *)[self document];
2826 MDSequence *seq;
2827 MDCatalog *catalog;
2828 PasteWarningPanelController *warningCont;
2829 int i, j, numberOfTracks, trackCount, hasConductorTrack, result;
2830 int *trackList;
2831 id firstResponder;
2832 int focusTrack;
2833
2834 if (![doc getPasteboardSequence: &seq catalog: &catalog])
2835 return;
2836 hasConductorTrack = (catalog->catTrack[0].originalTrackNo == 0);
2837 trackCount = [[doc myMIDISequence] trackCount];
2838 numberOfTracks = MDSequenceGetNumberOfTracks(seq);
2839 i = (numberOfTracks > trackCount ? numberOfTracks : trackCount);
2840 trackList = (int *)calloc(sizeof(int), i + 1);
2841 if (trackList == NULL) {
2842 MDSequenceRelease(seq);
2843 free(catalog);
2844 return;
2845 }
2846
2847 firstResponder = [[self window] firstResponder];
2848 focusTrack = -1; /* Only meaningful for the strip chart view */
2849
2850 if ([firstResponder isKindOfClass:[GraphicBackgroundView class]]) {
2851
2852 int kind, code;
2853 GraphicClientView *client = nil;
2854 /* Look for the client view contained in the firstResponder container view */
2855 for (i = 0; i < myClientViewsCount; i++) {
2856 if (records[i].container == firstResponder) {
2857 client = records[i].client;
2858 break;
2859 }
2860 }
2861 focusTrack = [client focusTrack];
2862 if ([client isKindOfClass:[StripChartView class]]) {
2863 /* Filter the events */
2864 IntGroup *ig;
2865 int32_t kindAndCode = [(StripChartView *)client kindAndCode];
2866 kind = (kindAndCode >> 16) & 65535;
2867 code = kindAndCode & 65535;
2868 if (kind == kMDEventTempo) {
2869 /* Only conductor events can be pasted */
2870 if (!hasConductorTrack)
2871 goto exit; /* Do nothing */
2872 while (MDSequenceGetNumberOfTracks(seq) > 1)
2873 MDSequenceDeleteTrack(seq, 1);
2874 numberOfTracks = 1;
2875 } else {
2876 /* Conductor track should be ignored */
2877 if (hasConductorTrack) {
2878 MDSequenceDeleteTrack(seq, 0);
2879 numberOfTracks--;
2880 if (numberOfTracks == 0)
2881 return; /* Do nothing */
2882 hasConductorTrack = 0;
2883 }
2884 /* Delete events that do not match the kind and code */
2885 ig = IntGroupNew();
2886 if (kind == kMDEventInternalNoteOff || kind == kMDEventInternalDuration)
2887 kind = kMDEventNote;
2888 for (i = 0; i < numberOfTracks; i++) {
2889 MDTrack *track = MDSequenceGetTrack(seq, i);
2890 MDPointer *pt = MDPointerNew(track);
2891 MDEvent *ep;
2892 while ((ep = MDPointerForward(pt)) != NULL) {
2893 if (MDGetKind(ep) == kind && (code == 65535 || MDGetCode(ep) == code))
2894 continue;
2895 IntGroupAdd(ig, MDPointerGetPosition(pt), 1);
2896 }
2897 if (IntGroupGetCount(ig) > 0)
2898 MDTrackUnmerge(track, NULL, ig);
2899 IntGroupClear(ig);
2900 MDPointerRelease(pt);
2901 }
2902 IntGroupRelease(ig);
2903 }
2904 }
2905
2906 } else if (firstResponder != myTableView) {
2907 goto exit;
2908 }
2909
2910 /* Build track list from the "editing" tracks
2911 or the "focused" track */
2912 for (i = j = 0; i < trackCount; i++) {
2913 MDTrackAttribute attr = [doc trackAttributeForTrack: i];
2914 if (focusTrack >= 0) {
2915 if (i != focusTrack)
2916 continue;
2917 } else {
2918 if ((attr & kMDTrackAttributeEditable) == 0)
2919 continue;
2920 }
2921 if (j == 0) {
2922 /* The first editable track: we need to check for the conductor track */
2923 if (!hasConductorTrack && i == 0)
2924 continue; /* We should not target the conductor track */
2925 if (hasConductorTrack && i != 0) {
2926 /* We should target the conductor track */
2927 warningCont = [PasteWarningPanelController createPasteWarningPanelControllerOfType:kPasteWarningTypeConductorShouldBeEditable];
2928 result = [warningCont runSheetModalWithWindow:[self window]];
2929 if (result == 0)
2930 goto exit; /* Canceled */
2931 trackList[j++] = 0;
2932 }
2933 }
2934 trackList[j++] = i;
2935 }
2936 if (j > numberOfTracks) {
2937 /* Too many editable tracks */
2938 warningCont = [PasteWarningPanelController createPasteWarningPanelControllerOfType:kPasteWarningTypeTooManyTargets];
2939 [warningCont setMainMessageWithInteger:numberOfTracks and:j];
2940 result = [warningCont runSheetModalWithWindow:[self window]];
2941 if (result == 0)
2942 goto exit; /* Canceled */
2943 else if (result == 1) {
2944 /* Paste only once */
2945 trackList[numberOfTracks] = -1;
2946 } else {
2947 /* Paste repeatedly */
2948 trackList[j] = -1;
2949 }
2950 } else if (j < numberOfTracks) {
2951 /* Too few editable tracks */
2952 warningCont = [PasteWarningPanelController createPasteWarningPanelControllerOfType:kPasteWarningTypeTooFewTargets];
2953 [warningCont setMainMessageWithInteger:numberOfTracks and:j];
2954 result = [warningCont runSheetModalWithWindow:[self window]];
2955 if (result == 0)
2956 goto exit; /* Canceled */
2957 else if (result == 1) {
2958 /* Ignore extra tracks */
2959 trackList[