Retired Document
Important: This sample code may not represent best practices for current development. The project may use deprecated symbols and illustrate technologies and techniques that are no longer recommended.
AppController.m
/* |
File: AppController.m |
Abstract: Controller class to manage play buttons, volume slider and |
movie list items |
Version: <1.0> |
Disclaimer: IMPORTANT: This Apple software is supplied to you by Apple |
Computer, Inc. ("Apple") in consideration of your agreement to the |
following terms, and your use, installation, modification or |
redistribution of this Apple software constitutes acceptance of these |
terms. If you do not agree with these terms, please do not use, |
install, modify or redistribute this Apple software. |
In consideration of your agreement to abide by the following terms, and |
subject to these terms, Apple grants you a personal, non-exclusive |
license, under Apple's copyrights in this original Apple software (the |
"Apple Software"), to use, reproduce, modify and redistribute the Apple |
Software, with or without modifications, in source and/or binary forms; |
provided that if you redistribute the Apple Software in its entirety and |
without modifications, you must retain this notice and the following |
text and disclaimers in all such redistributions of the Apple Software. |
Neither the name, trademarks, service marks or logos of Apple Computer, |
Inc. may be used to endorse or promote products derived from the Apple |
Software without specific prior written permission from Apple. Except |
as expressly stated in this notice, no other rights or licenses, express |
or implied, are granted by Apple herein, including but not limited to |
any patent rights that may be infringed by your derivative works or by |
other works in which the Apple Software may be incorporated. |
The Apple Software is provided by Apple on an "AS IS" basis. APPLE |
MAKES NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION |
THE IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS |
FOR A PARTICULAR PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND |
OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS. |
IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL |
OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF |
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS |
INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION, |
MODIFICATION AND/OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED |
AND WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE), |
STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE |
POSSIBILITY OF SUCH DAMAGE. |
Copyright © 2005 Apple Computer, Inc., All Rights Reserved |
*/ |
#import "AppController.h" |
#import "SimpleTreeNode.h" |
#import "ImageAndTextCell.h" |
#import "NSArray_Extensions.h" |
#import "NSOutlineView_Extensions.h" |
#import "QTMovieExtensions.h" |
#import "QTUtils.h" |
// ================================================================ |
// Useful Macros |
// ================================================================ |
#define DragDropSimplePboardType @"MyCustomOutlineViewPboardType" |
#define DragDropMoviePboardType @"MyMoviePboardType" |
#define INITIAL_INFODICT @"InitInfo" |
#define COLUMNID_IS_EXPANDABLE @"IsExpandableColumn" |
#define COLUMNID_NAME @"NameColumn" |
#define COLUMNID_NODE_KIND @"NodeKindColumn" |
// Conveniences for accessing nodes, or the data in the node. |
#define NODE(n) ((SimpleTreeNode*)n) |
#define NODE_DATA(n) ((SimpleNodeData*)[NODE((n)) nodeData]) |
#define SAFENODE(n) ((SimpleTreeNode*)((n)?(n):(treeData))) |
@implementation AppController |
- (id)init { |
NSDictionary *initData = nil; |
self = [super init]; |
if (self==nil) return nil; |
// create an empty dictionary to start with... |
initData = [NSDictionary dictionary]; |
treeData = [[SimpleTreeNode treeFromDictionary: initData] retain]; |
return self; |
} |
- (void)dealloc { |
[treeData release]; |
[draggedNodes release]; |
[iconImages release]; |
treeData = nil; |
draggedNodes = nil; |
iconImages = nil; |
[[NSNotificationCenter defaultCenter] removeObserver:self]; |
[super dealloc]; |
} |
- (void)awakeFromNib { |
NSTableColumn *tableColumn = nil; |
ImageAndTextCell *imageAndTextCell = nil; |
[[NSNotificationCenter defaultCenter] addObserver:self |
selector:@selector(selectionDidChange:) |
name:@"NSOutlineViewSelectionDidChangeNotification" object:outlineView]; |
// Insert custom cell types into the table view, the standard one does text only. |
// We want one column to have text and images. |
tableColumn = [outlineView tableColumnWithIdentifier: COLUMNID_NAME]; |
imageAndTextCell = [[[ImageAndTextCell alloc] init] autorelease]; |
[imageAndTextCell setEditable: YES]; |
[tableColumn setDataCell:imageAndTextCell]; |
// Register to get our custom type, strings, and filenames.... try dragging each into the view! |
[outlineView registerForDraggedTypes:[NSArray arrayWithObjects: |
DragDropSimplePboardType, |
DragDropMoviePboardType, |
NSFilenamesPboardType, |
NSURLPboardType, |
nil]]; |
// we want vertical motion treated as a drag, not |
// as a selection change |
[outlineView setVerticalMotionCanBeginDrag: YES]; |
// initialize the current "active" row |
activeRow = 0; |
} |
- (NSArray*)draggedNodes { return draggedNodes; } |
- (NSArray *)selectedNodes { return [outlineView allSelectedItems]; } |
- (BOOL)allowOnDropOnGroup { return YES; } |
- (BOOL)allowOnDropOnLeaf { return YES; } |
- (BOOL)allowBetweenDrop { return YES; } |
- (BOOL)onlyAcceptDropOnRoot { return NO; } |
// ================================================================ |
// Target / action methods. (most wired up in IB) |
// ================================================================ |
- (void)deleteSelections:(id)sender { |
NSArray *selection = [self selectedNodes]; |
// Tell all of the selected nodes to remove themselves from the model. |
[selection makeObjectsPerformSelector: @selector(removeFromParent)]; |
[outlineView deselectAll:nil]; |
[outlineView reloadData]; |
} |
- (BOOL)validateMenuItem:(id <NSMenuItem>)menuItem { |
if ([menuItem action]==@selector(deleteSelections:)) { |
// The delete selection item should be disabled if nothing is selected. |
return ([[self selectedNodes] count]>0); |
} |
return YES; |
} |
// |
// given an array of movie file paths, create an |
// array of TreeNodes |
// |
-(NSMutableArray *)treeNodesFromMovies:(NSArray *)moviePaths |
{ |
NSMutableArray *treeNodes = nil; |
NSEnumerator *paths = nil; |
id thisPath = nil; |
paths = [moviePaths reverseObjectEnumerator]; |
treeNodes = [[NSMutableArray alloc] init]; |
while (thisPath = [paths nextObject]) |
{ |
MyQTMovie *aQTMovie = [[MyQTMovie alloc] initWithFile:thisPath error:nil]; |
if (aQTMovie) |
{ |
SimpleTreeNode *newItem = |
[SimpleTreeNode treeNodeWithData:[SimpleNodeData leafDataWithMovie:aQTMovie]]; |
[aQTMovie release]; |
[treeNodes addObject:newItem]; |
} |
} |
[treeNodes autorelease]; |
return treeNodes; |
} |
// takes an array of movie TreeNodes and |
// adds them to the NSOutlineView |
// |
-(void)addMoviesToOutlineView:(NSArray *)treeNodes atIndex:(int)childIndex |
{ |
[SAFENODE(nil) insertChildren:treeNodes atIndex:childIndex]; |
} |
@end |
// ================================================================ |
// NSOutlineView data source methods. (The required ones) |
// ================================================================ |
@implementation AppController (DataSourceMethods) |
// Required methods. |
- (id)outlineView:(NSOutlineView *)olv child:(int)index ofItem:(id)item { |
return [SAFENODE(item) childAtIndex:index]; |
} |
- (BOOL)outlineView:(NSOutlineView *)olv isItemExpandable:(id)item { |
return [NODE_DATA(item) isGroup]; |
} |
- (int)outlineView:(NSOutlineView *)olv numberOfChildrenOfItem:(id)item { |
return [SAFENODE(item) numberOfChildren]; |
} |
- (id)outlineView:(NSOutlineView *)olv objectValueForTableColumn:(NSTableColumn *)tableColumn byItem:(id)item { |
id objectValue = nil; |
// The return value from this method is used to configure the state of the items cell via setObjectValue: |
if([[tableColumn identifier] isEqualToString: COLUMNID_NAME]) { |
objectValue = [NODE_DATA(item) name]; |
} else if([[tableColumn identifier] isEqualToString: COLUMNID_IS_EXPANDABLE] && [NODE_DATA(item) isGroup]) { |
// Here, object value will be used to set the state of a check box. |
objectValue = [NSNumber numberWithBool: [NODE_DATA(item) isExpandable]]; |
} else if([[tableColumn identifier] isEqualToString: COLUMNID_NODE_KIND]) { |
objectValue = ([NODE_DATA(item) isLeaf] ? @"Leaf" : @"Group"); |
} |
return objectValue; |
} |
// Optional method: needed to allow editing. |
- (void)outlineView:(NSOutlineView *)olv setObjectValue:(id)object forTableColumn:(NSTableColumn *)tableColumn byItem:(id)item { |
if([[tableColumn identifier] isEqualToString: COLUMNID_NAME]) { |
[NODE_DATA(item) setName: object]; |
} else if ([[tableColumn identifier] isEqualToString: COLUMNID_IS_EXPANDABLE]) { |
[NODE_DATA(item) setIsExpandable: [object boolValue]]; |
if (![NODE_DATA(item) isExpandable] && [outlineView isItemExpanded: item]) [outlineView collapseItem: item]; |
} else if([[tableColumn identifier] isEqualToString: COLUMNID_NODE_KIND]) { |
// We don't allow editing of this column, so we should never actually get here. |
} |
} |
// ================================================================ |
// NSOutlineView delegate methods. |
// ================================================================ |
- (BOOL)outlineView:(NSOutlineView *)olv shouldExpandItem:(id)item { |
return [NODE_DATA(item) isExpandable]; |
} |
- (void)outlineView:(NSOutlineView *)olv willDisplayCell:(NSCell *)cell forTableColumn:(NSTableColumn *)tableColumn item:(id)item { |
if ([[tableColumn identifier] isEqualToString: COLUMNID_NAME]) { |
// Make sure the image and text cell has an image. If not, give it something random. |
if (item && ![NODE_DATA(item) iconRep]) [NODE_DATA(item) setIconRep: [self _setIconImage]]; |
// Set the image here since the value returned from outlineView:objectValueForTableColumn:... didn't specify the image part... |
[(ImageAndTextCell*)cell setImage: [NODE_DATA(item) iconRep]]; |
// For fun, lets display in upper case! |
[(ImageAndTextCell*)cell setStringValue: [[cell stringValue] uppercaseString]]; |
} else if ([[tableColumn identifier] isEqualToString: COLUMNID_IS_EXPANDABLE]) { |
[cell setEnabled: [NODE_DATA(item) isGroup]]; |
} else if ([[tableColumn identifier] isEqualToString: COLUMNID_NODE_KIND]) { |
// Don't do anything unusual for the kind column. |
} |
} |
// ================================================================ |
// NSOutlineView data source methods. (dragging related) |
// ================================================================ |
- (BOOL)outlineView:(NSOutlineView *)olv writeItems:(NSArray*)items toPasteboard:(NSPasteboard*)pboard { |
draggedNodes = items; // Don't retain since this is just holding temporaral drag information, and it is only used during a drag! We could put this in the pboard actually. |
// Provide data for our custom type, and simple NSStrings. |
[pboard declareTypes:[NSArray arrayWithObjects: NSTIFFPboardType, DragDropMoviePboardType, nil] owner:self]; |
// the actual data doesn't matter since DragDropSimplePboardType drags aren't recognized by anyone but us!. |
[pboard setData:[NSData data] forType:DragDropMoviePboardType]; |
return YES; |
} |
- (unsigned int)outlineView:(NSOutlineView*)olv validateDrop:(id <NSDraggingInfo>)info proposedItem:(id)item proposedChildIndex:(int)childIndex { |
// This method validates whether or not the proposal is a valid one. Returns NO if the drop should not be allowed. |
SimpleTreeNode *targetNode = item; |
BOOL targetNodeIsValid = YES; |
if ([self onlyAcceptDropOnRoot]) { |
targetNode = nil; |
childIndex = NSOutlineViewDropOnItemIndex; |
} else { |
BOOL isOnDropTypeProposal = childIndex==NSOutlineViewDropOnItemIndex; |
// Refuse if: dropping "on" the view itself unless we have no data in the view. |
if (targetNode==nil && childIndex==NSOutlineViewDropOnItemIndex && [treeData numberOfChildren]!=0) |
targetNodeIsValid = NO; |
if (targetNode==nil && childIndex==NSOutlineViewDropOnItemIndex && [self allowOnDropOnLeaf]==NO) |
targetNodeIsValid = NO; |
// Refuse if: we are trying to do something which is not allowed as specified by the UI check boxes. |
if ((targetNodeIsValid && isOnDropTypeProposal==NO && [self allowBetweenDrop]==NO) || |
([NODE_DATA(targetNode) isGroup] && isOnDropTypeProposal==YES && [self allowOnDropOnGroup]==NO) || |
([NODE_DATA(targetNode) isLeaf ] && isOnDropTypeProposal==YES && [self allowOnDropOnLeaf]==NO)) |
targetNodeIsValid = NO; |
// Check to make sure we don't allow a node to be inserted into one of its descendants! |
if (targetNodeIsValid && ([info draggingSource]==outlineView) && [[info draggingPasteboard] availableTypeFromArray:[NSArray arrayWithObject: DragDropMoviePboardType]] != nil) { |
NSArray *_draggedNodes = [[[info draggingSource] dataSource] draggedNodes]; |
targetNodeIsValid = ![targetNode isDescendantOfNodeInArray: _draggedNodes]; |
} |
} |
// Set the item and child index in case we computed a retargeted one. |
[outlineView setDropItem:targetNode dropChildIndex:childIndex]; |
return targetNodeIsValid ? NSDragOperationGeneric : NSDragOperationNone; |
} |
- (void)_performDropOperation:(id <NSDraggingInfo>)info onNode:(TreeNode*)parentNode atIndex:(int)childIndex { |
// Helper method to insert dropped data into the model. |
NSPasteboard * pboard = [info draggingPasteboard]; |
NSMutableArray * itemsToSelect = nil; |
BOOL extendSelection = NO; |
// Do the appropriate thing depending on whether the data is DragDropMoviePboardType or NSFilenamesPboardType. |
if ([pboard availableTypeFromArray:[NSArray arrayWithObjects:DragDropMoviePboardType, nil]] != nil) |
{ |
AppController *dragDataSource = [[info draggingSource] dataSource]; |
NSArray *_draggedNodes = [TreeNode minimumNodeCoverFromNodesInArray: [dragDataSource draggedNodes]]; |
NSEnumerator *draggedNodesEnum = [_draggedNodes objectEnumerator]; |
SimpleTreeNode *_draggedNode = nil, *_draggedNodeParent = nil; |
itemsToSelect = (NSMutableArray *)_draggedNodes; |
while ((_draggedNode = [draggedNodesEnum nextObject])) { |
_draggedNodeParent = (SimpleTreeNode*)[_draggedNode nodeParent]; |
if (parentNode==_draggedNodeParent && [parentNode indexOfChild: _draggedNode]<childIndex) childIndex--; |
[_draggedNodeParent removeChild: _draggedNode]; |
} |
[parentNode insertChildren: _draggedNodes atIndex: childIndex]; |
} |
else if ([pboard availableTypeFromArray:[NSArray arrayWithObjects:NSFilenamesPboardType, nil]] != nil) |
{ |
NSArray *moviePaths = [[info draggingPasteboard] propertyListForType:NSFilenamesPboardType]; |
itemsToSelect = [self treeNodesFromMovies:moviePaths]; |
[self addMoviesToOutlineView:itemsToSelect atIndex:childIndex]; |
extendSelection = NO; |
} |
[outlineView reloadData]; |
[outlineView selectItems: itemsToSelect byExtendingSelection: extendSelection]; |
} |
- (BOOL)outlineView:(NSOutlineView*)olv acceptDrop:(id <NSDraggingInfo>)info item:(id)targetItem childIndex:(int)childIndex { |
TreeNode *parentNode = nil; |
// Determine the parent to insert into and the child index to insert at. |
if ([NODE_DATA(targetItem) isLeaf]) { |
parentNode = (SimpleTreeNode*)(childIndex==NSOutlineViewDropOnItemIndex ? [targetItem nodeParent] : targetItem); |
childIndex = (childIndex==NSOutlineViewDropOnItemIndex ? [[targetItem nodeParent] indexOfChild: targetItem]+1 : 0); |
if ([NODE_DATA(parentNode) isLeaf]) [NODE_DATA(parentNode) setIsLeaf:NO]; |
} else { |
parentNode = SAFENODE(targetItem); |
childIndex = (childIndex==NSOutlineViewDropOnItemIndex?0:childIndex); |
} |
[self _performDropOperation:info onNode:parentNode atIndex:childIndex]; |
return YES; |
} |
// |
// received when selection did change |
// |
- (void)selectionDidChange:(NSNotification *)notification |
{ |
[self newMovieSelection]; |
} |
@end |
@implementation AppController (PlayButtonCode) |
-(void)setPlayButtonToUnpressedState |
{ |
[playButton setState:NSOffState]; |
} |
-(void)setPlayButtonToPressedState |
{ |
[playButton setState:NSOnState]; |
} |
-(BOOL)isPlayButtonUnpressed |
{ |
return([playButton state] == NSOffState); |
} |
@end |
@implementation AppController (SliderCode) |
// initialize the movie timeline slider min/max to appropriate |
// values for the current movie |
-(void)initTimeLineSliderForMovie |
{ |
MyQTMovie *curMovie = [self activeRowMovie]; |
QTTime duration = QTZeroTime; |
duration = [curMovie duration]; |
[movieTimeLineSlider setMaxValue: duration.timeValue]; |
[movieTimeLineSlider setIntValue: 0]; |
} |
// set the time line slider to reflect the current movie time |
-(void)adjustTimeLineSliderForCurrentMovieTime |
{ |
MyQTMovie *curMovie = [self activeRowMovie]; |
NSNumber *num = [NSNumber numberWithLongLong:[curMovie currentTimeValue]]; |
[movieTimeLineSlider setDoubleValue:[num doubleValue]]; |
} |
// adjust the time line display (text) to show the current movie |
// time |
-(void)adjustTimeDisplayForCurrentMovieTime |
{ |
MyQTMovie *curMovie = [self activeRowMovie]; |
[movieTimeTextBox setStringValue:QTStringFromTime([curMovie currentPlayTime])]; |
} |
// set movie volume to correspond to current slider setting |
- (void)adjustMovieVolumeToCurrentSliderSetting |
{ |
/* set the movie volume to correspond to the |
current value of the slider */ |
[[theMovieView movie] setVolume:[movieVolumeSlider floatValue]]; |
} |
// slider action proc. -- set movie volume to correspond to |
// current slider setting |
- (IBAction)adjustMovieVolume:sender |
{ |
/* set the movie volume to correspond to the |
current value of the slider */ |
[self adjustMovieVolumeToCurrentSliderSetting]; |
} |
@end |
@implementation AppController (MoviePlayback) |
// button press to begin playing the currently selected movie |
- (IBAction)playMovie:sender |
{ |
switch ([sender state]) |
{ |
case NSOnState: |
if ([self lastMovieInList] && |
[self currentMovieTimeIsEndOfMovie]) |
{ |
[[self activeRowMovie] gotoBeginning]; |
[self initTimeLineSliderForMovie]; |
// play the next available (selected or non-selected) movie |
// in the list, and wraparound to the beginning of the list |
// if necessary |
[self playNextAvailableMovie:YES]; |
} |
else |
{ |
[self playMovieViewMovie]; |
} |
break; |
case NSOffState: |
[[theMovieView movie] setRate:0]; |
break; |
} |
} |
// play the movie currently set for the movie view |
- (void) playMovieViewMovie |
{ |
// if no movie is currently set to the view, then |
// set it to the first available movie |
if (![theMovieView movie]) |
{ |
[self setActiveRowToFirstAvailableRow]; |
} |
// reset GUI only if a new movie has been chosen |
if ([self currentPlayingMovie] != [self currentMovieViewMovie]) |
{ |
[self setupMovieViewForNewMovie]; |
} |
[self setupControlsForNewMovie]; |
[theMovieView play:self]; |
} |
// play the next available (either selected or non-selected) movie |
-(void) playNextAvailableMovie:(BOOL)wrapFlag |
{ |
if([self setNextAvailableMovie:wrapFlag]) |
{ |
[self playMovieViewMovie]; |
} |
} |
-(void)setupMovieViewForNewMovie |
{ |
[theMovieView setMovie:[self activeRowMovie]]; |
[theMovieView setControllerVisible:NO]; |
[theMovieView setEditable:NO]; |
[theMovieView setNeedsDisplay:YES]; |
} |
-(void)setupControlsForNewMovie |
{ |
[self initTimeLineSliderForMovie]; |
[self setPlayButtonToUnpressedState]; |
[self enableMovieCallbacksForCurrentMovie]; |
[self adjustMovieVolumeToCurrentSliderSetting]; |
} |
// button press to set movie time to beginning of the movie |
- (IBAction)goToBeginningOfMovie:sender |
{ |
[theMovieView gotoBeginning:sender]; |
[self adjustTimeLineSliderForCurrentMovieTime]; |
[self adjustTimeDisplayForCurrentMovieTime]; |
} |
// button press to set movie time to end of the movie |
- (IBAction)goToEndOfMovie:sender |
{ |
[theMovieView gotoEnd:sender]; |
[self adjustTimeLineSliderForCurrentMovieTime]; |
[self adjustTimeDisplayForCurrentMovieTime]; |
} |
// returns the movie currently set to the movie view |
-(MyQTMovie *)currentMovieViewMovie |
{ |
return ((MyQTMovie *)[theMovieView movie]); |
} |
// returns the movie from the current active row |
-(MyQTMovie *)currentPlayingMovie |
{ |
return ([self activeRowMovie]); |
} |
// assigns a new movie to the movie view |
-(void)newMovieSelection |
{ |
// first stop any currently playing movie |
if ([(MyQTMovie *)[theMovieView movie] isPlaying] == YES) |
{ |
[[theMovieView movie] setRate:0]; |
[theMovieView gotoBeginning:self]; |
} |
[self clearAllMovieCallbacks]; |
// if no items are selected, set active |
// row item to first available row item |
if ([outlineView numberOfSelectedRows] == 0) |
{ |
[self setActiveRowToFirstAvailableRow]; |
} |
else |
{ |
// get the next available movie |
if (![self setActiveRowToFirstSelectedRow]) |
[self setActiveRowToFirstAvailableRow]; |
} |
[[theMovieView movie] gotoBeginning]; |
[theMovieView gotoBeginning:self]; |
[self setupMovieViewForNewMovie]; |
[self setupControlsForNewMovie]; |
} |
// returns YES if the current movie is the last available |
// movie in the list, NO if not |
-(BOOL) lastMovieInList |
{ |
if ([self isActiveRowSelected]) |
{ |
return( [self isActiveRowLastSelectedRow]); |
} |
else |
{ |
return ([self isActiveRowLastRow]); |
} |
} |
// returns YES if the current movie time is the end of the movie, |
// NO if not |
- (BOOL) currentMovieTimeIsEndOfMovie |
{ |
return[[self activeRowMovie] currentTimeEqualsDuration]; |
} |
// set current movie playing cell to next available |
// (selected or non-selected) cell |
-(BOOL) setNextAvailableMovie:(BOOL)wrapFlag |
{ |
if ([self isActiveRowSelected]) |
{ |
return([self setActiveRowToNextSelectedRow:wrapFlag]); |
} |
else |
{ |
return ([self setActiveRowToNextRow:wrapFlag]); |
} |
} |
// returns the number of movies in the list |
-(int)numberOfMoviesInList |
{ |
return( [SAFENODE(nil) numberOfChildren]); |
} |
-(MyQTMovie *) movieAtRow:(int)index |
{ |
int numChildren = [self numberOfMoviesInList]; |
if (index < numChildren) |
{ |
SimpleTreeNode *root = SAFENODE(nil); |
TreeNode *tNode = [root childAtIndex:index]; |
SimpleNodeData *nodeData = (SimpleNodeData *)[tNode nodeData]; |
return([nodeData movie]); |
} |
return nil; |
} |
// create a single flattened, self-contained movie for |
// all the available movie clips |
- (IBAction)createMovieFileWithMovieClips:(id)sender |
{ |
QTMovie *flattenedMovie = nil; |
int i, numClips = 0; |
// first allocate an array to store all our movie clips |
NSMutableArray *movieClips = [[NSMutableArray alloc] init]; |
if (!movieClips) goto bail; |
numClips = [self numberOfMoviesInList]; |
for (i=0 ; i<numClips ; ++i) |
{ |
[movieClips addObject:[self movieAtRow:i]]; |
} |
// now create a single movie file which contains all the |
// movie clips |
NSString *destFilePath = [self putFileString]; |
if (nil != destFilePath) |
{ |
flattenedMovie = [QTUtils appendMovies:movieClips destFilePath:destFilePath]; |
} |
// clean up - release our array |
[movieClips release]; |
bail: |
return; |
} |
@end |
@implementation AppController (MovieCallbacks) |
// remove callbacks (movie playback, movie end, movie stopped) |
// for all movies |
-(void)clearAllMovieCallbacks |
{ |
int i, numClips = 0; |
numClips = [self numberOfMoviesInList]; |
for (i=0 ; i<numClips ; ++i) |
{ |
[[self movieAtRow:i] removeAllMovieCallbacks]; |
} |
} |
// enable callbacks (movie playback, movie end, movie stopped) |
// for current movie |
-(void)enableMovieCallbacksForCurrentMovie |
{ |
MyQTMovie *aMovie = [self activeRowMovie]; |
[aMovie setMovieDidEndNotificationCallback:@selector(movieDidEndCallBack:) withObject:self]; |
[aMovie setMovieRateChangeNotificationCallback:@selector(movieStoppedCallBack:) withObject:self]; |
[aMovie setMoviePlaybackNotificationCallback:@selector(moviePlayingCallBack:) withObject:self]; |
} |
// routine to be called during movie playback |
-(void)moviePlayingCallBack:(id)sender |
{ |
if (![movieTimeLineSlider isUserDraggingSlider]) |
{ |
[self adjustTimeLineSliderForCurrentMovieTime]; |
[self adjustTimeDisplayForCurrentMovieTime]; |
} |
if ([sender isPlaying]) |
{ |
if ([self isPlayButtonUnpressed]) |
[self setPlayButtonToPressedState]; |
} |
} |
// routine to be called when movie playback stops |
-(void)movieStoppedCallBack:(id)sender |
{ |
[self adjustTimeLineSliderForCurrentMovieTime]; |
[self adjustTimeDisplayForCurrentMovieTime]; |
if (![movieTimeLineSlider isUserDraggingSlider]) |
{ |
[self setPlayButtonToUnpressedState]; |
} |
} |
// routine to be called when movie playback ends (end of movie) |
-(void)movieDidEndCallBack:(id)sender |
{ |
if (![movieTimeLineSlider isUserDraggingSlider]) |
[(MyQTMovie *)sender removeAllMovieCallbacks]; |
// play the next available (selected or non-selected) movie |
// in the list, but don't wraparound to the beginning of |
// the list |
if (![movieTimeLineSlider isUserDraggingSlider]) |
{ |
[self playNextAvailableMovie:NO]; |
} |
} |
@end |
@implementation AppController (ActiveRow) |
- (MyQTMovie *) activeRowMovie |
{ |
return([self movieAtRow:[self activeRow]]); |
} |
-(void) setActiveRow:(int)aRowItem |
{ |
activeRow = aRowItem; |
} |
- (int) activeRow |
{ |
return activeRow; |
} |
-(BOOL) isActiveRowSelected |
{ |
return ([outlineView isRowSelected:activeRow]); |
} |
-(BOOL) isActiveRowLastRow |
{ |
return (activeRow == ([outlineView numberOfRows] - 1)); |
} |
-(BOOL) isActiveRowLastSelectedRow |
{ |
return(activeRow == [outlineView lastSelectedRow]); |
} |
-(BOOL) setActiveRowToNextSelectedRow:(BOOL)wrapFlag |
{ |
int newRow = [outlineView nextSelectedRow:activeRow wrapOK:wrapFlag]; |
if (NSNotFound != newRow) |
{ |
[self setActiveRow:newRow]; |
return YES; |
} |
return NO; |
} |
-(BOOL) setActiveRowToNextRow:(BOOL)wrapFlag |
{ |
int newRow = [outlineView nextRow:activeRow wrapOK:wrapFlag]; |
if (NSNotFound != newRow) |
{ |
[self setActiveRow:newRow]; |
return YES; |
} |
return NO; |
} |
-(BOOL) setActiveRowToFirstSelectedRow |
{ |
int newRow = [outlineView nextSelectedRow:-1 wrapOK:NO]; |
if (NSNotFound != newRow) |
{ |
[self setActiveRow:newRow]; |
return YES; |
} |
return NO; |
} |
-(BOOL) setActiveRowToFirstAvailableRow |
{ |
int newRow = [outlineView nextRow:-1 wrapOK:YES]; |
if (NSNotFound != newRow) |
{ |
[self setActiveRow:newRow]; |
return YES; |
} |
return NO; |
} |
@end |
@implementation AppController (FileUtils) |
// Open panel delegate methods |
- (IBAction) openFile: sender |
{ |
NSOpenPanel *panel = [NSOpenPanel openPanel]; |
[panel setCanChooseDirectories:YES]; |
[panel setAllowsMultipleSelection:YES]; |
[panel // Get the shared open panel |
beginSheetForDirectory:NSHomeDirectory() // Point it at the user's home |
file:nil |
types:[QTMovie movieUnfilteredFileTypes] |
modalForWindow:[outlineView window] // This makes it show up as a sheet, attached to window |
modalDelegate:self |
didEndSelector:@selector(openPanelDidEnd:returnCode:contextInfo:) // Call this method when you're done.. |
contextInfo:NULL]; |
} |
- (void) openPanelDidEnd:(NSOpenPanel *)sheet returnCode:(int)returnCode contextInfo:(void *)contextInfo |
{ |
if (returnCode == 1) |
{ |
NSArray *treeNodes = [self treeNodesFromMovies:[sheet filenames]]; |
[self addMoviesToOutlineView:treeNodes atIndex:[self activeRow]]; |
[outlineView reloadData]; |
[outlineView selectItems: treeNodes byExtendingSelection: NO]; |
} |
} |
// put up the save panel dialog to allow the user to specify |
// a save file, then return this filename |
-(NSString *)putFileString |
{ |
NSSavePanel *savePanel = nil; |
savePanel = [NSSavePanel savePanel]; |
if (!savePanel) goto bail; |
if ([savePanel runModalForDirectory:nil file:@"newMovie.mov"] == NSOKButton) |
{ |
return [savePanel filename]; |
} |
bail: |
return nil; |
} |
@end |
@implementation AppController (Private) |
- (NSImage *)_setIconImage { |
NSImage *theImage = nil; |
NSString *imageName = [NSString stringWithFormat:@"moov icon.tiff"]; |
theImage = [NSImage imageNamed:imageName]; |
return theImage; |
} |
@end |
Copyright © 2006 Apple Computer, Inc. All Rights Reserved. Terms of Use | Privacy Policy | Updated: 2006-01-03