OSDN Git Service

0.2.1
[eliscolors/main.git] / ElisController.m
1 //  Copyright (c) 2009 Yanagi Asakura
2 //
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.
6 //
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:
10 //
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.
15 //
16 //  2. Altered source versions must be plainly marked as such, and must not be
17 //  misrepresented as being the original software.
18 //
19 //  3. This notice may not be removed or altered from any source
20 //  distribution.
21
22 //
23 //  ElisController.m
24 //  Elis Colors
25 //
26 //  Created by 柳 on 09/09/12.
27 //  Copyright 2009 __MyCompanyName__. All rights reserved.
28 //
29
30 #import "ElisController.h"
31
32
33 static float convertQTTimeToSecond(QTTime t)
34 {
35     return (float)t.timeValue/t.timeScale;
36 }
37
38 @implementation ElisController
39
40 - (void)awakeFromNib
41 {
42     layers = [[NSMutableArray alloc] init];
43     _animationLayerFactory = [[ElisAnimationLayerFactory alloc] init];
44     playing = NO;
45     recording = NO;
46     hipTime = 0.0;
47     savePath = nil;
48     
49     ProjectMovieSize = CGRectMake(0, 0, 640, 480);
50     
51     // カスタムフィルタを初期化。
52 //    [ElisCustomFilter class];
53     
54     NSLog(@"Building effects ...");
55     // エフェクトメニューを構築。
56     [self buildEffectMenu];
57     
58     // Quartz Composerのデフォルト再生時間を変更。
59     NSUserDefaults* defaults = [NSUserDefaults standardUserDefaults];
60     [defaults setInteger:60*10 forKey:@"QuartzComposerDefaultMovieDuration"];
61     
62     timeLineXShift = 0;
63     usingStampMode = NO;
64     
65 #ifdef __SNOW_LEOPARD_GCD__
66     diq = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);
67 #endif
68 }
69
70 - (CALayer*)createNewLayer:(NSString*)path
71 {
72     ElisLayer* layer;
73     CALayer* alayer;
74     ElisMedia* m;
75     layer = [[ElisLayer alloc] init];
76     
77     NSWorkspace* sharedWorkspace = [NSWorkspace sharedWorkspace];
78     
79     // 読めるメディアかチェック
80     if([sharedWorkspace type:[sharedWorkspace typeOfFile:path error:nil] 
81               conformsToType:@"public.image"])
82     {
83         m = [[ElisMedia alloc] initWithImageFile:path];
84         alayer = [_animationLayerFactory createNewAnimationLayer:convertQTTimeToSecond([m duration]) 
85                                                             name:[path lastPathComponent] type:@"image"];
86     }
87     else if([sharedWorkspace type:[sharedWorkspace typeOfFile:path error:nil]
88               conformsToType:@"public.audio"])
89     {
90            m = [[ElisMedia alloc] initWithSoundFile:path];
91            alayer = [_animationLayerFactory createNewAnimationLayer:convertQTTimeToSecond([m duration]) 
92                                                                name:[path lastPathComponent] type:@"sound"];
93     }
94     else if([sharedWorkspace type:[sharedWorkspace typeOfFile:path error:nil] 
95                    conformsToType:@"public.movie"] ||
96             [sharedWorkspace type:[sharedWorkspace typeOfFile:path error:nil] 
97                    conformsToType:@"com.apple.quartz-composer-composition"])
98     {
99         m = [[ElisMedia alloc] initWithMovieFile:path];
100         alayer = [_animationLayerFactory createNewAnimationLayer:convertQTTimeToSecond([m duration])
101                                                             name:[path lastPathComponent] type:@"movie"];
102     }
103     else {
104         NSLog(@"error: cannot open %@", path);
105         return nil;
106     }
107
108     layer.media = m;
109 //    [alayer setValue:layer forKey:@"ElisLayer"];
110
111     [layer setAlayer:alayer];
112     [layers addObject:layer];
113     
114     return alayer;
115 }
116
117 - (CALayer*)createNewTextLayer:(NSString*)t
118 {
119 //    [_textLayerField selectAll:nil];
120 //    NSAttributedString* as = [[NSAttributedString alloc]
121 //                              initWithRTF:[_textLayerField RTFFromRange:[_textLayerField selectedRange]] documentAttributes:nil];
122     NSAttributedString* as = [_textLayerField attributedString];
123     ElisLayer* layer = [[ElisLayer alloc] init];
124     ElisMedia* m = [[ElisMedia alloc] initWithText:as];
125     CALayer* cal = [_animationLayerFactory createNewAnimationLayer:convertQTTimeToSecond([m duration])
126                                                               name:@"text" type:@"text"];
127     
128     layer.media = m;
129     [layer setAlayer:cal];
130     [layers addObject:layer];
131     
132     return cal;
133 }
134
135 // 絶対時間qttimeと関係があるレイヤーをまとめて返す。
136 - (void)getFrameForTime:(QTTime)qttime result:(NSMutableArray*)layerSet
137 {
138     int size = [layers count];
139     
140     // 再生時間オーバー。停止。
141     if(convertQTTimeToSecond(qttime) >= hipTime){
142         [_mainView stopDisplayLink];
143         [self stop:qttime];
144         [_playstopButton setState:NSOffState];
145         return;
146     }
147     
148     globalCurrentTime = qttime;
149     [self moveSliderTo:qttime];
150     [_tableController reload];
151     
152     // GCD使ってみたら画面がちらつく。どういうことなの...?
153     // [array addObject] ってどう見てもクリティカルセッションだった。
154 #ifdef __SNOW_LEOPARD_GCD__
155     void** temp = malloc(sizeof(void*) * 128);
156     memset(temp, 0, sizeof(void*)*128);
157     
158     dispatch_apply(size, diq, ^(size_t i) {
159         ElisLayer* l = [layers objectAtIndex:i];
160         if([l isInclude:qttime]){
161             if(playing) [l play];
162 //            [layerSet addObject:l];
163             temp[i] = l;
164         }else{
165             if(playing) [l stop];
166         }
167     });
168     
169     int i;
170     for(i = 0; i < 128; i++)
171         if(temp[i] != 0) [layerSet addObject:temp[i]];
172     free(temp);
173 #else
174     // GCDなりOpenMPなりで並列化すること。
175     int i;
176     ElisLayer* l;
177     for(i = 0; i < size; i++){
178         l = [layers objectAtIndex:i];
179         if([l isInclude:qttime]){
180             if(playing) [l play];
181             [layerSet addObject:l];
182         }else{
183             if(playing) [l stop];
184         }
185     }
186 #endif
187 }
188
189 - (void)play:(QTTime)time
190 {
191     NSMutableArray* interestLayers = [[NSMutableArray alloc] init];
192     int i, size = [layers count];
193     
194     hipTime = [self getHipTime];
195     
196     for(i = 0; i < size; i++)
197         if([[layers objectAtIndex:i] isInclude:time])
198             [interestLayers addObject:[layers objectAtIndex:i]];
199     
200     size = [interestLayers count];
201
202     for(i = 0; i < size; i++)
203         [(ElisLayer*)[interestLayers objectAtIndex:i] play];
204     
205     playing = YES;
206 }
207
208 - (void)stop:(QTTime)time
209 {
210 //    NSMutableArray* interestLayers = [[NSMutableArray alloc] init];
211     int i, size = [layers count];
212     
213 //    hipTime = [self getHipTime];
214 //    
215 //    for(i = 0; i < size; i++)
216 ////        if([[layers objectAtIndex:i] isInclude:time])
217 //        [interestLayers addObject:[layers objectAtIndex:i]];
218 //    
219 //    size = [interestLayers count];
220 //    
221 //    for(i = 0; i < size; i++)
222 //        [(ElisLayer*)[interestLayers objectAtIndex:i] stop];
223     
224     for(i = 0; i < size; i++)
225         [(ElisLayer*)[layers objectAtIndex:i] stop];
226     
227     playing = NO;
228     globalCurrentTime = time;
229     _currentTime = time;
230 }
231
232 - (IBAction)startPlay:(id)sender
233 {
234     hipTime = [self getHipTime];
235     QTTime currentTime = QTMakeTime([timeSlider floatValue] * hipTime * DEFAULT_FPS, DEFAULT_FPS);
236     [self seek:currentTime];
237     [self play:currentTime];
238     [_mainView startDisplayLink];
239 }
240
241 - (IBAction)stopPlay:(id)sender
242 {
243     hipTime = [self getHipTime];
244     QTTime currentTime = QTMakeTime([timeSlider floatValue] * hipTime * DEFAULT_FPS, DEFAULT_FPS);
245     [self stop:currentTime];
246     [_mainView stopDisplayLink];
247 }
248     
249 - (float)getHipTime
250 {
251     int i, size = [layers count];
252     float hipTimeSecond = 0.0f, candidate;
253     
254     for(i = 0; i < size; i++){
255         candidate = convertQTTimeToSecond([[layers objectAtIndex:i] mapping].time)
256                     + convertQTTimeToSecond([[layers objectAtIndex:i] mapping].duration);
257         if(candidate > hipTimeSecond)
258             hipTimeSecond = candidate;
259     }
260
261     return hipTimeSecond;
262 }
263
264 - (void)moveSliderTo:(QTTime)time
265 {
266     float now = convertQTTimeToSecond(time);
267     [self performSelectorOnMainThread:@selector(moveSliderOnThread:) withObject:[NSNumber numberWithFloat:now] waitUntilDone:NO];
268 //    [timeSlider setFloatValue:now/hipTime];
269 //    [timeCodeField setStringValue:QTStringFromTime(QTMakeTime(now * DEFAULT_FPS, DEFAULT_FPS))];
270 //    [_timeLineController movePlaybackBar:now*timeLineScale];
271 }
272
273 - (void)moveSliderOnThread:(NSNumber*)nowv
274 {
275     float now = [nowv floatValue];
276     [timeSlider setFloatValue:now/hipTime];
277     [timeCodeField setStringValue:QTStringFromTime(QTMakeTime(now * DEFAULT_FPS, DEFAULT_FPS))];
278     [_timeLineController movePlaybackBar:now*timeLineScale];
279 }
280
281 - (IBAction)timeSliderChanged:(id)sender
282 {
283     float seconds = [sender floatValue];
284     QTTime currentTime = QTMakeTime(seconds * hipTime * DEFAULT_FPS, DEFAULT_FPS);
285     [timeCodeField setStringValue:QTStringFromTime(currentTime)];
286     _currentTime = currentTime;
287     globalCurrentTime = currentTime;
288     [self refresh];
289 }
290
291 - (void)seek:(QTTime)time
292 {
293     int i, size = [layers count];
294     [_mainView seek:time];
295
296     for(i = 0; i < size; i++)
297         [[layers objectAtIndex:i] seek:time];
298 }
299
300 - (void)refresh
301 {
302     if(playing) return;
303     hipTime = [self getHipTime];
304     [_mainView getFrameForQTTime:_currentTime];
305 }
306
307 - (void)getSoundTrack:(NSMutableArray*)soundTrack
308 {
309     int i, size = [layers count];
310     for(i = 0; i < size; i++)
311         [[layers objectAtIndex:i] getSoundTrack:soundTrack];
312 }
313
314 - (IBAction)deleteSelectLayer:(id)sender
315 {
316     CALayer* selected = [_timeLineController getSelectLayer];
317     ElisLayer* layer = [selected valueForKey:@"ElisLayer"];
318     
319     layer.media = nil;
320     [layers removeObject:layer];
321     [selected removeFromSuperlayer];
322     [_timeLineController removeSelectLayer];
323     [_tableController createPropertyTable:nil];
324     [_tableController reload];
325     [self refresh];
326 }
327
328 - (IBAction)recordingStateChanged:(id)sender
329 {
330     recording = !recording;
331 }
332
333 - (IBAction)removeAllKeyFrame:(id)sender
334 {
335     [_tableController removeAllKeyframe];
336     [self refresh];
337 }
338
339 - (IBAction)removeEffect:(id)sender
340 {
341     [_tableController removeEffect];
342     [self refresh];
343 }
344
345 - (IBAction)writeToFile:(id)sender
346 {
347     NSSavePanel* sp = [NSSavePanel savePanel];
348     
349     [sp setRequiredFileType:@"mov"];
350     [sp beginSheetForDirectory:nil
351                           file:nil 
352                 modalForWindow:_mainWindow
353                  modalDelegate:self
354                 didEndSelector:@selector(writeMovie:returnCode:contextInfo:)
355                    contextInfo:NULL];
356 }
357
358 - (void)writeMovie:(NSSavePanel*)sheet returnCode:(int)code contextInfo:(void*)info
359 {
360     NSString* path = [sheet filename];
361     
362     if(code == NSCancelButton) return;
363     [sheet close];
364     
365     ElisWriterLegacy* writer;
366     writer = [[ElisWriterLegacy alloc] init];
367     [writer setMainWindow:_mainWindow];
368     [writer setMainController:self];
369     [writer setMainView:_mainView];
370     
371 //    [_writer write:sheet];
372     [NSThread detachNewThreadSelector:@selector(write:) toTarget:writer withObject:sheet];
373 }
374
375 // エフェクトのメニュー項目を構築
376 - (void)buildEffectMenu
377 {
378     NSArray* filterNames;
379     CIFilter* filter;
380     NSDictionary* attrs;
381     filterNames = [CIFilter filterNamesInCategories:[NSArray arrayWithObjects:
382                                                      kCICategoryDistortionEffect,
383                                                      kCICategoryGeometryAdjustment,
384                                                      kCICategoryCompositeOperation,
385                                                      kCICategoryHalftoneEffect,
386                                                      kCICategoryColorAdjustment,
387                                                      kCICategoryColorEffect,
388                                                      kCICategoryTransition,
389                                                      kCICategoryTileEffect,
390                                                      kCICategoryGenerator,
391                                                      kCICategoryGradient,
392                                                      kCICategoryStylize,
393                                                      kCICategorySharpen,
394                                                      kCICategoryBlur,
395                                                      nil]];
396     
397     NSString* name;
398     NSArray* inputKeys;
399     id elm;
400     NSMenuItem* item = [[NSMenuItem alloc] init], *child;
401     NSMenu* menu = [[NSMenu alloc] init];
402     int c = 0;
403     
404     [item setTitle:@"Effect"];
405     [menu setTitle:@"Effect"];
406     
407     filterNames = [CIFilter filterNamesInCategory:kCICategoryBuiltIn];
408     
409     for(name in filterNames){
410         filter = [CIFilter filterWithName:name];
411         attrs = [filter attributes];
412         inputKeys = [filter inputKeys];
413         if([inputKeys count] == 1) goto jimp;
414         for(elm in inputKeys){
415             if([elm isEqualToString:@"inputImage"]) continue;
416             if([[[attrs valueForKey:elm] valueForKey:kCIAttributeClass] isEqualToString:@"CIVector"]){
417                 if([[[attrs valueForKey:elm] valueForKey:kCIAttributeDefault] count] == 2) goto ok;
418             }
419             if(!([[[attrs valueForKey:elm] valueForKey:kCIAttributeClass] isEqualToString:@"NSNumber"] || 
420                  [[[attrs valueForKey:elm] valueForKey:kCIAttributeClass] isEqualToString:@"CIColor"])){
421                 goto jimp;
422             }
423         }
424     ok:
425         child = [[[NSMenuItem alloc] init] autorelease];
426         [child setTitle:name];
427         [child setAction:@selector(effectMenuPushed:)];
428         [child setTarget:self];
429         c++;
430         
431         [menu addItem:child];
432         
433     jimp:
434         ; // ラベルの後ろには必ずstatementがないといけないらしい。なんで?
435     }
436     NSLog(@"%d effetcs usable.", c);
437     [item setSubmenu:menu];
438     [menu setAutoenablesItems:NO];
439     [item setEnabled:YES];
440     [menu release];
441     [[NSApp mainMenu] insertItem:item atIndex:5];
442     [item setTarget:self];
443 }
444
445 - (void)effectMenuPushed:(id)sender
446 {
447     CALayer* l;
448     l = [_timeLineController getSelectLayer];
449     [[l valueForKey:@"ElisLayer"] addEffect:[sender title]];
450     [self refresh];
451     [_tableController createPropertyTable:[l valueForKey:@"ElisLayer"]];
452     [_undoManager pushOperation:[l valueForKey:@"ElisLayer"]];
453     [_tableController reload];
454 }
455
456 - (void)saveProjectToFile:(NSSavePanel*)sheet returnCode:(int)code contextInfo:(void*)info
457 {
458     NSString* path = [sheet filename];
459     NSMutableData* data = [NSMutableData data];
460     NSKeyedArchiver* encoder = [[NSKeyedArchiver alloc] initForWritingWithMutableData:data];
461     
462     [encoder encodeObject:layers forKey:@"layers"];
463     [encoder encodeFloat:ProjectMovieSize.size.width forKey:@"movieWidth"];
464     [encoder encodeFloat:ProjectMovieSize.size.height forKey:@"movieHeight"];
465     [encoder encodeObject:ELIS_VERSION forKey:@"version"];
466     [encoder finishEncoding];
467     
468     savePath = path;
469     
470     [data writeToFile:path atomically:YES];
471 }
472
473 - (void)loadProjectFromFile:(NSString*)path
474 {
475     NSMutableData* data = [NSMutableData dataWithContentsOfFile:path];
476     NSKeyedUnarchiver* decoder = [[NSKeyedUnarchiver alloc] initForReadingWithData:data];
477     float w, h;
478     
479     [_timeLineController awakeFromNib]; // これはひどい。
480     NSString* version = [decoder decodeObjectForKey:@"version"];
481     if([version compare:ELIS_VERSION] > 0) return;
482     layers = [decoder decodeObjectForKey:@"layers"];
483     w = [decoder decodeFloatForKey:@"movieWidth"];
484     h = [decoder decodeFloatForKey:@"movieHeight"];
485     [decoder finishDecoding];
486     
487     ProjectMovieSize.size.width = w;
488     ProjectMovieSize.size.height = h;
489     
490     int i, size = [layers count];
491     ElisLayer* l;
492     CALayer* al;
493     
494     for(i = 0; i < size; i++){
495         l = [layers objectAtIndex:i];
496         al = [_animationLayerFactory createNewAnimationLayer:[l duration] name:[l printName] type:[l getType]];
497         [l setLayer:al];
498         [_timeLineController addLayer:al];
499     }
500     
501     savePath = path;
502     
503     [self refresh];
504 }
505
506 - (IBAction)openProjectSaveDialog:(id)sender
507 {
508     NSSavePanel* sp = [NSSavePanel savePanel];
509     
510     [sp setRequiredFileType:@"elis"];
511     [sp beginSheetForDirectory:nil
512                           file:nil 
513                 modalForWindow:_mainWindow
514                  modalDelegate:self
515                 didEndSelector:@selector(saveProjectToFile:returnCode:contextInfo:)
516                    contextInfo:NULL];
517 }
518
519 - (IBAction)openProjectLoadDialog:(id)sender
520 {
521     NSOpenPanel* op = [NSOpenPanel openPanel];
522     
523     int st;
524     st = [op runModalForTypes:[NSArray arrayWithObject:@"elis"]];
525     
526     if(st == NSOKButton)
527         [self loadProjectFromFile:[op filename]];
528 }
529
530 - (BOOL)validateMenuItem:(NSMenuItem *)menuItem
531 {
532     if([menuItem action] == @selector(rewriteProject:))
533         return savePath != nil;
534     
535 //    if([menuItem action] == @selector(removeAllKeyFrame:))
536 //        return [_tableController canRemoveAllKeyframe];
537     
538     if([menuItem action] == @selector(removeEffect:))
539         return [_tableController canRemoveEffect];
540     
541     if([menuItem action] == @selector(deleteSelectLayer:))
542         return [_timeLineController canDeleteLayer];
543     
544     if([menuItem action] == @selector(undo:))
545         return [_undoManager canUndo];
546     
547     if([menuItem action] == @selector(redo:))
548         return [_undoManager canRedo];
549     
550     if([menuItem action] == @selector(changeMovieSpeed:))
551         return [self canChangeMovieSpeed];
552     
553     if([menuItem action] == @selector(cutLayerAtCurrentTime:))
554         return [_timeLineController canDeleteLayer];
555     
556     if([menuItem action] == @selector(effectMenuPushed:))
557         return [self canAddEffect];
558     
559     return YES;
560 }
561
562 - (BOOL)canAddEffect
563 {
564     return ![[[[[_timeLineController getSelectLayer] valueForKey:@"ElisLayer"] media] type] isEqualToString:@"sound"];
565 }
566
567 - (IBAction)rewriteProject:(id)sender
568 {
569     NSMutableData* data = [NSMutableData data];
570     NSKeyedArchiver* encoder = [[NSKeyedArchiver alloc] initForWritingWithMutableData:data];
571     
572     [encoder encodeObject:layers forKey:@"layers"];
573     [encoder encodeFloat:ProjectMovieSize.size.width forKey:@"movieWidth"];
574     [encoder encodeFloat:ProjectMovieSize.size.height forKey:@"movieHeight"];
575     [encoder finishEncoding];
576     
577     [data writeToFile:savePath atomically:YES];
578 }
579
580 - (IBAction)changeToSmallWindiw:(id)sender
581 {
582     [_mainWindow close];
583     [NSBundle loadNibNamed:@"MainMenuForSmallDisplay" owner:self];
584 }
585
586 - (IBAction)undo:(id)sender
587 {
588     ElisLayer* l = [_undoManager popOperation];
589     [self refresh];
590     [_timeLineController updateKeyframeLayer];
591     [_tableController createPropertyTable:l];
592     [_tableController reload];
593 }
594
595 - (IBAction)redo:(id)sender
596 {
597     [_undoManager redoOperation];
598     [self refresh];
599     [_timeLineController updateKeyframeLayer];
600     [_tableController reload];
601 }
602
603 - (IBAction)changeMovieSize:(id)sender
604 {
605     ElisMovieSizeWindowController* c;
606     c = [[ElisMovieSizeWindowController alloc] init];
607     
608     [c setMainWindow:_mainWindow];
609     [c setMainView:_mainView];
610     [c run];
611 }
612
613 - (IBAction)preference:(id)sender
614 {
615     ElisPreferenceController*c;
616     c = [[ElisPreferenceController alloc] init];
617     
618     [c setMainWindow:_mainWindow];
619     [c run];
620 }
621
622 - (void)textDidChange:(NSNotification*)n
623 {
624     CALayer* l = [_timeLineController getSelectLayer];
625     ElisLayer* layer;
626     if(l == nil) return;
627     
628     layer = [l valueForKey:@"ElisLayer"];
629     if([[[layer  media] type] isEqualToString:@"text"]){
630         NSAttributedString* s = [_textLayerField attributedString];
631         [[layer media] setText:s];
632         [self refresh];
633     }
634     
635 }
636
637 - (IBAction)changeMovieSpeed:(id)sender
638 {
639     ElisMovieSpeedController* c;
640     c = [[ElisMovieSpeedController alloc] init];
641     
642     [_undoManager pushOperation:[[_timeLineController getSelectLayer] valueForKey:@"ElisLayer"]];
643     
644     [c setMainWindow:_mainWindow];
645     [c setLayer:[[_timeLineController getSelectLayer] valueForKey:@"ElisLayer"]];
646     [c run];
647     
648     [self refresh];
649 }
650
651 - (BOOL)canChangeMovieSpeed
652 {
653     CALayer* l;
654     l = [_timeLineController getSelectLayer];
655     if(l == nil) return NO;
656     if([[[[l valueForKey:@"ElisLayer"] media] type] isEqualToString:@"movie"]) return YES;
657     return NO;
658 }
659
660 - (IBAction)gotoNextKeyTime:(id)sender
661 {
662     float currentTime = [self getHipTime] * [timeSlider floatValue];
663     NSMutableArray* array = [[NSMutableArray alloc] init];
664     ElisLayer* l;
665     QTTime nextTime;
666     int i, size = [layers count];
667     
668     for(i = 0; i < size; i++){
669         [array addObject:[NSNumber numberWithFloat:0.0]];
670         [array addObject:[NSNumber numberWithFloat:convertQTTimeToSecond([[layers objectAtIndex:i] mapping].time)]];
671         [array addObject:[NSNumber numberWithFloat:convertQTTimeToSecond([[layers objectAtIndex:i] mapping].time) + 
672                           convertQTTimeToSecond([[layers objectAtIndex:i] mapping].duration)]];
673     }
674     
675     [array sortUsingSelector:@selector(compare:)];
676     size = [array count];
677     
678     for(i = 0; i < size; i++)
679         if([[array objectAtIndex:i] floatValue] > currentTime){
680             nextTime = QTMakeTime(([[array objectAtIndex:i] floatValue] +1.0/60.0)* DEFAULT_FPS, DEFAULT_FPS);
681             globalCurrentTime = nextTime;
682             _currentTime = nextTime;
683             [self moveSliderTo:nextTime];
684             [self refresh];
685             return;
686         }
687 }
688
689 - (IBAction)gotoPrevKeyTime:(id)sender
690 {
691     float currentTime = [self getHipTime] * [timeSlider floatValue];
692     NSMutableArray* array = [[NSMutableArray alloc] init];
693     ElisLayer* l;
694     QTTime nextTime;
695     int i, size = [layers count];
696     
697     for(i = 0; i < size; i++){
698         [array addObject:[NSNumber numberWithFloat:0.0]];
699         [array addObject:[NSNumber numberWithFloat:convertQTTimeToSecond([[layers objectAtIndex:i] mapping].time)]];
700         [array addObject:[NSNumber numberWithFloat:convertQTTimeToSecond([[layers objectAtIndex:i] mapping].time) + 
701                           convertQTTimeToSecond([[layers objectAtIndex:i] mapping].duration)]];
702     }
703     
704     [array sortUsingSelector:@selector(compare:)];
705     size = [array count];
706     
707     for(i = size-1; i >= 0; i--)
708         if([[array objectAtIndex:i] floatValue] < currentTime){
709 //            if(i % 2 != 0)
710 //                nextTime = QTMakeTime(([[array objectAtIndex:i] floatValue] -1.0/60)* DEFAULT_FPS, DEFAULT_FPS);
711 //            else
712 //                nextTime = QTMakeTime(([[array objectAtIndex:i] floatValue] +1.0/60)* DEFAULT_FPS, DEFAULT_FPS);
713             nextTime = QTMakeTime(([[array objectAtIndex:i] floatValue] +0.0/60)* DEFAULT_FPS, DEFAULT_FPS);
714             globalCurrentTime = nextTime;
715             _currentTime = nextTime;
716             [self moveSliderTo:nextTime];
717             [self refresh];
718             return;
719         }
720 }
721
722 - (IBAction)cutLayerAtCurrentTime:(id)sender
723 {
724     ElisLayer* layer = [[_timeLineController getSelectLayer] valueForKey:@"ElisLayer"];
725     ElisLayer* new;
726
727     new = [layer cutAtTime:globalCurrentTime];
728     [layers addObject:new];
729     
730     [_timeLineController addLayer:[new alayer]];
731 }
732
733 - (IBAction)playStop:(id)sender
734 {
735     [[NSGarbageCollector defaultCollector] collectExhaustively];
736     if(playing) [self stopPlay:nil];
737     else [self startPlay:nil];
738 }
739
740 - (NSMutableArray*)writeAudioFiles
741 {
742     NSMutableArray* paths = [[NSMutableArray alloc] init];
743     int i, size = [layers count];
744     for(i = 0; i < size; i++){
745         if([[[[layers objectAtIndex:i] media] type] isEqualToString:@"sound"]){
746             ElisAudioWriter* w = [[ElisAudioWriter alloc] initWithLayer:[layers objectAtIndex:i]];
747             NSString* outPath = [NSString stringWithFormat:@"/tmp/%d.wav", i];
748             [w writeToFile:outPath];
749             [paths addObject:outPath];
750         }
751     }
752     
753     return paths;
754 }
755
756 @end