/* HBQueueController This file is part of the HandBrake source code. Homepage: . It may be used under the terms of the GNU General Public License. */ #import "HBQueueController.h" #import "Controller.h" #import "HBImageAndTextCell.h" #define HB_ROW_HEIGHT_TITLE_ONLY 17.0 #define HB_ROW_HEIGHT_FULL_DESCRIPTION 200.0 // Pasteboard type for or drag operations #define DragDropSimplePboardType @"MyCustomOutlineViewPboardType" //------------------------------------------------------------------------------------ #pragma mark - //------------------------------------------------------------------------------------ //------------------------------------------------------------------------------------ // NSMutableAttributedString (HBAdditions) //------------------------------------------------------------------------------------ @interface NSMutableAttributedString (HBAdditions) - (void) appendString: (NSString*)aString withAttributes: (NSDictionary *)aDictionary; @end @implementation NSMutableAttributedString (HBAdditions) - (void) appendString: (NSString*)aString withAttributes: (NSDictionary *)aDictionary { NSAttributedString * s = [[[NSAttributedString alloc] initWithString: aString attributes: aDictionary] autorelease]; [self appendAttributedString: s]; } @end @implementation HBQueueOutlineView - (void)viewDidEndLiveResize { // Since we disabled calculating row heights during a live resize, force them to // recalculate now. [self noteHeightOfRowsWithIndexesChanged: [NSIndexSet indexSetWithIndexesInRange: NSMakeRange(0, [self numberOfRows])]]; [super viewDidEndLiveResize]; } /* This should be for dragging, we take this info from the presets right now */ - (NSImage *)dragImageForRowsWithIndexes:(NSIndexSet *)dragRows tableColumns:(NSArray *)tableColumns event:(NSEvent*)dragEvent offset:(NSPointPointer)dragImageOffset { fIsDragging = YES; // By default, NSTableView only drags an image of the first column. Change this to // drag an image of the queue's icon and desc and action columns. NSArray * cols = [NSArray arrayWithObjects: [self tableColumnWithIdentifier:@"desc"], [self tableColumnWithIdentifier:@"icon"],[self tableColumnWithIdentifier:@"action"], nil]; return [super dragImageForRowsWithIndexes:dragRows tableColumns:cols event:dragEvent offset:dragImageOffset]; } - (void) mouseDown:(NSEvent *)theEvent { [super mouseDown:theEvent]; fIsDragging = NO; } - (BOOL) isDragging; { return fIsDragging; } @end #pragma mark Toolbar Identifiers // Toolbar identifiers static NSString* HBQueueToolbar = @"HBQueueToolbar1"; static NSString* HBQueueStartCancelToolbarIdentifier = @"HBQueueStartCancelToolbarIdentifier"; static NSString* HBQueuePauseResumeToolbarIdentifier = @"HBQueuePauseResumeToolbarIdentifier"; #pragma mark - @implementation HBQueueController //------------------------------------------------------------------------------------ // init //------------------------------------------------------------------------------------ - (id)init { if (self = [super initWithWindowNibName:@"Queue"]) { // NSWindowController likes to lazily load its window nib. Since this // controller tries to touch the outlets before accessing the window, we // need to force it to load immadiately by invoking its accessor. // // If/when we switch to using bindings, this can probably go away. [self window]; // Our defaults [[NSUserDefaults standardUserDefaults] registerDefaults:[NSDictionary dictionaryWithObjectsAndKeys: @"NO", @"QueueWindowIsOpen", @"NO", @"QueueShowsDetail", @"YES", @"QueueShowsJobsAsGroups", nil]]; fJobGroups = [[NSMutableArray arrayWithCapacity:0] retain]; } return self; } - (void)setQueueArray: (NSMutableArray *)QueueFileArray { [fJobGroups setArray:QueueFileArray]; fIsDragging = NO; /* First stop any timer working now */ //[self stopAnimatingCurrentJobGroupInQueue]; [fOutlineView reloadData]; /* lets get the stats on the status of the queue array */ fPendingCount = 0; fCompletedCount = 0; fCanceledCount = 0; fWorkingCount = 0; /* We use a number system to set the encode status of the queue item * in controller.mm * 0 == already encoded * 1 == is being encoded * 2 == is yet to be encoded * 3 == cancelled */ int i = 0; NSDictionary *thisQueueDict = nil; for(id tempObject in fJobGroups) { thisQueueDict = tempObject; if ([[thisQueueDict objectForKey:@"Status"] intValue] == 0) // Completed { fCompletedCount++; } if ([[thisQueueDict objectForKey:@"Status"] intValue] == 1) // being encoded { fWorkingCount++; /* we have an encoding job so, lets start the animation timer */ if ([thisQueueDict objectForKey:@"EncodingPID"] && [[thisQueueDict objectForKey:@"EncodingPID"] intValue] == pidNum) { fEncodingQueueItem = i; } } if ([[thisQueueDict objectForKey:@"Status"] intValue] == 2) // pending { fPendingCount++; } if ([[thisQueueDict objectForKey:@"Status"] intValue] == 3) // cancelled { fCanceledCount++; } i++; } /* Set the queue status field in the queue window */ NSMutableString * string; if (fPendingCount == 0) { string = [NSMutableString stringWithFormat: NSLocalizedString( @"No encode pending", @"" )]; } else if (fPendingCount == 1) { string = [NSMutableString stringWithFormat: NSLocalizedString( @"%d encode pending", @"" ), fPendingCount]; } else { string = [NSMutableString stringWithFormat: NSLocalizedString( @"%d encodes pending", @"" ), fPendingCount]; } [fQueueCountField setStringValue:string]; } /* This method sets the status string in the queue window * and is called from Controller.mm (fHBController) * instead of running another timer here polling libhb * for encoding status */ - (void)setQueueStatusString: (NSString *)statusString { [fProgressTextField setStringValue:statusString]; } //------------------------------------------------------------------------------------ // dealloc //------------------------------------------------------------------------------------ - (void)dealloc { // clear the delegate so that windowWillClose is not attempted if( [[self window] delegate] == self ) [[self window] setDelegate:nil]; [fJobGroups release]; [fSavedExpandedItems release]; [fSavedSelectedItems release]; [[NSNotificationCenter defaultCenter] removeObserver:self]; [super dealloc]; } //------------------------------------------------------------------------------------ // Receive HB handle //------------------------------------------------------------------------------------ - (void)setHandle: (hb_handle_t *)handle { fQueueEncodeLibhb = handle; } //------------------------------------------------------------------------------------ // Receive HBController //------------------------------------------------------------------------------------ - (void)setHBController: (HBController *)controller { fHBController = controller; } - (void)setPidNum: (int)myPidnum { pidNum = myPidnum; [fHBController writeToActivityLog: "HBQueueController : My Pidnum is %d", pidNum]; } #pragma mark - //------------------------------------------------------------------------------------ // Displays and brings the queue window to the front //------------------------------------------------------------------------------------ - (IBAction) showQueueWindow: (id)sender { [self showWindow:sender]; [[NSUserDefaults standardUserDefaults] setBool:YES forKey:@"QueueWindowIsOpen"]; [self startAnimatingCurrentWorkingEncodeInQueue]; } //------------------------------------------------------------------------------------ // awakeFromNib //------------------------------------------------------------------------------------ - (void)awakeFromNib { [self setupToolbar]; if( ![[self window] setFrameUsingName:@"Queue"] ) [[self window] center]; [self setWindowFrameAutosaveName:@"Queue"]; /* lets setup our queue list outline view for drag and drop here */ [fOutlineView registerForDraggedTypes: [NSArray arrayWithObject:DragDropSimplePboardType] ]; [fOutlineView setDraggingSourceOperationMask:NSDragOperationEvery forLocal:YES]; [fOutlineView setVerticalMotionCanBeginDrag: YES]; // Don't allow autoresizing of main column, else the "delete" column will get // pushed out of view. [fOutlineView setAutoresizesOutlineColumn: NO]; #if HB_OUTLINE_METRIC_CONTROLS [fIndentation setHidden: NO]; [fSpacing setHidden: NO]; [fIndentation setIntegerValue:[fOutlineView indentationPerLevel]]; // debug [fSpacing setIntegerValue:3]; // debug #endif // Show/hide UI elements fCurrentJobPaneShown = NO; // it's shown in the nib } //------------------------------------------------------------------------------------ // windowWillClose //------------------------------------------------------------------------------------ - (void)windowWillClose:(NSNotification *)aNotification { [[NSUserDefaults standardUserDefaults] setBool:NO forKey:@"QueueWindowIsOpen"]; [self stopAnimatingCurrentJobGroupInQueue]; } #pragma mark Toolbar //------------------------------------------------------------------------------------ // setupToolbar //------------------------------------------------------------------------------------ - (void)setupToolbar { // Create a new toolbar instance, and attach it to our window NSToolbar *toolbar = [[[NSToolbar alloc] initWithIdentifier: HBQueueToolbar] autorelease]; // Set up toolbar properties: Allow customization, give a default display mode, and remember state in user defaults [toolbar setAllowsUserCustomization: YES]; [toolbar setAutosavesConfiguration: YES]; [toolbar setDisplayMode: NSToolbarDisplayModeIconAndLabel]; // We are the delegate [toolbar setDelegate: self]; // Attach the toolbar to our window [[self window] setToolbar:toolbar]; } //------------------------------------------------------------------------------------ // toolbar:itemForItemIdentifier:willBeInsertedIntoToolbar: //------------------------------------------------------------------------------------ - (NSToolbarItem *)toolbar:(NSToolbar *)toolbar itemForItemIdentifier:(NSString *)itemIdentifier willBeInsertedIntoToolbar:(BOOL)flag { // Required delegate method: Given an item identifier, this method returns an item. // The toolbar will use this method to obtain toolbar items that can be displayed // in the customization sheet, or in the toolbar itself. NSToolbarItem *toolbarItem = nil; if ([itemIdentifier isEqual: HBQueueStartCancelToolbarIdentifier]) { toolbarItem = [[[NSToolbarItem alloc] initWithItemIdentifier: itemIdentifier] autorelease]; // Set the text label to be displayed in the toolbar and customization palette [toolbarItem setLabel: @"Start"]; [toolbarItem setPaletteLabel: @"Start/Cancel"]; // Set up a reasonable tooltip, and image [toolbarItem setToolTip: @"Start Encoding"]; [toolbarItem setImage: [NSImage imageNamed: @"Play"]]; // Tell the item what message to send when it is clicked [toolbarItem setTarget: self]; [toolbarItem setAction: @selector(toggleStartCancel:)]; } if ([itemIdentifier isEqual: HBQueuePauseResumeToolbarIdentifier]) { toolbarItem = [[[NSToolbarItem alloc] initWithItemIdentifier: itemIdentifier] autorelease]; // Set the text label to be displayed in the toolbar and customization palette [toolbarItem setLabel: @"Pause"]; [toolbarItem setPaletteLabel: @"Pause/Resume"]; // Set up a reasonable tooltip, and image [toolbarItem setToolTip: @"Pause Encoding"]; [toolbarItem setImage: [NSImage imageNamed: @"Pause"]]; // Tell the item what message to send when it is clicked [toolbarItem setTarget: self]; [toolbarItem setAction: @selector(togglePauseResume:)]; } return toolbarItem; } //------------------------------------------------------------------------------------ // toolbarDefaultItemIdentifiers: //------------------------------------------------------------------------------------ - (NSArray *) toolbarDefaultItemIdentifiers: (NSToolbar *) toolbar { // Required delegate method: Returns the ordered list of items to be shown in the // toolbar by default. return [NSArray arrayWithObjects: HBQueueStartCancelToolbarIdentifier, HBQueuePauseResumeToolbarIdentifier, nil]; } //------------------------------------------------------------------------------------ // toolbarAllowedItemIdentifiers: //------------------------------------------------------------------------------------ - (NSArray *) toolbarAllowedItemIdentifiers: (NSToolbar *) toolbar { // Required delegate method: Returns the list of all allowed items by identifier. // By default, the toolbar does not assume any items are allowed, even the // separator. So, every allowed item must be explicitly listed. return [NSArray arrayWithObjects: HBQueueStartCancelToolbarIdentifier, HBQueuePauseResumeToolbarIdentifier, NSToolbarCustomizeToolbarItemIdentifier, NSToolbarFlexibleSpaceItemIdentifier, NSToolbarSpaceItemIdentifier, NSToolbarSeparatorItemIdentifier, nil]; } //------------------------------------------------------------------------------------ // validateToolbarItem: //------------------------------------------------------------------------------------ - (BOOL) validateToolbarItem: (NSToolbarItem *) toolbarItem { // Optional method: This message is sent to us since we are the target of some // toolbar item actions. if (!fQueueEncodeLibhb) return NO; BOOL enable = NO; hb_state_t s; hb_get_state2 (fQueueEncodeLibhb, &s); if ([[toolbarItem itemIdentifier] isEqual: HBQueueStartCancelToolbarIdentifier]) { if ((s.state == HB_STATE_PAUSED) || (s.state == HB_STATE_WORKING) || (s.state == HB_STATE_MUXING)) { enable = YES; [toolbarItem setImage:[NSImage imageNamed: @"Stop"]]; [toolbarItem setLabel: @"Stop"]; [toolbarItem setToolTip: @"Stop Encoding"]; } else if (fPendingCount > 0) { enable = YES; [toolbarItem setImage:[NSImage imageNamed: @"Play"]]; [toolbarItem setLabel: @"Start"]; [toolbarItem setToolTip: @"Start Encoding"]; } else { enable = NO; [toolbarItem setImage:[NSImage imageNamed: @"Play"]]; [toolbarItem setLabel: @"Start"]; [toolbarItem setToolTip: @"Start Encoding"]; } } if ([[toolbarItem itemIdentifier] isEqual: HBQueuePauseResumeToolbarIdentifier]) { if (s.state == HB_STATE_PAUSED) { enable = YES; [toolbarItem setImage:[NSImage imageNamed: @"Play"]]; [toolbarItem setLabel: @"Resume"]; [toolbarItem setToolTip: @"Resume Encoding"]; } else if ((s.state == HB_STATE_WORKING) || (s.state == HB_STATE_MUXING)) { enable = YES; [toolbarItem setImage:[NSImage imageNamed: @"Pause"]]; [toolbarItem setLabel: @"Pause"]; [toolbarItem setToolTip: @"Pause Encoding"]; } else { enable = NO; [toolbarItem setImage:[NSImage imageNamed: @"Pause"]]; [toolbarItem setLabel: @"Pause"]; [toolbarItem setToolTip: @"Pause Encoding"]; } } return enable; } #pragma mark - #pragma mark Queue Item Controls //------------------------------------------------------------------------------------ // Delete encodes from the queue window and accompanying array // Also handling first cancelling the encode if in fact its currently encoding. //------------------------------------------------------------------------------------ - (IBAction)removeSelectedQueueItem: (id)sender { NSIndexSet * selectedRows = [fOutlineView selectedRowIndexes]; NSUInteger row = [selectedRows firstIndex]; if( row == NSNotFound ) return; /* if this is a currently encoding job, we need to be sure to alert the user, * to let them decide to cancel it first, then if they do, we can come back and * remove it */ if ([[[fJobGroups objectAtIndex:row] objectForKey:@"Status"] integerValue] == 1) { /* We pause the encode here so that it doesn't finish right after and then * screw up the sync while the window is open */ [fHBController Pause:NULL]; NSString * alertTitle = [NSString stringWithFormat:NSLocalizedString(@"Stop This Encode and Remove It ?", nil)]; // Which window to attach the sheet to? NSWindow * docWindow = nil; if ([sender respondsToSelector: @selector(window)]) docWindow = [sender window]; NSBeginCriticalAlertSheet( alertTitle, NSLocalizedString(@"Keep Encoding", nil), nil, NSLocalizedString(@"Stop Encoding and Delete", nil), docWindow, self, nil, @selector(didDimissCancelCurrentJob:returnCode:contextInfo:), nil, NSLocalizedString(@"Your movie will be lost if you don't continue encoding.", nil)); // didDimissCancelCurrentJob:returnCode:contextInfo: will be called when the dialog is dismissed } else { /* since we are not a currently encoding item, we can just be removed */ [fHBController removeQueueFileItem:row]; } } - (void) didDimissCancelCurrentJob: (NSWindow *)sheet returnCode: (int)returnCode contextInfo: (void *)contextInfo { /* We resume encoding and perform the appropriate actions * Note: Pause: is a toggle type method based on hb's current * state, if it paused, it will resume encoding and vice versa. * In this case, we are paused from the calling window, so calling * [fHBController Pause:NULL]; Again will resume encoding */ [fHBController Pause:NULL]; if (returnCode == NSAlertOtherReturn) { /* We need to save the currently encoding item number first */ int encodingItemToRemove = fEncodingQueueItem; /* Since we are encoding, we need to let fHBController Cancel this job * upon which it will move to the next one if there is one */ [fHBController doCancelCurrentJob]; /* Now, we can go ahead and remove the job we just cancelled since * we have its item number from above */ [fHBController removeQueueFileItem:encodingItemToRemove]; } } //------------------------------------------------------------------------------------ // Show the finished encode in the finder //------------------------------------------------------------------------------------ - (IBAction)revealSelectedQueueItem: (id)sender { NSIndexSet * selectedRows = [fOutlineView selectedRowIndexes]; NSInteger row = [selectedRows firstIndex]; if (row != NSNotFound) { while (row != NSNotFound) { NSMutableDictionary *queueItemToOpen = [fOutlineView itemAtRow: row]; [[NSWorkspace sharedWorkspace] selectFile:[queueItemToOpen objectForKey:@"DestinationPath"] inFileViewerRootedAtPath:nil]; row = [selectedRows indexGreaterThanIndex: row]; } } } //------------------------------------------------------------------------------------ // Starts or cancels the processing of jobs depending on the current state //------------------------------------------------------------------------------------ - (IBAction)toggleStartCancel: (id)sender { if (!fQueueEncodeLibhb) return; hb_state_t s; hb_get_state2 (fQueueEncodeLibhb, &s); if ((s.state == HB_STATE_PAUSED) || (s.state == HB_STATE_WORKING) || (s.state == HB_STATE_MUXING)) [fHBController Cancel: fQueuePane]; // sender == fQueuePane so that warning alert shows up on queue window else if (fPendingCount > 0) [fHBController Rip: NULL]; } //------------------------------------------------------------------------------------ // Toggles the pause/resume state of libhb //------------------------------------------------------------------------------------ - (IBAction)togglePauseResume: (id)sender { if (!fQueueEncodeLibhb) return; hb_state_t s; hb_get_state2 (fQueueEncodeLibhb, &s); if (s.state == HB_STATE_PAUSED) { hb_resume (fQueueEncodeLibhb); [self startAnimatingCurrentWorkingEncodeInQueue]; } else if ((s.state == HB_STATE_WORKING) || (s.state == HB_STATE_MUXING)) { hb_pause (fQueueEncodeLibhb); [self stopAnimatingCurrentJobGroupInQueue]; } } //------------------------------------------------------------------------------------ // Send the selected queue item back to the main window for rescan and possible edit. //------------------------------------------------------------------------------------ - (IBAction)editSelectedQueueItem: (id)sender { NSIndexSet * selectedRows = [fOutlineView selectedRowIndexes]; NSUInteger row = [selectedRows firstIndex]; if( row == NSNotFound ) return; /* if this is a currently encoding job, we need to be sure to alert the user, * to let them decide to cancel it first, then if they do, we can come back and * remove it */ if ([[[fJobGroups objectAtIndex:row] objectForKey:@"Status"] integerValue] == 1) { /* We pause the encode here so that it doesn't finish right after and then * screw up the sync while the window is open */ [fHBController Pause:NULL]; NSString * alertTitle = [NSString stringWithFormat:NSLocalizedString(@"Stop This Encode and Remove It ?", nil)]; // Which window to attach the sheet to? NSWindow * docWindow = nil; if ([sender respondsToSelector: @selector(window)]) docWindow = [sender window]; NSBeginCriticalAlertSheet( alertTitle, NSLocalizedString(@"Keep Encoding", nil), nil, NSLocalizedString(@"Stop Encoding and Delete", nil), docWindow, self, nil, @selector(didDimissCancelCurrentJob:returnCode:contextInfo:), nil, NSLocalizedString(@"Your movie will be lost if you don't continue encoding.", nil)); } else { /* since we are not a currently encoding item, we can just be cancelled */ [fHBController rescanQueueItemToMainWindow:[[fJobGroups objectAtIndex:row] objectForKey:@"SourcePath"] scanTitleNum:[[[fJobGroups objectAtIndex:row] objectForKey:@"TitleNumber"] integerValue] selectedQueueItem:row]; } } #pragma mark - #pragma mark Animate Endcoding Item //------------------------------------------------------------------------------------ // Starts animating the job icon of the currently processing job in the queue outline // view. //------------------------------------------------------------------------------------ - (void) startAnimatingCurrentWorkingEncodeInQueue { if (!fAnimationTimer) fAnimationTimer = [[NSTimer scheduledTimerWithTimeInterval:1.0/12.0 // 1/12 because there are 6 images in the animation cycle target:self selector:@selector(animateWorkingEncodeInQueue:) userInfo:nil repeats:YES] retain]; } //------------------------------------------------------------------------------------ // If a job is currently processing, its job icon in the queue outline view is // animated to its next state. //------------------------------------------------------------------------------------ - (void) animateWorkingEncodeInQueue:(NSTimer*)theTimer { if (fWorkingCount > 0) { fAnimationIndex++; fAnimationIndex %= 6; // there are 6 animation images; see outlineView:objectValueForTableColumn:byItem: below. [self animateWorkingEncodeIconInQueue]; } } /* We need to make sure we denote only working encodes even for multiple instances */ - (void) animateWorkingEncodeIconInQueue { NSInteger row = fEncodingQueueItem; /// need to set to fEncodingQueueItem NSInteger col = [fOutlineView columnWithIdentifier: @"icon"]; if (row != -1 && col != -1) { NSRect frame = [fOutlineView frameOfCellAtColumn:col row:row]; [fOutlineView setNeedsDisplayInRect: frame]; } } //------------------------------------------------------------------------------------ // Stops animating the job icon of the currently processing job in the queue outline // view. //------------------------------------------------------------------------------------ - (void) stopAnimatingCurrentJobGroupInQueue { if (fAnimationTimer && [fAnimationTimer isValid]) { [fAnimationTimer invalidate]; [fAnimationTimer release]; fAnimationTimer = nil; } } #pragma mark - - (void)moveObjectsInArray:(NSMutableArray *)array fromIndexes:(NSIndexSet *)indexSet toIndex:(NSUInteger)insertIndex { NSUInteger index = [indexSet lastIndex]; NSUInteger aboveInsertIndexCount = 0; while (index != NSNotFound) { NSUInteger removeIndex; if (index >= insertIndex) { removeIndex = index + aboveInsertIndexCount; aboveInsertIndexCount++; } else { removeIndex = index; insertIndex--; } id object = [[array objectAtIndex:removeIndex] retain]; [array removeObjectAtIndex:removeIndex]; [array insertObject:object atIndex:insertIndex]; [object release]; index = [indexSet indexLessThanIndex:index]; } } #pragma mark - #pragma mark NSOutlineView delegate - (id)outlineView:(NSOutlineView *)fOutlineView child:(NSInteger)index ofItem:(id)item { if (item == nil) return [fJobGroups objectAtIndex:index]; // We are only one level deep, so we can't be asked about children NSAssert (NO, @"HBQueueController outlineView:child:ofItem: can't handle nested items."); return nil; } - (BOOL)outlineView:(NSOutlineView *)fOutlineView isItemExpandable:(id)item { // Our outline view has no levels, but we can still expand every item. Doing so // just makes the row taller. See heightOfRowByItem below. return YES; } - (BOOL)outlineView:(NSOutlineView *)outlineView shouldExpandItem:(id)item { // Our outline view has no levels, but we can still expand every item. Doing so // just makes the row taller. See heightOfRowByItem below. return ![(HBQueueOutlineView*)outlineView isDragging]; } - (NSInteger)outlineView:(NSOutlineView *)fOutlineView numberOfChildrenOfItem:(id)item { // Our outline view has no levels, so number of children will be zero for all // top-level items. if (item == nil) return [fJobGroups count]; else return 0; } - (void)outlineViewItemDidCollapse:(NSNotification *)notification { id item = [[notification userInfo] objectForKey:@"NSObject"]; NSInteger row = [fOutlineView rowForItem:item]; [fOutlineView noteHeightOfRowsWithIndexesChanged:[NSIndexSet indexSetWithIndexesInRange:NSMakeRange(row,1)]]; } - (void)outlineViewItemDidExpand:(NSNotification *)notification { id item = [[notification userInfo] objectForKey:@"NSObject"]; NSInteger row = [fOutlineView rowForItem:item]; [fOutlineView noteHeightOfRowsWithIndexesChanged:[NSIndexSet indexSetWithIndexesInRange:NSMakeRange(row,1)]]; } - (CGFloat)outlineView:(NSOutlineView *)outlineView heightOfRowByItem:(id)item { if ([outlineView isItemExpanded: item]) { /* Below is the original code to accommodate a live resize, * however as stated in travistex's comments it's very buggy. * For now I will leave it here ... commented out and use * the code below to determine the row height based on each * encodes optional parameters and how they are displayed. */ // Short-circuit here if in a live resize primarily to fix a bug but also to // increase resposivness during a resize. There's a bug in NSTableView that // causes row heights to get messed up if you try to change them during a live // resize. So if in a live resize, simply return the previously calculated // height. The row heights will get fixed up after the resize because we have // implemented viewDidEndLiveResize to force all of them to be recalculated. // if ([outlineView inLiveResize] && [item lastDescriptionHeight] > 0) // return [item lastDescriptionHeight]; // CGFloat width = [[outlineView tableColumnWithIdentifier: @"desc"] width]; // Column width is NOT what is ultimately used. I can't quite figure out what // width to use for calculating text metrics. No matter how I tweak this value, // there are a few conditions in which the drawn text extends below the bounds // of the row cell. In previous versions, which ran under Tiger, I was // reducing width by 47 pixles. // width -= 2; // (?) for intercell spacing // CGFloat height = [item heightOfDescriptionForWidth: width]; // return height; /* So, we know several rows of text that are in all queue items for display. * These are the title line, Preset, Format, Destination, Picture, and Video Lines */ CGFloat rowHeightNonTitle = 15.0; /* Add the title line height, then the non title line height for Preset, Format, Destination * Picture and Video */ CGFloat itemHeightForDisplay = HB_ROW_HEIGHT_TITLE_ONLY + (rowHeightNonTitle * 5); /* get our item row number so we an use it to calc how many lines we have to display based * on MP4 Options, Filter Options, X264 Options, Audio Tracks and Subtitles from our queue array */ int itemRowNum = [outlineView rowForItem: item]; NSMutableDictionary *queueItemToCheck = [outlineView itemAtRow: itemRowNum]; /* Check to see if we need to allow for mp4 opts */ BOOL mp4OptsPresent = NO; if ([[queueItemToCheck objectForKey:@"FileFormat"] isEqualToString: @"MP4 file"]) { if( [[queueItemToCheck objectForKey:@"Mp4LargeFile"] intValue] == 1) { mp4OptsPresent = YES; } if( [[queueItemToCheck objectForKey:@"Mp4HttpOptimize"] intValue] == 1) { mp4OptsPresent = YES; } if( [[queueItemToCheck objectForKey:@"Mp4iPodCompatible"] intValue] == 1) { mp4OptsPresent = YES; } } if (mp4OptsPresent == YES) { itemHeightForDisplay += rowHeightNonTitle; } /* check to see if we need to allow for the Picture Filters row */ BOOL pictureFiltersPresent = NO; if( [[queueItemToCheck objectForKey:@"PictureDetelecine"] intValue] > 0) { pictureFiltersPresent = YES; } if( [[queueItemToCheck objectForKey:@"PictureDecomb"] intValue] > 0) { pictureFiltersPresent = YES; } if( [[queueItemToCheck objectForKey:@"PictureDeinterlace"] intValue] > 0) { pictureFiltersPresent = YES; } if( [[queueItemToCheck objectForKey:@"PictureDenoise"] intValue] > 0) { pictureFiltersPresent = YES; } if( [[queueItemToCheck objectForKey:@"PictureDeblock"] intValue] > 0) { pictureFiltersPresent = YES; } if( [[queueItemToCheck objectForKey:@"VideoGrayScale"] intValue] > 0) { pictureFiltersPresent = YES; } if (pictureFiltersPresent == YES) { itemHeightForDisplay += rowHeightNonTitle; } /* check to see if we need a line to display x264 options */ if ([[queueItemToCheck objectForKey:@"VideoEncoder"] isEqualToString: @"H.264 (x264)"]) { itemHeightForDisplay += rowHeightNonTitle; } /* check to see how many audio track lines to allow for */ unsigned int ourMaximumNumberOfAudioTracks = [HBController maximumNumberOfAllowedAudioTracks]; int actualCountOfAudioTracks = 0; for (unsigned int i = 1; i <= ourMaximumNumberOfAudioTracks; i++) { if (0 < [[queueItemToCheck objectForKey: [NSString stringWithFormat: @"Audio%dTrack", i]] intValue]) { actualCountOfAudioTracks++; } } itemHeightForDisplay += (actualCountOfAudioTracks * rowHeightNonTitle); /* add in subtitle lines for each subtitle in the SubtitleList array */ itemHeightForDisplay += rowHeightNonTitle * [[queueItemToCheck objectForKey:@"SubtitleList"] count]; return itemHeightForDisplay; } else { return HB_ROW_HEIGHT_TITLE_ONLY; } } - (CGFloat) heightOfDescriptionForWidth:(CGFloat)width { // Try to return the cached value if no changes have happened since the last time //if ((width == fLastDescriptionWidth) && (fLastDescriptionHeight != 0) && !fNeedsDescription) // return fLastDescriptionHeight; //if (fNeedsDescription) // [self updateDescription]; // Calculate the height //NSRect bounds = [fDescription boundingRectWithSize:NSMakeSize(width, 10000) options:NSStringDrawingUsesLineFragmentOrigin]; //fLastDescriptionHeight = bounds.size.height + 6.0; // add some border to bottom //fLastDescriptionWidth = width; return HB_ROW_HEIGHT_FULL_DESCRIPTION; /* supposedly another way to do this, in case boundingRectWithSize isn't working NSTextView* tmpView = [[NSTextView alloc] initWithFrame:NSMakeRect(0, 0, width, 1)]; [[tmpView textStorage] setAttributedString:aString]; [tmpView setHorizontallyResizable:NO]; [tmpView setVerticallyResizable:YES]; // [[tmpView textContainer] setHeightTracksTextView: YES]; // [[tmpView textContainer] setContainerSize: NSMakeSize(width, 10000)]; [tmpView sizeToFit]; float height = [tmpView frame].size.height; [tmpView release]; return height; */ } - (CGFloat) lastDescriptionHeight { return HB_ROW_HEIGHT_FULL_DESCRIPTION; } - (id)outlineView:(NSOutlineView *)fOutlineView objectValueForTableColumn:(NSTableColumn *)tableColumn byItem:(id)item { if ([[tableColumn identifier] isEqualToString:@"desc"]) { /* Below should be put into a separate method but I am way too f'ing lazy right now */ NSMutableAttributedString * finalString = [[[NSMutableAttributedString alloc] initWithString: @""] autorelease]; // Attributes NSMutableParagraphStyle * ps = [[[NSParagraphStyle defaultParagraphStyle] mutableCopy] retain]; [ps setHeadIndent: 40.0]; [ps setParagraphSpacing: 1.0]; [ps setTabStops:[NSArray array]]; // clear all tabs [ps addTabStop: [[[NSTextTab alloc] initWithType: NSLeftTabStopType location: 20.0] autorelease]]; NSDictionary* detailAttr = [NSDictionary dictionaryWithObjectsAndKeys: [NSFont systemFontOfSize:10.0], NSFontAttributeName, ps, NSParagraphStyleAttributeName, nil]; NSDictionary* detailBoldAttr = [NSDictionary dictionaryWithObjectsAndKeys: [NSFont boldSystemFontOfSize:10.0], NSFontAttributeName, ps, NSParagraphStyleAttributeName, nil]; NSDictionary* titleAttr = [NSDictionary dictionaryWithObjectsAndKeys: [NSFont systemFontOfSize:[NSFont systemFontSize]], NSFontAttributeName, ps, NSParagraphStyleAttributeName, nil]; NSDictionary* shortHeightAttr = [NSDictionary dictionaryWithObjectsAndKeys: [NSFont systemFontOfSize:2.0], NSFontAttributeName, nil]; /* First line, we should strip the destination path and just show the file name and add the title num and chapters (if any) */ NSString * summaryInfo; NSString * titleString = [NSString stringWithFormat:@"Title %d", [[item objectForKey:@"TitleNumber"] intValue]]; NSString * startStopString = @""; if ([[item objectForKey:@"fEncodeStartStop"] intValue] == 0) { /* Start Stop is chapters */ startStopString = ([[item objectForKey:@"ChapterStart"] intValue] == [[item objectForKey:@"ChapterEnd"] intValue]) ? [NSString stringWithFormat:@"Chapter %d", [[item objectForKey:@"ChapterStart"] intValue]] : [NSString stringWithFormat:@"Chapters %d through %d", [[item objectForKey:@"ChapterStart"] intValue], [[item objectForKey:@"ChapterEnd"] intValue]]; } else if ([[item objectForKey:@"fEncodeStartStop"] intValue] == 1) { /* Start Stop is seconds */ startStopString = [NSString stringWithFormat:@"Seconds %d through %d", [[item objectForKey:@"StartSeconds"] intValue], [[item objectForKey:@"StartSeconds"] intValue] + [[item objectForKey:@"StopSeconds"] intValue]]; } else if ([[item objectForKey:@"fEncodeStartStop"] intValue] == 2) { /* Start Stop is Frames */ startStopString = [NSString stringWithFormat:@"Frames %d through %d", [[item objectForKey:@"StartFrame"] intValue], [[item objectForKey:@"StartFrame"] intValue] + [[item objectForKey:@"StopFrame"] intValue]]; } NSString * passesString = @""; /* check to see if our first subtitle track is Foreign Language Search, in which case there is an in depth scan */ if ([item objectForKey:@"SubtitleList"] && [[[[item objectForKey:@"SubtitleList"] objectAtIndex:0] objectForKey:@"subtitleSourceTrackNum"] intValue] == 1) { passesString = [passesString stringByAppendingString:@"1 Foreign Language Search Pass - "]; } if ([[item objectForKey:@"VideoTwoPass"] intValue] == 0) { passesString = [passesString stringByAppendingString:@"1 Video Pass"]; } else { if ([[item objectForKey:@"VideoTurboTwoPass"] intValue] == 1) { passesString = [passesString stringByAppendingString:@"2 Video Passes First Turbo"]; } else { passesString = [passesString stringByAppendingString:@"2 Video Passes"]; } } [finalString appendString:[NSString stringWithFormat:@"%@", [item objectForKey:@"SourceName"]] withAttributes:titleAttr]; /* lets add the output file name to the title string here */ NSString * outputFilenameString = [[item objectForKey:@"DestinationPath"] lastPathComponent]; summaryInfo = [NSString stringWithFormat: @" (%@, %@, %@) -> %@", titleString, startStopString, passesString, outputFilenameString]; [finalString appendString:[NSString stringWithFormat:@"%@\n", summaryInfo] withAttributes:detailAttr]; // Insert a short-in-height line to put some white space after the title [finalString appendString:@"\n" withAttributes:shortHeightAttr]; // End of Title Stuff /* Second Line (Preset Name)*/ [finalString appendString: @"Preset: " withAttributes:detailBoldAttr]; [finalString appendString:[NSString stringWithFormat:@"%@\n", [item objectForKey:@"PresetName"]] withAttributes:detailAttr]; /* Third Line (Format Summary) */ NSString * audioCodecSummary = @""; // This seems to be set by the last track we have available... /* Lets also get our audio track detail since we are going through the logic for use later */ unsigned int ourMaximumNumberOfAudioTracks = [HBController maximumNumberOfAllowedAudioTracks]; NSMutableArray *audioDetails = [NSMutableArray arrayWithCapacity: ourMaximumNumberOfAudioTracks]; NSString *base; NSString *detailString; NSNumber *drc; for (unsigned int i = 1; i <= ourMaximumNumberOfAudioTracks; i++) { base = [NSString stringWithFormat: @"Audio%d", i]; if (0 < [[item objectForKey: [base stringByAppendingString: @"Track"]] intValue]) { audioCodecSummary = [NSString stringWithFormat: @"%@", [item objectForKey: [base stringByAppendingString: @"Encoder"]]]; drc = [item objectForKey: [base stringByAppendingString: @"TrackDRCSlider"]]; detailString = [NSString stringWithFormat: @"%@ Encoder: %@ Mixdown: %@ SampleRate: %@(khz) Bitrate: %@(kbps), DRC: %@", [item objectForKey: [base stringByAppendingString: @"TrackDescription"]], [item objectForKey: [base stringByAppendingString: @"Encoder"]], [item objectForKey: [base stringByAppendingString: @"Mixdown"]], [item objectForKey: [base stringByAppendingString: @"Samplerate"]], [item objectForKey: [base stringByAppendingString: @"Bitrate"]], (0.0 < [drc floatValue]) ? drc : @"Off" ]; [audioDetails addObject: detailString]; } } NSString * jobFormatInfo; if ([[item objectForKey:@"ChapterMarkers"] intValue] == 1) jobFormatInfo = [NSString stringWithFormat:@"%@ Container, %@ Video %@ Audio, Chapter Markers\n", [item objectForKey:@"FileFormat"], [item objectForKey:@"VideoEncoder"], audioCodecSummary]; else jobFormatInfo = [NSString stringWithFormat:@"%@ Container, %@ Video %@ Audio\n", [item objectForKey:@"FileFormat"], [item objectForKey:@"VideoEncoder"], audioCodecSummary]; [finalString appendString: @"Format: " withAttributes:detailBoldAttr]; [finalString appendString: jobFormatInfo withAttributes:detailAttr]; /* Optional String for mp4 options */ if ([[item objectForKey:@"FileFormat"] isEqualToString: @"MP4 file"]) { NSString * MP4Opts = @""; BOOL mp4OptsPresent = NO; if( [[item objectForKey:@"Mp4LargeFile"] intValue] == 1) { mp4OptsPresent = YES; MP4Opts = [MP4Opts stringByAppendingString:@" - Large file size"]; } if( [[item objectForKey:@"Mp4HttpOptimize"] intValue] == 1) { mp4OptsPresent = YES; MP4Opts = [MP4Opts stringByAppendingString:@" - Web optimized"]; } if( [[item objectForKey:@"Mp4iPodCompatible"] intValue] == 1) { mp4OptsPresent = YES; MP4Opts = [MP4Opts stringByAppendingString:@" - iPod 5G support "]; } if (mp4OptsPresent == YES) { [finalString appendString: @"MP4 Options: " withAttributes:detailBoldAttr]; [finalString appendString: MP4Opts withAttributes:detailAttr]; [finalString appendString:@"\n" withAttributes:detailAttr]; } } /* Fourth Line (Destination Path)*/ [finalString appendString: @"Destination: " withAttributes:detailBoldAttr]; [finalString appendString: [item objectForKey:@"DestinationPath"] withAttributes:detailAttr]; [finalString appendString:@"\n" withAttributes:detailAttr]; /* Fifth Line Picture Details*/ NSString * pictureInfo; pictureInfo = [NSString stringWithFormat:@"%@", [item objectForKey:@"PictureSizingSummary"]]; if ([[item objectForKey:@"PictureKeepRatio"] intValue] == 1) { pictureInfo = [pictureInfo stringByAppendingString:@" Keep Aspect Ratio"]; } if ([[item objectForKey:@"VideoGrayScale"] intValue] == 1) { pictureInfo = [pictureInfo stringByAppendingString:@", Grayscale"]; } [finalString appendString: @"Picture: " withAttributes:detailBoldAttr]; [finalString appendString: pictureInfo withAttributes:detailAttr]; [finalString appendString:@"\n" withAttributes:detailAttr]; /* Optional String for Picture Filters */ NSString * pictureFilters = @""; BOOL pictureFiltersPresent = NO; if( [[item objectForKey:@"PictureDetelecine"] intValue] == 1) { pictureFiltersPresent = YES; pictureFilters = [pictureFilters stringByAppendingString:[NSString stringWithFormat:@" - Detelecine (%@)",[item objectForKey:@"PictureDetelecineCustom"]]]; } else if( [[item objectForKey:@"PictureDetelecine"] intValue] == 2) { pictureFiltersPresent = YES; pictureFilters = [pictureFilters stringByAppendingString:@" - Detelecine (Default)"]; } if( [[item objectForKey:@"PictureDecombDeinterlace"] intValue] == 1) { if ([[item objectForKey:@"PictureDecomb"] intValue] != 0) { pictureFiltersPresent = YES; if( [[item objectForKey:@"PictureDecomb"] intValue] == 1) { pictureFiltersPresent = YES; pictureFilters = [pictureFilters stringByAppendingString:[NSString stringWithFormat:@" - Decomb (%@)",[item objectForKey:@"PictureDecombCustom"]]]; } else if( [[item objectForKey:@"PictureDecomb"] intValue] == 2) { pictureFiltersPresent = YES; pictureFilters = [pictureFilters stringByAppendingString:@" - Decomb (Default)"]; } } } else { if ([[item objectForKey:@"PictureDeinterlace"] intValue] != 0) { pictureFiltersPresent = YES; if ([[item objectForKey:@"PictureDeinterlace"] intValue] == 1) { pictureFilters = [pictureFilters stringByAppendingString:[NSString stringWithFormat:@" - Deinterlace (%@)",[item objectForKey:@"PictureDeinterlaceCustom"]]]; } else if ([[item objectForKey:@"PictureDeinterlace"] intValue] == 2) { pictureFilters = [pictureFilters stringByAppendingString:@" - Deinterlace (Fast)"]; } else if ([[item objectForKey:@"PictureDeinterlace"] intValue] == 3) { pictureFilters = [pictureFilters stringByAppendingString:@" - Deinterlace (Slow)"]; } else if ([[item objectForKey:@"PictureDeinterlace"] intValue] == 4) { pictureFilters = [pictureFilters stringByAppendingString:@" - Deinterlace (Slower)"]; } } } if ([[item objectForKey:@"PictureDenoise"] intValue] != 0) { pictureFiltersPresent = YES; if ([[item objectForKey:@"PictureDenoise"] intValue] == 1) { pictureFilters = [pictureFilters stringByAppendingString:[NSString stringWithFormat:@" - Denoise (%@)",[item objectForKey:@"PictureDenoiseCustom"]]]; } else if ([[item objectForKey:@"PictureDenoise"] intValue] == 2) { pictureFilters = [pictureFilters stringByAppendingString:@" - Denoise (Weak)"]; } else if ([[item objectForKey:@"PictureDenoise"] intValue] == 3) { pictureFilters = [pictureFilters stringByAppendingString:@" - Denoise (Medium)"]; } else if ([[item objectForKey:@"PictureDenoise"] intValue] == 4) { pictureFilters = [pictureFilters stringByAppendingString:@" - Denoise (Strong)"]; } } if ([[item objectForKey:@"PictureDeblock"] intValue] != 0) { pictureFiltersPresent = YES; pictureFilters = [pictureFilters stringByAppendingString: [NSString stringWithFormat:@" - Deblock (pp7) (%d)",[[item objectForKey:@"PictureDeblock"] intValue]]]; } if ([[item objectForKey:@"VideoGrayScale"] intValue] == 1) { pictureFiltersPresent = YES; pictureFilters = [pictureFilters stringByAppendingString:@" - Grayscale"]; } if (pictureFiltersPresent == YES) { [finalString appendString: @"Filters: " withAttributes:detailBoldAttr]; [finalString appendString: pictureFilters withAttributes:detailAttr]; [finalString appendString:@"\n" withAttributes:detailAttr]; } /* Sixth Line Video Details*/ NSString * videoInfo; videoInfo = [NSString stringWithFormat:@"Encoder: %@", [item objectForKey:@"VideoEncoder"]]; /* for framerate look to see if we are using vfr detelecine */ if ([[item objectForKey:@"JobIndexVideoFramerate"] intValue] == 0) { if ([[item objectForKey:@"VideoFramerateMode"] isEqualToString:@"vfr"]) { /* we are using same as source with vfr detelecine */ videoInfo = [NSString stringWithFormat:@"%@ Framerate: Same as source (Variable Frame Rate)", videoInfo]; } else { /* we are using a variable framerate without dropping frames */ videoInfo = [NSString stringWithFormat:@"%@ Framerate: Same as source (Constant Frame Rate)", videoInfo]; } } else { /* we have a specified, constant framerate */ if ([[item objectForKey:@"VideoFramerateMode"] isEqualToString:@"pfr"]) { videoInfo = [NSString stringWithFormat:@"%@ Framerate: %@ (Peak Frame Rate)", videoInfo ,[item objectForKey:@"VideoFramerate"]]; } else { videoInfo = [NSString stringWithFormat:@"%@ Framerate: %@ (Constant Frame Rate)", videoInfo ,[item objectForKey:@"VideoFramerate"]]; } } if ([[item objectForKey:@"VideoQualityType"] intValue] == 0)// Target Size MB { videoInfo = [NSString stringWithFormat:@"%@ Target Size: %@(MB) (%d(kbps) abr)", videoInfo ,[item objectForKey:@"VideoTargetSize"],[[item objectForKey:@"VideoAvgBitrate"] intValue]]; } else if ([[item objectForKey:@"VideoQualityType"] intValue] == 1) // ABR { videoInfo = [NSString stringWithFormat:@"%@ Bitrate: %d(kbps)", videoInfo ,[[item objectForKey:@"VideoAvgBitrate"] intValue]]; } else // CRF { videoInfo = [NSString stringWithFormat:@"%@ Constant Quality: %.2f", videoInfo ,[[item objectForKey:@"VideoQualitySlider"] floatValue]]; } [finalString appendString: @"Video: " withAttributes:detailBoldAttr]; [finalString appendString: videoInfo withAttributes:detailAttr]; [finalString appendString:@"\n" withAttributes:detailAttr]; if ([[item objectForKey:@"VideoEncoder"] isEqualToString: @"H.264 (x264)"]) { [finalString appendString: @"x264 Options: " withAttributes:detailBoldAttr]; [finalString appendString: [item objectForKey:@"x264Option"] withAttributes:detailAttr]; [finalString appendString:@"\n" withAttributes:detailAttr]; } /* Seventh Line Audio Details*/ NSEnumerator *audioDetailEnumerator = [audioDetails objectEnumerator]; NSString *anAudioDetail; int audioDetailCount = 0; while (nil != (anAudioDetail = [audioDetailEnumerator nextObject])) { audioDetailCount++; if (0 < [anAudioDetail length]) { [finalString appendString: [NSString stringWithFormat: @"Audio Track %d ", audioDetailCount] withAttributes: detailBoldAttr]; [finalString appendString: anAudioDetail withAttributes: detailAttr]; [finalString appendString: @"\n" withAttributes: detailAttr]; } } /* Eighth Line Subtitle Details */ int i = 0; NSEnumerator *enumerator = [[item objectForKey:@"SubtitleList"] objectEnumerator]; id tempObject; while (tempObject = [enumerator nextObject]) { /* since the subtitleSourceTrackNum 0 is "None" in our array of the subtitle popups, * we want to ignore it for display as well as encoding. */ if ([[tempObject objectForKey:@"subtitleSourceTrackNum"] intValue] > 0) { /* remember that index 0 of Subtitles can contain "Foreign Audio Search*/ [finalString appendString: @"Subtitle: " withAttributes:detailBoldAttr]; [finalString appendString: [tempObject objectForKey:@"subtitleSourceTrackName"] withAttributes:detailAttr]; if ([[tempObject objectForKey:@"subtitleTrackForced"] intValue] == 1) { [finalString appendString: @" - Forced Only" withAttributes:detailAttr]; } if ([[tempObject objectForKey:@"subtitleTrackBurned"] intValue] == 1) { [finalString appendString: @" - Burned In" withAttributes:detailAttr]; } if ([[tempObject objectForKey:@"subtitleTrackDefault"] intValue] == 1) { [finalString appendString: @" - Default" withAttributes:detailAttr]; } [finalString appendString:@"\n" withAttributes:detailAttr]; } i++; } return finalString; } else if ([[tableColumn identifier] isEqualToString:@"icon"]) { if ([[item objectForKey:@"Status"] intValue] == 0) { return [NSImage imageNamed:@"EncodeComplete"]; } else if ([[item objectForKey:@"Status"] intValue] == 1) { return [NSImage imageNamed: [NSString stringWithFormat: @"EncodeWorking%d", fAnimationIndex]]; } else if ([[item objectForKey:@"Status"] intValue] == 3) { return [NSImage imageNamed:@"EncodeCanceled"]; } else { return [NSImage imageNamed:@"JobSmall"]; } } else { return @""; } } /* This method inserts the proper action icons into the far right of the queue window */ - (void)outlineView:(NSOutlineView *)outlineView willDisplayCell:(id)cell forTableColumn:(NSTableColumn *)tableColumn item:(id)item { if ([[tableColumn identifier] isEqualToString:@"desc"]) { // nb: The "desc" column is currently an HBImageAndTextCell. However, we are longer // using the image portion of the cell so we could switch back to a regular NSTextFieldCell. // Set the image here since the value returned from outlineView:objectValueForTableColumn: didn't specify the image part [cell setImage:nil]; } else if ([[tableColumn identifier] isEqualToString:@"action"]) { [cell setEnabled: YES]; BOOL highlighted = [outlineView isRowSelected:[outlineView rowForItem: item]] && [[outlineView window] isKeyWindow] && ([[outlineView window] firstResponder] == outlineView); if ([[item objectForKey:@"Status"] intValue] == 0 || ([[item objectForKey:@"Status"] intValue] == 1 && [[item objectForKey:@"EncodingPID"] intValue] != pidNum)) { [cell setAction: @selector(revealSelectedQueueItem:)]; if (highlighted) { [cell setImage:[NSImage imageNamed:@"RevealHighlight"]]; [cell setAlternateImage:[NSImage imageNamed:@"RevealHighlightPressed"]]; } else [cell setImage:[NSImage imageNamed:@"Reveal"]]; } else { [cell setAction: @selector(removeSelectedQueueItem:)]; if (highlighted) { [cell setImage:[NSImage imageNamed:@"DeleteHighlight"]]; [cell setAlternateImage:[NSImage imageNamed:@"DeleteHighlightPressed"]]; } else [cell setImage:[NSImage imageNamed:@"Delete"]]; } } } - (void)outlineView:(NSOutlineView *)outlineView willDisplayOutlineCell:(id)cell forTableColumn:(NSTableColumn *)tableColumn item:(id)item { // By default, the disclosure image gets centered vertically in the cell. We want // always at the top. if ([outlineView isItemExpanded: item]) [cell setImagePosition: NSImageAbove]; else [cell setImagePosition: NSImageOnly]; } #pragma mark - #pragma mark NSOutlineView delegate (dragging related) //------------------------------------------------------------------------------------ // NSTableView delegate //------------------------------------------------------------------------------------ - (BOOL)outlineView:(NSOutlineView *)outlineView writeItems:(NSArray *)items toPasteboard:(NSPasteboard *)pboard { // Dragging is only allowed of the pending items. if ([[[items objectAtIndex:0] objectForKey:@"Status"] integerValue] != 2) // 2 is pending { return NO; } // 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. fDraggedNodes = items; // Provide data for our custom type, and simple NSStrings. [pboard declareTypes:[NSArray arrayWithObjects: DragDropSimplePboardType, nil] owner:self]; // the actual data doesn't matter since DragDropSimplePboardType drags aren't recognized by anyone but us!. [pboard setData:[NSData data] forType:DragDropSimplePboardType]; return YES; } /* This method is used to validate the drops. */ - (NSDragOperation)outlineView:(NSOutlineView *)outlineView validateDrop:(id )info proposedItem:(id)item proposedChildIndex:(NSInteger)index { // Don't allow dropping ONTO an item since they can't really contain any children. BOOL isOnDropTypeProposal = index == NSOutlineViewDropOnItemIndex; if (isOnDropTypeProposal) { return NSDragOperationNone; } // Don't allow dropping INTO an item since they can't really contain any children. if (item != nil) { index = [fOutlineView rowForItem: item] + 1; item = nil; } // NOTE: Should we allow dropping a pending job *above* the // finished or already encoded jobs ? // We do not let the user drop a pending job before or *above* // already finished or currently encoding jobs. if (index <= fEncodingQueueItem) { return NSDragOperationNone; index = MAX (index, fEncodingQueueItem); } [outlineView setDropItem:item dropChildIndex:index]; return NSDragOperationGeneric; } - (BOOL)outlineView:(NSOutlineView *)outlineView acceptDrop:(id )info item:(id)item childIndex:(NSInteger)index { NSMutableIndexSet *moveItems = [NSMutableIndexSet indexSet]; for( id obj in fDraggedNodes ) [moveItems addIndex:[fJobGroups indexOfObject:obj]]; // Successful drop, we use moveObjectsInQueueArray:... in fHBController // to properly rearrange the queue array, save it to plist and then send it back here. // since Controller.mm is handling all queue array manipulation. // We *could do this here, but I think we are better served keeping that code together. [fHBController moveObjectsInQueueArray:fJobGroups fromIndexes:moveItems toIndex: index]; return YES; } @end