1 // Copyright (c) 2009 Yanagi Asakura
3 // This software is provided 'as-is', without any express or implied
4 // warranty. In no event will the authors be held liable for any damages
5 // arising from the use of this software.
7 // Permission is granted to anyone to use this software for any purpose,
8 // including commercial applications, and to alter it and redistribute it
9 // freely, subject to the following restrictions:
11 // 1. The origin of this software must not be misrepresented; you must not
12 // claim that you wrote the original software. If you use this software
13 // in a product, an acknowledgment in the product documentation would be
14 // appreciated but is not required.
16 // 2. Altered source versions must be plainly marked as such, and must not be
17 // misrepresented as being the original software.
19 // 3. This notice may not be removed or altered from any source
26 // Created by 柳 on 09/09/12.
27 // Copyright 2009 __MyCompanyName__. All rights reserved.
30 #import "ElisController.h"
33 static float convertQTTimeToSecond(QTTime t)
35 return (float)t.timeValue/t.timeScale;
38 @implementation ElisController
42 layers = [[NSMutableArray alloc] init];
43 _animationLayerFactory = [[ElisAnimationLayerFactory alloc] init];
49 ProjectMovieSize = CGRectMake(0, 0, 640, 480);
52 [ElisCustomFilter class];
54 NSLog(@"Building effects ...");
56 [self buildEffectMenu];
58 // テキストフィールドでリッチテキスト編集を許可する。
59 // [_textLayerField setAllowsEditingTextAttributes:YES];
64 #ifdef __SNOW_LEOPARD_GCD__
65 diq = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);
69 - (CALayer*)createNewLayer:(NSString*)path
74 layer = [[ElisLayer alloc] init];
76 NSWorkspace* sharedWorkspace = [NSWorkspace sharedWorkspace];
79 if([sharedWorkspace type:[sharedWorkspace typeOfFile:path error:nil]
80 conformsToType:@"public.image"])
82 m = [[ElisMedia alloc] initWithImageFile:path];
83 alayer = [_animationLayerFactory createNewAnimationLayer:convertQTTimeToSecond([m duration])
84 name:[path lastPathComponent] type:@"image"];
86 else if([sharedWorkspace type:[sharedWorkspace typeOfFile:path error:nil]
87 conformsToType:@"public.audio"])
89 m = [[ElisMedia alloc] initWithSoundFile:path];
90 alayer = [_animationLayerFactory createNewAnimationLayer:convertQTTimeToSecond([m duration])
91 name:[path lastPathComponent] type:@"sound"];
93 else if([sharedWorkspace type:[sharedWorkspace typeOfFile:path error:nil]
94 conformsToType:@"public.movie"] ||
95 [sharedWorkspace type:[sharedWorkspace typeOfFile:path error:nil]
96 conformsToType:@"com.apple.quartz-composer-composition"])
98 m = [[ElisMedia alloc] initWithMovieFile:path];
99 alayer = [_animationLayerFactory createNewAnimationLayer:convertQTTimeToSecond([m duration])
100 name:[path lastPathComponent] type:@"movie"];
103 NSLog(@"error: cannot open %@", path);
108 // [alayer setValue:layer forKey:@"ElisLayer"];
110 [layer setAlayer:alayer];
111 [layers addObject:layer];
116 - (CALayer*)createNewTextLayer:(NSString*)t
118 // [_textLayerField selectAll:nil];
119 // NSAttributedString* as = [[NSAttributedString alloc]
120 // initWithRTF:[_textLayerField RTFFromRange:[_textLayerField selectedRange]] documentAttributes:nil];
121 NSAttributedString* as = [_textLayerField attributedString];
122 ElisLayer* layer = [[ElisLayer alloc] init];
123 ElisMedia* m = [[ElisMedia alloc] initWithText:as];
124 CALayer* cal = [_animationLayerFactory createNewAnimationLayer:convertQTTimeToSecond([m duration])
125 name:@"text" type:@"text"];
128 [layer setAlayer:cal];
129 [layers addObject:layer];
134 // 絶対時間qttimeと関係があるレイヤーをまとめて返す。
135 - (void)getFrameForTime:(QTTime)qttime result:(NSMutableArray*)layerSet
137 int size = [layers count];
140 if(convertQTTimeToSecond(qttime) >= hipTime){
141 [_mainView stopDisplayLink];
143 [_playstopButton setState:NSOffState];
147 globalCurrentTime = qttime;
148 [self moveSliderTo:qttime];
149 [_tableController reload];
151 // GCD使ってみたら画面がちらつく。どういうことなの...?
152 #ifdef __SNOW_LEOPARD_GCD__
153 dispatch_apply(size, diq, ^(size_t i) {
154 ElisLayer* l = [layers objectAtIndex:i];
155 if([l isInclude:qttime]){
156 if(playing) [l play];
157 [layerSet addObject:l];
159 if(playing) [l stop];
163 // GCDなりOpenMPなりで並列化すること。
166 for(i = 0; i < size; i++){
167 l = [layers objectAtIndex:i];
168 if([l isInclude:qttime]){
169 if(playing) [l play];
170 [layerSet addObject:l];
172 if(playing) [l stop];
178 - (void)play:(QTTime)time
180 NSMutableArray* interestLayers = [[NSMutableArray alloc] init];
181 int i, size = [layers count];
183 hipTime = [self getHipTime];
185 for(i = 0; i < size; i++)
186 if([[layers objectAtIndex:i] isInclude:time])
187 [interestLayers addObject:[layers objectAtIndex:i]];
189 size = [interestLayers count];
191 for(i = 0; i < size; i++)
192 [(ElisLayer*)[interestLayers objectAtIndex:i] play];
197 - (void)stop:(QTTime)time
199 // NSMutableArray* interestLayers = [[NSMutableArray alloc] init];
200 int i, size = [layers count];
202 // hipTime = [self getHipTime];
204 // for(i = 0; i < size; i++)
205 //// if([[layers objectAtIndex:i] isInclude:time])
206 // [interestLayers addObject:[layers objectAtIndex:i]];
208 // size = [interestLayers count];
210 // for(i = 0; i < size; i++)
211 // [(ElisLayer*)[interestLayers objectAtIndex:i] stop];
213 for(i = 0; i < size; i++)
214 [(ElisLayer*)[layers objectAtIndex:i] stop];
217 globalCurrentTime = time;
221 - (IBAction)startPlay:(id)sender
223 hipTime = [self getHipTime];
224 QTTime currentTime = QTMakeTime([timeSlider floatValue] * hipTime * DEFAULT_FPS, DEFAULT_FPS);
225 [self seek:currentTime];
226 [self play:currentTime];
227 [_mainView startDisplayLink];
230 - (IBAction)stopPlay:(id)sender
232 hipTime = [self getHipTime];
233 QTTime currentTime = QTMakeTime([timeSlider floatValue] * hipTime * DEFAULT_FPS, DEFAULT_FPS);
234 [self stop:currentTime];
235 [_mainView stopDisplayLink];
240 int i, size = [layers count];
241 float hipTimeSecond = 0.0f, candidate;
243 for(i = 0; i < size; i++){
244 candidate = convertQTTimeToSecond([[layers objectAtIndex:i] mapping].time)
245 + convertQTTimeToSecond([[layers objectAtIndex:i] mapping].duration);
246 if(candidate > hipTimeSecond)
247 hipTimeSecond = candidate;
250 return hipTimeSecond;
253 - (void)moveSliderTo:(QTTime)time
255 float now = convertQTTimeToSecond(time);
256 [timeSlider setFloatValue:now/hipTime];
257 [timeCodeField setStringValue:QTStringFromTime(QTMakeTime(now * DEFAULT_FPS, DEFAULT_FPS))];
258 [_timeLineController movePlaybackBar:now*timeLineScale];
261 - (IBAction)timeSliderChanged:(id)sender
263 float seconds = [sender floatValue];
264 QTTime currentTime = QTMakeTime(seconds * hipTime * DEFAULT_FPS, DEFAULT_FPS);
265 [timeCodeField setStringValue:QTStringFromTime(currentTime)];
266 _currentTime = currentTime;
267 globalCurrentTime = currentTime;
271 - (void)seek:(QTTime)time
273 int i, size = [layers count];
274 [_mainView seek:time];
276 for(i = 0; i < size; i++)
277 [[layers objectAtIndex:i] seek:time];
283 hipTime = [self getHipTime];
284 [_mainView getFrameForQTTime:_currentTime];
287 - (void)getSoundTrack:(NSMutableArray*)soundTrack
289 int i, size = [layers count];
290 for(i = 0; i < size; i++)
291 [[layers objectAtIndex:i] getSoundTrack:soundTrack];
294 - (IBAction)deleteSelectLayer:(id)sender
296 CALayer* selected = [_timeLineController getSelectLayer];
297 ElisLayer* layer = [selected valueForKey:@"ElisLayer"];
299 [layers removeObject:layer];
300 [selected removeFromSuperlayer];
301 [_timeLineController removeSelectLayer];
302 [_tableController createPropertyTable:nil];
303 [_tableController reload];
307 - (IBAction)recordingStateChanged:(id)sender
309 recording = !recording;
312 - (IBAction)removeAllKeyFrame:(id)sender
314 [_tableController removeAllKeyframe];
318 - (IBAction)removeEffect:(id)sender
320 [_tableController removeEffect];
324 - (IBAction)writeToFile:(id)sender
326 NSSavePanel* sp = [NSSavePanel savePanel];
328 [sp setRequiredFileType:@"mov"];
329 [sp beginSheetForDirectory:nil
331 modalForWindow:_mainWindow
333 didEndSelector:@selector(writeMovie:returnCode:contextInfo:)
337 - (void)writeMovie:(NSSavePanel*)sheet returnCode:(int)code contextInfo:(void*)info
339 NSString* path = [sheet filename];
341 if(code == NSCancelButton) return;
344 ElisWriterLegacy* writer;
345 writer = [[ElisWriterLegacy alloc] init];
346 [writer setMainWindow:_mainWindow];
347 [writer setMainController:self];
348 [writer setMainView:_mainView];
350 // [_writer write:sheet];
351 [NSThread detachNewThreadSelector:@selector(write:) toTarget:writer withObject:sheet];
355 - (void)buildEffectMenu
357 NSArray* filterNames;
360 filterNames = [CIFilter filterNamesInCategories:[NSArray arrayWithObjects:
361 kCICategoryDistortionEffect,
362 kCICategoryGeometryAdjustment,
363 kCICategoryCompositeOperation,
364 kCICategoryHalftoneEffect,
365 kCICategoryColorAdjustment,
366 kCICategoryColorEffect,
367 kCICategoryTransition,
368 kCICategoryTileEffect,
369 kCICategoryGenerator,
379 NSMenuItem* item = [[NSMenuItem alloc] init], *child;
380 NSMenu* menu = [[NSMenu alloc] init];
383 [item setTitle:@"Effect"];
384 [menu setTitle:@"Effect"];
386 filterNames = [CIFilter filterNamesInCategory:kCICategoryBuiltIn];
388 for(name in filterNames){
389 filter = [CIFilter filterWithName:name];
390 attrs = [filter attributes];
391 inputKeys = [filter inputKeys];
392 if([inputKeys count] == 1) goto jimp;
393 for(elm in inputKeys){
394 if([elm isEqualToString:@"inputImage"]) continue;
395 if([[[attrs valueForKey:elm] valueForKey:kCIAttributeClass] isEqualToString:@"CIVector"]){
396 if([[[attrs valueForKey:elm] valueForKey:kCIAttributeDefault] count] == 2) goto ok;
398 if(!([[[attrs valueForKey:elm] valueForKey:kCIAttributeClass] isEqualToString:@"NSNumber"] ||
399 [[[attrs valueForKey:elm] valueForKey:kCIAttributeClass] isEqualToString:@"CIColor"])){
404 child = [[[NSMenuItem alloc] init] autorelease];
405 [child setTitle:name];
406 [child setAction:@selector(effectMenuPushed:)];
407 [child setTarget:self];
410 [menu addItem:child];
413 ; // ラベルの後ろには必ずstatementがないといけないらしい。なんで?
415 NSLog(@"%d effetcs usable.", c);
416 [item setSubmenu:menu];
417 [menu setAutoenablesItems:NO];
418 [item setEnabled:YES];
420 [[NSApp mainMenu] insertItem:item atIndex:5];
421 [item setTarget:self];
424 - (void)effectMenuPushed:(id)sender
427 l = [_timeLineController getSelectLayer];
428 [[l valueForKey:@"ElisLayer"] addEffect:[sender title]];
430 [_tableController createPropertyTable:[l valueForKey:@"ElisLayer"]];
431 [_undoManager pushOperation:[l valueForKey:@"ElisLayer"]];
432 [_tableController reload];
435 - (void)saveProjectToFile:(NSSavePanel*)sheet returnCode:(int)code contextInfo:(void*)info
437 NSString* path = [sheet filename];
438 NSMutableData* data = [NSMutableData data];
439 NSKeyedArchiver* encoder = [[NSKeyedArchiver alloc] initForWritingWithMutableData:data];
441 [encoder encodeObject:layers forKey:@"layers"];
442 [encoder encodeFloat:ProjectMovieSize.size.width forKey:@"movieWidth"];
443 [encoder encodeFloat:ProjectMovieSize.size.height forKey:@"movieHeight"];
444 [encoder encodeObject:ELIS_VERSION forKey:@"version"];
445 [encoder finishEncoding];
449 [data writeToFile:path atomically:YES];
452 - (void)loadProjectFromFile:(NSString*)path
454 NSMutableData* data = [NSMutableData dataWithContentsOfFile:path];
455 NSKeyedUnarchiver* decoder = [[NSKeyedUnarchiver alloc] initForReadingWithData:data];
458 [_timeLineController awakeFromNib]; // これはひどい。
459 layers = [decoder decodeObjectForKey:@"layers"];
460 w = [decoder decodeFloatForKey:@"movieWidth"];
461 h = [decoder decodeFloatForKey:@"movieHeight"];
462 [decoder finishDecoding];
464 ProjectMovieSize.size.width = w;
465 ProjectMovieSize.size.height = h;
467 int i, size = [layers count];
471 for(i = 0; i < size; i++){
472 l = [layers objectAtIndex:i];
473 al = [_animationLayerFactory createNewAnimationLayer:[l duration] name:[l printName] type:[l getType]];
475 [_timeLineController addLayer:al];
483 - (IBAction)openProjectSaveDialog:(id)sender
485 NSSavePanel* sp = [NSSavePanel savePanel];
487 [sp setRequiredFileType:@"elis"];
488 [sp beginSheetForDirectory:nil
490 modalForWindow:_mainWindow
492 didEndSelector:@selector(saveProjectToFile:returnCode:contextInfo:)
496 - (IBAction)openProjectLoadDialog:(id)sender
498 NSOpenPanel* op = [NSOpenPanel openPanel];
501 st = [op runModalForTypes:[NSArray arrayWithObject:@"elis"]];
504 [self loadProjectFromFile:[op filename]];
507 - (BOOL)validateMenuItem:(NSMenuItem *)menuItem
509 if([menuItem action] == @selector(rewriteProject:))
510 return savePath != nil;
512 // if([menuItem action] == @selector(removeAllKeyFrame:))
513 // return [_tableController canRemoveAllKeyframe];
515 if([menuItem action] == @selector(removeEffect:))
516 return [_tableController canRemoveEffect];
518 if([menuItem action] == @selector(deleteSelectLayer:))
519 return [_timeLineController canDeleteLayer];
521 if([menuItem action] == @selector(undo:))
522 return [_undoManager canUndo];
524 if([menuItem action] == @selector(redo:))
525 return [_undoManager canRedo];
527 if([menuItem action] == @selector(changeMovieSpeed:))
528 return [self canChangeMovieSpeed];
530 if([menuItem action] == @selector(cutLayerAtCurrentTime:))
531 return [_timeLineController canDeleteLayer];
536 - (IBAction)rewriteProject:(id)sender
538 NSMutableData* data = [NSMutableData data];
539 NSKeyedArchiver* encoder = [[NSKeyedArchiver alloc] initForWritingWithMutableData:data];
541 [encoder encodeObject:layers forKey:@"layers"];
542 [encoder encodeFloat:ProjectMovieSize.size.width forKey:@"movieWidth"];
543 [encoder encodeFloat:ProjectMovieSize.size.height forKey:@"movieHeight"];
544 [encoder finishEncoding];
546 [data writeToFile:savePath atomically:YES];
549 - (IBAction)changeToSmallWindiw:(id)sender
552 [NSBundle loadNibNamed:@"MainMenuForSmallDisplay" owner:self];
555 - (IBAction)undo:(id)sender
557 ElisLayer* l = [_undoManager popOperation];
559 [_timeLineController updateKeyframeLayer];
560 [_tableController createPropertyTable:l];
561 [_tableController reload];
564 - (IBAction)redo:(id)sender
566 [_undoManager redoOperation];
568 [_timeLineController updateKeyframeLayer];
569 [_tableController reload];
572 - (IBAction)changeMovieSize:(id)sender
574 ElisMovieSizeWindowController* c;
575 c = [[ElisMovieSizeWindowController alloc] init];
577 [c setMainWindow:_mainWindow];
578 [c setMainView:_mainView];
582 - (IBAction)preference:(id)sender
584 ElisPreferenceController*c;
585 c = [[ElisPreferenceController alloc] init];
587 [c setMainWindow:_mainWindow];
591 - (void)textDidChange:(NSNotification*)n
593 CALayer* l = [_timeLineController getSelectLayer];
597 layer = [l valueForKey:@"ElisLayer"];
598 if([[[layer media] type] isEqualToString:@"text"]){
599 NSAttributedString* s = [_textLayerField attributedString];
600 [[layer media] setText:s];
606 - (IBAction)changeMovieSpeed:(id)sender
608 ElisMovieSpeedController* c;
609 c = [[ElisMovieSpeedController alloc] init];
611 [_undoManager pushOperation:[[_timeLineController getSelectLayer] valueForKey:@"ElisLayer"]];
613 [c setMainWindow:_mainWindow];
614 [c setLayer:[[_timeLineController getSelectLayer] valueForKey:@"ElisLayer"]];
620 - (BOOL)canChangeMovieSpeed
623 l = [_timeLineController getSelectLayer];
624 if(l == nil) return NO;
625 if([[[[l valueForKey:@"ElisLayer"] media] type] isEqualToString:@"movie"]) return YES;
629 - (IBAction)gotoNextKeyTime:(id)sender
631 float currentTime = [self getHipTime] * [timeSlider floatValue];
632 NSMutableArray* array = [[NSMutableArray alloc] init];
635 int i, size = [layers count];
637 for(i = 0; i < size; i++){
638 [array addObject:[NSNumber numberWithFloat:0.0]];
639 [array addObject:[NSNumber numberWithFloat:convertQTTimeToSecond([[layers objectAtIndex:i] mapping].time)]];
640 [array addObject:[NSNumber numberWithFloat:convertQTTimeToSecond([[layers objectAtIndex:i] mapping].time) +
641 convertQTTimeToSecond([[layers objectAtIndex:i] mapping].duration)]];
644 [array sortUsingSelector:@selector(compare:)];
645 size = [array count];
647 for(i = 0; i < size; i++)
648 if([[array objectAtIndex:i] floatValue] > currentTime){
649 nextTime = QTMakeTime(([[array objectAtIndex:i] floatValue] +1.0/60.0)* DEFAULT_FPS, DEFAULT_FPS);
650 globalCurrentTime = nextTime;
651 _currentTime = nextTime;
652 [self moveSliderTo:nextTime];
658 - (IBAction)gotoPrevKeyTime:(id)sender
660 float currentTime = [self getHipTime] * [timeSlider floatValue];
661 NSMutableArray* array = [[NSMutableArray alloc] init];
664 int i, size = [layers count];
666 for(i = 0; i < size; i++){
667 [array addObject:[NSNumber numberWithFloat:0.0]];
668 [array addObject:[NSNumber numberWithFloat:convertQTTimeToSecond([[layers objectAtIndex:i] mapping].time)]];
669 [array addObject:[NSNumber numberWithFloat:convertQTTimeToSecond([[layers objectAtIndex:i] mapping].time) +
670 convertQTTimeToSecond([[layers objectAtIndex:i] mapping].duration)]];
673 [array sortUsingSelector:@selector(compare:)];
674 size = [array count];
676 for(i = size-1; i >= 0; i--)
677 if([[array objectAtIndex:i] floatValue] < currentTime){
679 // nextTime = QTMakeTime(([[array objectAtIndex:i] floatValue] -1.0/60)* DEFAULT_FPS, DEFAULT_FPS);
681 // nextTime = QTMakeTime(([[array objectAtIndex:i] floatValue] +1.0/60)* DEFAULT_FPS, DEFAULT_FPS);
682 nextTime = QTMakeTime(([[array objectAtIndex:i] floatValue] +0.0/60)* DEFAULT_FPS, DEFAULT_FPS);
683 globalCurrentTime = nextTime;
684 _currentTime = nextTime;
685 [self moveSliderTo:nextTime];
691 - (IBAction)cutLayerAtCurrentTime:(id)sender
693 ElisLayer* layer = [[_timeLineController getSelectLayer] valueForKey:@"ElisLayer"];
696 new = [layer cutAtTime:globalCurrentTime];
697 [layers addObject:new];
699 [_timeLineController addLayer:[new alayer]];
702 - (IBAction)playStop:(id)sender
704 if(playing) [self stopPlay:nil];
705 else [self startPlay:nil];