OSDN Git Service

9f7ce38a054dd4038d431911c322e57f39bba2d4
[handbrake-jp/handbrake-jp-git.git] / macosx / HBQueueController.mm
1 /* HBQueueController
2
3     This file is part of the HandBrake source code.
4     Homepage: <http://handbrake.fr/>.
5     It may be used under the terms of the GNU General Public License. */
6
7 #import "HBQueueController.h"
8 #import "Controller.h"
9 #import "HBImageAndTextCell.h"
10
11 #define HB_ROW_HEIGHT_TITLE_ONLY           17.0
12 #define HB_ROW_HEIGHT_FULL_DESCRIPTION           200.0
13 // Pasteboard type for or drag operations
14 #define DragDropSimplePboardType        @"MyCustomOutlineViewPboardType"
15
16 //------------------------------------------------------------------------------------
17 #pragma mark -
18 //------------------------------------------------------------------------------------
19
20 //------------------------------------------------------------------------------------
21 // NSMutableAttributedString (HBAdditions)
22 //------------------------------------------------------------------------------------
23
24 @interface NSMutableAttributedString (HBAdditions)
25 - (void) appendString: (NSString*)aString withAttributes: (NSDictionary *)aDictionary;
26 @end
27
28 @implementation NSMutableAttributedString (HBAdditions)
29 - (void) appendString: (NSString*)aString withAttributes: (NSDictionary *)aDictionary
30 {
31     NSAttributedString * s = [[[NSAttributedString alloc]
32         initWithString: aString
33         attributes: aDictionary] autorelease];
34     [self appendAttributedString: s];
35 }
36 @end
37
38
39 @implementation HBQueueOutlineView
40
41 - (void)viewDidEndLiveResize
42 {
43     // Since we disabled calculating row heights during a live resize, force them to
44     // recalculate now.
45     [self noteHeightOfRowsWithIndexesChanged:
46             [NSIndexSet indexSetWithIndexesInRange: NSMakeRange(0, [self numberOfRows])]];
47     [super viewDidEndLiveResize];
48 }
49
50
51
52 /* This should be for dragging, we take this info from the presets right now */
53 - (NSImage *)dragImageForRowsWithIndexes:(NSIndexSet *)dragRows tableColumns:(NSArray *)tableColumns event:(NSEvent*)dragEvent offset:(NSPointPointer)dragImageOffset
54 {
55     fIsDragging = YES;
56
57     // By default, NSTableView only drags an image of the first column. Change this to
58     // drag an image of the queue's icon and desc and action columns.
59     NSArray * cols = [NSArray arrayWithObjects: [self tableColumnWithIdentifier:@"desc"], [self tableColumnWithIdentifier:@"icon"],[self tableColumnWithIdentifier:@"action"], nil];
60     return [super dragImageForRowsWithIndexes:dragRows tableColumns:cols event:dragEvent offset:dragImageOffset];
61 }
62
63
64
65 - (void) mouseDown:(NSEvent *)theEvent
66 {
67     [super mouseDown:theEvent];
68         fIsDragging = NO;
69 }
70
71
72
73 - (BOOL) isDragging;
74 {
75     return fIsDragging;
76 }
77
78 @end
79
80 #pragma mark Toolbar Identifiers
81 // Toolbar identifiers
82 static NSString*    HBQueueToolbar                            = @"HBQueueToolbar1";
83 static NSString*    HBQueueStartCancelToolbarIdentifier       = @"HBQueueStartCancelToolbarIdentifier";
84 static NSString*    HBQueuePauseResumeToolbarIdentifier       = @"HBQueuePauseResumeToolbarIdentifier";
85
86 #pragma mark -
87
88 @implementation HBQueueController
89
90 //------------------------------------------------------------------------------------
91 // init
92 //------------------------------------------------------------------------------------
93 - (id)init
94 {
95     if (self = [super initWithWindowNibName:@"Queue"])
96     {
97         // NSWindowController likes to lazily load its window nib. Since this
98         // controller tries to touch the outlets before accessing the window, we
99         // need to force it to load immadiately by invoking its accessor.
100         //
101         // If/when we switch to using bindings, this can probably go away.
102         [self window];
103
104         // Our defaults
105         [[NSUserDefaults standardUserDefaults] registerDefaults:[NSDictionary dictionaryWithObjectsAndKeys:
106             @"NO",      @"QueueWindowIsOpen",
107             @"NO",      @"QueueShowsDetail",
108             @"YES",     @"QueueShowsJobsAsGroups",
109             nil]];
110
111         fJobGroups = [[NSMutableArray arrayWithCapacity:0] retain];
112        } 
113         return self;
114 }
115
116 - (void)setQueueArray: (NSMutableArray *)QueueFileArray
117 {
118     [fJobGroups setArray:QueueFileArray];
119     fIsDragging = NO; 
120     /* First stop any timer working now */
121     [self stopAnimatingCurrentJobGroupInQueue];
122     [fOutlineView reloadData];
123     
124     
125     
126     /* lets get the stats on the status of the queue array */
127     
128     fEncodingQueueItem = 0;
129     fPendingCount = 0;
130     fCompletedCount = 0;
131     fCanceledCount = 0;
132     fWorkingCount = 0;
133     
134     /* We use a number system to set the encode status of the queue item
135      * in controller.mm
136      * 0 == already encoded
137      * 1 == is being encoded
138      * 2 == is yet to be encoded
139      * 3 == cancelled
140      */
141     
142         int i = 0;
143         for(id tempObject in fJobGroups)
144         {
145                 NSDictionary *thisQueueDict = tempObject;
146                 if ([[thisQueueDict objectForKey:@"Status"] intValue] == 0) // Completed
147                 {
148                         fCompletedCount++;      
149                 }
150                 if ([[thisQueueDict objectForKey:@"Status"] intValue] == 1) // being encoded
151                 {
152                         fWorkingCount++;
153             fEncodingQueueItem = i;     
154                 }
155         if ([[thisQueueDict objectForKey:@"Status"] intValue] == 2) // pending          
156         {
157                         fPendingCount++;
158                 }
159         if ([[thisQueueDict objectForKey:@"Status"] intValue] == 3) // cancelled                
160         {
161                         fCanceledCount++;
162                 }
163                 i++;
164         }
165     
166     /* We should fire up the encoding timer here based on fWorkingCount */
167     
168     if (fWorkingCount > 0)
169     {
170         /* we have an encoding job so, lets start the animation timer */
171         [self startAnimatingCurrentWorkingEncodeInQueue];
172     }
173     
174     /* Set the queue status field in the queue window */
175     NSMutableString * string;
176     if (fPendingCount == 1)
177     {
178         string = [NSMutableString stringWithFormat: NSLocalizedString( @"%d encode pending", @"" ), fPendingCount];
179     }
180     else
181     {
182         string = [NSMutableString stringWithFormat: NSLocalizedString( @"%d encode(s) pending", @"" ), fPendingCount];
183     }
184     [fQueueCountField setStringValue:string];
185     
186 }
187 /* This method sets the status string in the queue window
188  * and is called from Controller.mm (fHBController)
189  * instead of running another timer here polling libhb
190  * for encoding status
191  */
192 - (void)setQueueStatusString: (NSString *)statusString
193 {
194 [fProgressTextField setStringValue:statusString];
195 }
196
197 //------------------------------------------------------------------------------------
198 // dealloc
199 //------------------------------------------------------------------------------------
200 - (void)dealloc
201 {
202     // clear the delegate so that windowWillClose is not attempted
203     if( [[self window] delegate] == self )
204         [[self window] setDelegate:nil];
205
206     [fJobGroups release];
207
208     [fSavedExpandedItems release];
209     [fSavedSelectedItems release];
210
211     [[NSNotificationCenter defaultCenter] removeObserver:self];
212
213     [super dealloc];
214 }
215
216 //------------------------------------------------------------------------------------
217 // Receive HB handle
218 //------------------------------------------------------------------------------------
219 - (void)setHandle: (hb_handle_t *)handle
220 {
221     fQueueEncodeLibhb = handle;
222 }
223
224 //------------------------------------------------------------------------------------
225 // Receive HBController
226 //------------------------------------------------------------------------------------
227 - (void)setHBController: (HBController *)controller
228 {
229     fHBController = controller;
230 }
231
232 #pragma mark -
233
234 //------------------------------------------------------------------------------------
235 // Displays and brings the queue window to the front
236 //------------------------------------------------------------------------------------
237 - (IBAction) showQueueWindow: (id)sender
238 {
239     [self showWindow:sender];
240     [[NSUserDefaults standardUserDefaults] setBool:YES forKey:@"QueueWindowIsOpen"];
241 }
242
243
244
245 //------------------------------------------------------------------------------------
246 // awakeFromNib
247 //------------------------------------------------------------------------------------
248 - (void)awakeFromNib
249 {
250     [self setupToolbar];
251
252     if( ![[self window] setFrameUsingName:@"Queue"] )
253         [[self window] center];
254     [self setWindowFrameAutosaveName:@"Queue"];
255     //[[self window] setExcludedFromWindowsMenu:YES];
256
257     /* lets setup our queue list outline view for drag and drop here */
258     [fOutlineView registerForDraggedTypes: [NSArray arrayWithObject:DragDropSimplePboardType] ];
259     [fOutlineView setDraggingSourceOperationMask:NSDragOperationEvery forLocal:YES];
260     [fOutlineView setVerticalMotionCanBeginDrag: YES];
261
262
263     // Don't allow autoresizing of main column, else the "delete" column will get
264     // pushed out of view.
265     [fOutlineView setAutoresizesOutlineColumn: NO];
266
267 #if HB_OUTLINE_METRIC_CONTROLS
268     [fIndentation setHidden: NO];
269     [fSpacing setHidden: NO];
270     [fIndentation setIntegerValue:[fOutlineView indentationPerLevel]];  // debug
271     [fSpacing setIntegerValue:3];       // debug
272 #endif
273
274     // Show/hide UI elements
275     fCurrentJobPaneShown = NO;     // it's shown in the nib
276     //[self showCurrentJobPane:NO];
277
278     //[self updateQueueCountField];
279 }
280
281
282 //------------------------------------------------------------------------------------
283 // windowWillClose
284 //------------------------------------------------------------------------------------
285 - (void)windowWillClose:(NSNotification *)aNotification
286 {
287     [[NSUserDefaults standardUserDefaults] setBool:NO forKey:@"QueueWindowIsOpen"];
288 }
289
290 #pragma mark Toolbar
291
292 //------------------------------------------------------------------------------------
293 // setupToolbar
294 //------------------------------------------------------------------------------------
295 - (void)setupToolbar
296 {
297     // Create a new toolbar instance, and attach it to our window
298     NSToolbar *toolbar = [[[NSToolbar alloc] initWithIdentifier: HBQueueToolbar] autorelease];
299
300     // Set up toolbar properties: Allow customization, give a default display mode, and remember state in user defaults
301     [toolbar setAllowsUserCustomization: YES];
302     [toolbar setAutosavesConfiguration: YES];
303     [toolbar setDisplayMode: NSToolbarDisplayModeIconAndLabel];
304
305     // We are the delegate
306     [toolbar setDelegate: self];
307
308     // Attach the toolbar to our window
309     [[self window] setToolbar:toolbar];
310 }
311
312 //------------------------------------------------------------------------------------
313 // toolbar:itemForItemIdentifier:willBeInsertedIntoToolbar:
314 //------------------------------------------------------------------------------------
315 - (NSToolbarItem *)toolbar:(NSToolbar *)toolbar
316         itemForItemIdentifier:(NSString *)itemIdentifier
317         willBeInsertedIntoToolbar:(BOOL)flag
318 {
319     // Required delegate method: Given an item identifier, this method returns an item.
320     // The toolbar will use this method to obtain toolbar items that can be displayed
321     // in the customization sheet, or in the toolbar itself.
322
323     NSToolbarItem *toolbarItem = nil;
324
325     if ([itemIdentifier isEqual: HBQueueStartCancelToolbarIdentifier])
326     {
327         toolbarItem = [[[NSToolbarItem alloc] initWithItemIdentifier: itemIdentifier] autorelease];
328
329         // Set the text label to be displayed in the toolbar and customization palette
330         [toolbarItem setLabel: @"Start"];
331         [toolbarItem setPaletteLabel: @"Start/Cancel"];
332
333         // Set up a reasonable tooltip, and image
334         [toolbarItem setToolTip: @"Start Encoding"];
335         [toolbarItem setImage: [NSImage imageNamed: @"Play"]];
336
337         // Tell the item what message to send when it is clicked
338         [toolbarItem setTarget: self];
339         [toolbarItem setAction: @selector(toggleStartCancel:)];
340     }
341
342     if ([itemIdentifier isEqual: HBQueuePauseResumeToolbarIdentifier])
343     {
344         toolbarItem = [[[NSToolbarItem alloc] initWithItemIdentifier: itemIdentifier] autorelease];
345
346         // Set the text label to be displayed in the toolbar and customization palette
347         [toolbarItem setLabel: @"Pause"];
348         [toolbarItem setPaletteLabel: @"Pause/Resume"];
349
350         // Set up a reasonable tooltip, and image
351         [toolbarItem setToolTip: @"Pause Encoding"];
352         [toolbarItem setImage: [NSImage imageNamed: @"Pause"]];
353
354         // Tell the item what message to send when it is clicked
355         [toolbarItem setTarget: self];
356         [toolbarItem setAction: @selector(togglePauseResume:)];
357     }
358
359     return toolbarItem;
360 }
361
362 //------------------------------------------------------------------------------------
363 // toolbarDefaultItemIdentifiers:
364 //------------------------------------------------------------------------------------
365 - (NSArray *) toolbarDefaultItemIdentifiers: (NSToolbar *) toolbar
366 {
367     // Required delegate method: Returns the ordered list of items to be shown in the
368     // toolbar by default.
369
370     return [NSArray arrayWithObjects:
371         HBQueueStartCancelToolbarIdentifier,
372         HBQueuePauseResumeToolbarIdentifier,
373         nil];
374 }
375
376 //------------------------------------------------------------------------------------
377 // toolbarAllowedItemIdentifiers:
378 //------------------------------------------------------------------------------------
379 - (NSArray *) toolbarAllowedItemIdentifiers: (NSToolbar *) toolbar
380 {
381     // Required delegate method: Returns the list of all allowed items by identifier.
382     // By default, the toolbar does not assume any items are allowed, even the
383     // separator. So, every allowed item must be explicitly listed.
384
385     return [NSArray arrayWithObjects:
386         HBQueueStartCancelToolbarIdentifier,
387         HBQueuePauseResumeToolbarIdentifier,
388         NSToolbarCustomizeToolbarItemIdentifier,
389         NSToolbarFlexibleSpaceItemIdentifier,
390         NSToolbarSpaceItemIdentifier,
391         NSToolbarSeparatorItemIdentifier,
392         nil];
393 }
394
395 //------------------------------------------------------------------------------------
396 // validateToolbarItem:
397 //------------------------------------------------------------------------------------
398 - (BOOL) validateToolbarItem: (NSToolbarItem *) toolbarItem
399 {
400     // Optional method: This message is sent to us since we are the target of some
401     // toolbar item actions.
402
403     if (!fQueueEncodeLibhb) return NO;
404
405     BOOL enable = NO;
406
407     hb_state_t s;
408     hb_get_state2 (fQueueEncodeLibhb, &s);
409
410     if ([[toolbarItem itemIdentifier] isEqual: HBQueueStartCancelToolbarIdentifier])
411     {
412         if ((s.state == HB_STATE_PAUSED) || (s.state == HB_STATE_WORKING) || (s.state == HB_STATE_MUXING))
413         {
414             enable = YES;
415             [toolbarItem setImage:[NSImage imageNamed: @"Stop"]];
416             [toolbarItem setLabel: @"Stop"];
417             [toolbarItem setToolTip: @"Stop Encoding"];
418         }
419
420         else if (fPendingCount > 0)
421         {
422             enable = YES;
423             [toolbarItem setImage:[NSImage imageNamed: @"Play"]];
424             [toolbarItem setLabel: @"Start"];
425             [toolbarItem setToolTip: @"Start Encoding"];
426         }
427
428         else
429         {
430             enable = NO;
431             [toolbarItem setImage:[NSImage imageNamed: @"Play"]];
432             [toolbarItem setLabel: @"Start"];
433             [toolbarItem setToolTip: @"Start Encoding"];
434         }
435     }
436
437     if ([[toolbarItem itemIdentifier] isEqual: HBQueuePauseResumeToolbarIdentifier])
438     {
439         if (s.state == HB_STATE_PAUSED)
440         {
441             enable = YES;
442             [toolbarItem setImage:[NSImage imageNamed: @"Play"]];
443             [toolbarItem setLabel: @"Resume"];
444             [toolbarItem setToolTip: @"Resume Encoding"];
445        }
446
447         else if ((s.state == HB_STATE_WORKING) || (s.state == HB_STATE_MUXING))
448         {
449             enable = YES;
450             [toolbarItem setImage:[NSImage imageNamed: @"Pause"]];
451             [toolbarItem setLabel: @"Pause"];
452             [toolbarItem setToolTip: @"Pause Encoding"];
453         }
454         else
455         {
456             enable = NO;
457             [toolbarItem setImage:[NSImage imageNamed: @"Pause"]];
458             [toolbarItem setLabel: @"Pause"];
459             [toolbarItem setToolTip: @"Pause Encoding"];
460         }
461     }
462
463     return enable;
464 }
465
466 #pragma mark -
467
468
469 #pragma mark Queue Item Controls
470 //------------------------------------------------------------------------------------
471 // Delete encodes from the queue window and accompanying array
472 // Also handling first cancelling the encode if in fact its currently encoding.
473 //------------------------------------------------------------------------------------
474 - (IBAction)removeSelectedQueueItem: (id)sender
475 {
476     NSIndexSet * selectedRows = [fOutlineView selectedRowIndexes];
477     NSUInteger row = [selectedRows firstIndex];
478     if( row == NSNotFound )
479         return;
480     /* if this is a currently encoding job, we need to be sure to alert the user,
481      * to let them decide to cancel it first, then if they do, we can come back and
482      * remove it */
483     
484     if ([[[fJobGroups objectAtIndex:row] objectForKey:@"Status"] integerValue] == 1)
485     {
486        /* We pause the encode here so that it doesn't finish right after and then
487         * screw up the sync while the window is open
488         */
489        [fHBController Pause:NULL];
490          NSString * alertTitle = [NSString stringWithFormat:NSLocalizedString(@"Stop This Encode and Remove It ?", nil)];
491         // Which window to attach the sheet to?
492         NSWindow * docWindow = nil;
493         if ([sender respondsToSelector: @selector(window)])
494             docWindow = [sender window];
495         
496         
497         NSBeginCriticalAlertSheet(
498                                   alertTitle,
499                                   NSLocalizedString(@"Keep Encoding", nil),
500                                   nil,
501                                   NSLocalizedString(@"Stop Encoding and Delete", nil),
502                                   docWindow, self,
503                                   nil, @selector(didDimissCancelCurrentJob:returnCode:contextInfo:), nil,
504                                   NSLocalizedString(@"Your movie will be lost if you don't continue encoding.", nil));
505         
506         // didDimissCancelCurrentJob:returnCode:contextInfo: will be called when the dialog is dismissed
507     }
508     else
509     { 
510     /* since we are not a currently encoding item, we can just be cancelled */
511             [fHBController removeQueueFileItem:row];
512     }
513 }
514
515 - (void) didDimissCancelCurrentJob: (NSWindow *)sheet returnCode: (int)returnCode contextInfo: (void *)contextInfo
516 {
517     /* We resume encoding and perform the appropriate actions 
518      * Note: Pause: is a toggle type method based on hb's current
519      * state, if it paused, it will resume encoding and vice versa.
520      * In this case, we are paused from the calling window, so calling
521      * [fHBController Pause:NULL]; Again will resume encoding
522      */
523        [fHBController Pause:NULL];
524     if (returnCode == NSAlertOtherReturn)
525     {
526     /* We need to save the currently encoding item number first */
527     int encodingItemToRemove = fEncodingQueueItem;
528     /* Since we are encoding, we need to let fHBController Cancel this job
529      * upon which it will move to the next one if there is one
530      */
531     [fHBController doCancelCurrentJob];
532     /* Now, we can go ahead and remove the job we just cancelled since
533      * we have its item number from above
534      */
535     [fHBController removeQueueFileItem:encodingItemToRemove];
536     }
537     
538 }
539
540 //------------------------------------------------------------------------------------
541 // Show the finished encode in the finder
542 //------------------------------------------------------------------------------------
543 - (IBAction)revealSelectedQueueItem: (id)sender
544 {
545     NSIndexSet * selectedRows = [fOutlineView selectedRowIndexes];
546     NSInteger row = [selectedRows firstIndex];
547     if (row != NSNotFound)
548     {
549         while (row != NSNotFound)
550         {
551            NSMutableDictionary *queueItemToOpen = [fOutlineView itemAtRow: row];
552          [[NSWorkspace sharedWorkspace] selectFile:[queueItemToOpen objectForKey:@"DestinationPath"] inFileViewerRootedAtPath:nil];
553
554             row = [selectedRows indexGreaterThanIndex: row];
555         }
556     }
557 }
558
559
560 //------------------------------------------------------------------------------------
561 // Starts or cancels the processing of jobs depending on the current state
562 //------------------------------------------------------------------------------------
563 - (IBAction)toggleStartCancel: (id)sender
564 {
565     if (!fQueueEncodeLibhb) return;
566
567     hb_state_t s;
568     hb_get_state2 (fQueueEncodeLibhb, &s);
569
570     if ((s.state == HB_STATE_PAUSED) || (s.state == HB_STATE_WORKING) || (s.state == HB_STATE_MUXING))
571         [fHBController Cancel: fQueuePane]; // sender == fQueuePane so that warning alert shows up on queue window
572
573     else if (fPendingCount > 0)
574         [fHBController Rip: NULL];
575 }
576
577 //------------------------------------------------------------------------------------
578 // Toggles the pause/resume state of libhb
579 //------------------------------------------------------------------------------------
580 - (IBAction)togglePauseResume: (id)sender
581 {
582     if (!fQueueEncodeLibhb) return;
583     
584     hb_state_t s;
585     hb_get_state2 (fQueueEncodeLibhb, &s);
586     
587     if (s.state == HB_STATE_PAUSED)
588     {
589         hb_resume (fQueueEncodeLibhb);
590         [self startAnimatingCurrentWorkingEncodeInQueue];
591     }
592     else if ((s.state == HB_STATE_WORKING) || (s.state == HB_STATE_MUXING))
593     {
594         hb_pause (fQueueEncodeLibhb);
595         [self stopAnimatingCurrentJobGroupInQueue];
596     }
597 }
598
599
600 //------------------------------------------------------------------------------------
601 // Send the selected queue item back to the main window for rescan and possible edit.
602 //------------------------------------------------------------------------------------
603 - (IBAction)editSelectedQueueItem: (id)sender
604 {
605     NSIndexSet * selectedRows = [fOutlineView selectedRowIndexes];
606     NSUInteger row = [selectedRows firstIndex];
607     if( row == NSNotFound )
608         return;
609     /* if this is a currently encoding job, we need to be sure to alert the user,
610      * to let them decide to cancel it first, then if they do, we can come back and
611      * remove it */
612     
613     if ([[[fJobGroups objectAtIndex:row] objectForKey:@"Status"] integerValue] == 1)
614     {
615        /* We pause the encode here so that it doesn't finish right after and then
616         * screw up the sync while the window is open
617         */
618        [fHBController Pause:NULL];
619          NSString * alertTitle = [NSString stringWithFormat:NSLocalizedString(@"Stop This Encode and Remove It ?", nil)];
620         // Which window to attach the sheet to?
621         NSWindow * docWindow = nil;
622         if ([sender respondsToSelector: @selector(window)])
623             docWindow = [sender window];
624         
625         
626         NSBeginCriticalAlertSheet(
627                                   alertTitle,
628                                   NSLocalizedString(@"Keep Encoding", nil),
629                                   nil,
630                                   NSLocalizedString(@"Stop Encoding and Delete", nil),
631                                   docWindow, self,
632                                   nil, @selector(didDimissCancelCurrentJob:returnCode:contextInfo:), nil,
633                                   NSLocalizedString(@"Your movie will be lost if you don't continue encoding.", nil));
634         
635     }
636     else
637     { 
638     /* since we are not a currently encoding item, we can just be cancelled */
639     [fHBController rescanQueueItemToMainWindow:[[fJobGroups objectAtIndex:row] objectForKey:@"SourcePath"] scanTitleNum:[[[fJobGroups objectAtIndex:row] objectForKey:@"TitleNumber"] integerValue] selectedQueueItem:row];
640     
641     }
642 }
643
644
645 #pragma mark -
646 #pragma mark Animate Endcoding Item
647
648
649
650
651 //------------------------------------------------------------------------------------
652 // Starts animating the job icon of the currently processing job in the queue outline
653 // view.
654 //------------------------------------------------------------------------------------
655 - (void) startAnimatingCurrentWorkingEncodeInQueue
656 {
657     if (!fAnimationTimer)
658         fAnimationTimer = [[NSTimer scheduledTimerWithTimeInterval:1.0/12.0     // 1/12 because there are 6 images in the animation cycle
659                 target:self
660                 selector:@selector(animateWorkingEncodeInQueue:)
661                 userInfo:nil
662                 repeats:YES] retain];
663 }
664
665 //------------------------------------------------------------------------------------
666 // If a job is currently processing, its job icon in the queue outline view is
667 // animated to its next state.
668 //------------------------------------------------------------------------------------
669 - (void) animateWorkingEncodeInQueue:(NSTimer*)theTimer
670 {
671     if (fWorkingCount > 0)
672     {
673         fAnimationIndex++;
674         fAnimationIndex %= 6;   // there are 6 animation images; see outlineView:objectValueForTableColumn:byItem: below.
675         [self animateWorkingEncodeIconInQueue];
676     }
677 }
678
679
680 - (void) animateWorkingEncodeIconInQueue
681 {
682     NSInteger row = fEncodingQueueItem; /// need to set to fEncodingQueueItem
683     NSInteger col = [fOutlineView columnWithIdentifier: @"icon"];
684     if (row != -1 && col != -1)
685     {
686         NSRect frame = [fOutlineView frameOfCellAtColumn:col row:row];
687         [fOutlineView setNeedsDisplayInRect: frame];
688     }
689 }
690
691 //------------------------------------------------------------------------------------
692 // Stops animating the job icon of the currently processing job in the queue outline
693 // view.
694 //------------------------------------------------------------------------------------
695 - (void) stopAnimatingCurrentJobGroupInQueue
696 {
697     if (fAnimationTimer && [fAnimationTimer isValid])
698     {
699         [fAnimationTimer invalidate];
700         [fAnimationTimer release];
701         fAnimationTimer = nil;
702     }
703 }
704
705
706 #pragma mark -
707
708 - (void)moveObjectsInArray:(NSMutableArray *)array fromIndexes:(NSIndexSet *)indexSet toIndex:(NSUInteger)insertIndex
709 {
710     NSUInteger index = [indexSet lastIndex];
711     NSUInteger aboveInsertIndexCount = 0;
712
713     while (index != NSNotFound)
714     {
715         NSUInteger removeIndex;
716
717         if (index >= insertIndex)
718         {
719             removeIndex = index + aboveInsertIndexCount;
720             aboveInsertIndexCount++;
721         }
722         else
723         {
724             removeIndex = index;
725             insertIndex--;
726         }
727
728         id object = [[array objectAtIndex:removeIndex] retain];
729         [array removeObjectAtIndex:removeIndex];
730         [array insertObject:object atIndex:insertIndex];
731         [object release];
732
733         index = [indexSet indexLessThanIndex:index];
734     }
735 }
736
737
738 #pragma mark -
739 #pragma mark NSOutlineView delegate
740
741
742 - (id)outlineView:(NSOutlineView *)fOutlineView child:(NSInteger)index ofItem:(id)item
743 {
744     if (item == nil)
745         return [fJobGroups objectAtIndex:index];
746
747     // We are only one level deep, so we can't be asked about children
748     NSAssert (NO, @"HBQueueController outlineView:child:ofItem: can't handle nested items.");
749     return nil;
750 }
751
752 - (BOOL)outlineView:(NSOutlineView *)fOutlineView isItemExpandable:(id)item
753 {
754     // Our outline view has no levels, but we can still expand every item. Doing so
755     // just makes the row taller. See heightOfRowByItem below.
756     return YES;
757 }
758
759 - (BOOL)outlineView:(NSOutlineView *)outlineView shouldExpandItem:(id)item
760 {
761     // Our outline view has no levels, but we can still expand every item. Doing so
762     // just makes the row taller. See heightOfRowByItem below.
763 return ![(HBQueueOutlineView*)outlineView isDragging];
764 }
765
766 - (NSInteger)outlineView:(NSOutlineView *)fOutlineView numberOfChildrenOfItem:(id)item
767 {
768     // Our outline view has no levels, so number of children will be zero for all
769     // top-level items.
770     if (item == nil)
771         return [fJobGroups count];
772     else
773         return 0;
774 }
775
776 - (void)outlineViewItemDidCollapse:(NSNotification *)notification
777 {
778     id item = [[notification userInfo] objectForKey:@"NSObject"];
779     NSInteger row = [fOutlineView rowForItem:item];
780     [fOutlineView noteHeightOfRowsWithIndexesChanged:[NSIndexSet indexSetWithIndexesInRange:NSMakeRange(row,1)]];
781 }
782
783 - (void)outlineViewItemDidExpand:(NSNotification *)notification
784 {
785     id item = [[notification userInfo] objectForKey:@"NSObject"];
786     NSInteger row = [fOutlineView rowForItem:item];
787     [fOutlineView noteHeightOfRowsWithIndexesChanged:[NSIndexSet indexSetWithIndexesInRange:NSMakeRange(row,1)]];
788 }
789
790 - (CGFloat)outlineView:(NSOutlineView *)outlineView heightOfRowByItem:(id)item
791 {
792     if ([outlineView isItemExpanded: item])
793     {
794         /* Below is the original code to accommodate a live resize,
795          * however as stated in travistex's comments it's very buggy.
796          * For now I will leave it here ... commented out and use
797          * the code below to determine the row height based on each
798          * encodes optional parameters and how they are displayed. */
799         
800         // Short-circuit here if in a live resize primarily to fix a bug but also to
801         // increase resposivness during a resize. There's a bug in NSTableView that
802         // causes row heights to get messed up if you try to change them during a live
803         // resize. So if in a live resize, simply return the previously calculated
804         // height. The row heights will get fixed up after the resize because we have
805         // implemented viewDidEndLiveResize to force all of them to be recalculated.
806         // if ([outlineView inLiveResize] && [item lastDescriptionHeight] > 0)
807         //   return [item lastDescriptionHeight];
808         
809         // CGFloat width = [[outlineView tableColumnWithIdentifier: @"desc"] width];
810         // Column width is NOT what is ultimately used. I can't quite figure out what
811         // width to use for calculating text metrics. No matter how I tweak this value,
812         // there are a few conditions in which the drawn text extends below the bounds
813         // of the row cell. In previous versions, which ran under Tiger, I was
814         // reducing width by 47 pixles.
815         // width -= 2;     // (?) for intercell spacing
816         
817         // CGFloat height = [item heightOfDescriptionForWidth: width];
818         // return height;
819         
820         /* So, we know several rows of text that are in all queue items for display.
821          * These are the title line, Preset, Format, Destination, Picture, and Video Lines
822          */
823         CGFloat rowHeightNonTitle = 15.0;
824         /* Add the title line height, then the non title line height for Preset, Format, Destination
825          * Picture and Video
826          */
827         CGFloat itemHeightForDisplay = HB_ROW_HEIGHT_TITLE_ONLY + (rowHeightNonTitle * 5);
828         
829         /* get our item row number so we an use it to calc how many lines we have to display based
830          * on MP4 Options, Filter Options, X264 Options, Audio Tracks and Subtitles from our queue array */
831         int itemRowNum = [outlineView rowForItem: item];
832         NSMutableDictionary *queueItemToCheck = [outlineView itemAtRow: itemRowNum];
833         
834         /* Check to see if we need to allow for mp4 opts */
835         BOOL mp4OptsPresent = NO;
836         if ([[queueItemToCheck objectForKey:@"FileFormat"] isEqualToString: @"MP4 file"])
837         {
838             
839             if( [[queueItemToCheck objectForKey:@"Mp4LargeFile"] intValue] == 1)
840             {
841                 mp4OptsPresent = YES;
842             }
843             if( [[queueItemToCheck objectForKey:@"Mp4HttpOptimize"] intValue] == 1)
844             {
845                 mp4OptsPresent = YES;
846             }
847             if( [[queueItemToCheck objectForKey:@"Mp4iPodCompatible"] intValue] == 1)
848             {
849                 mp4OptsPresent = YES;
850             }
851         }
852         
853         if (mp4OptsPresent == YES)
854         {
855             itemHeightForDisplay +=  rowHeightNonTitle;   
856         }
857         
858         /* check to see if we need to allow for the Picture Filters row */
859         BOOL pictureFiltersPresent = NO;
860         if( [[queueItemToCheck objectForKey:@"PictureDetelecine"] intValue] > 0)
861         {
862             pictureFiltersPresent = YES;
863         }
864         if( [[queueItemToCheck objectForKey:@"PictureDecomb"] intValue] > 0)
865         {
866             pictureFiltersPresent = YES;
867         }
868         if( [[queueItemToCheck objectForKey:@"PictureDeinterlace"] intValue] > 0)
869         {
870             pictureFiltersPresent = YES;
871         }
872         if( [[queueItemToCheck objectForKey:@"PictureDenoise"] intValue] > 0)
873         {
874             pictureFiltersPresent = YES;
875         }
876         if( [[queueItemToCheck objectForKey:@"PictureDeblock"] intValue] > 0)
877         {
878             pictureFiltersPresent = YES;
879         }
880         if( [[queueItemToCheck objectForKey:@"VideoGrayScale"] intValue] > 0)
881         {
882             pictureFiltersPresent = YES;
883         }
884         
885         if (pictureFiltersPresent == YES)
886         {
887             itemHeightForDisplay +=  rowHeightNonTitle;
888         }
889         
890         /* check to see if we need a line to display x264 options */
891         if ([[queueItemToCheck objectForKey:@"VideoEncoder"] isEqualToString: @"H.264 (x264)"])
892         {
893             itemHeightForDisplay +=  rowHeightNonTitle;
894         }
895         
896         /* check to see how many audio track lines to allow for */
897         if ([[queueItemToCheck objectForKey:@"Audio1Track"] intValue] > 0)
898         {
899             itemHeightForDisplay +=  rowHeightNonTitle; 
900         }
901         if ([[queueItemToCheck objectForKey:@"Audio2Track"] intValue] > 0)
902         {
903             itemHeightForDisplay +=  rowHeightNonTitle; 
904         }
905         if ([[queueItemToCheck objectForKey:@"Audio3Track"] intValue] > 0)
906         {
907             itemHeightForDisplay +=  rowHeightNonTitle; 
908         }
909         if ([[queueItemToCheck objectForKey:@"Audio4Track"] intValue] > 0)
910         {
911             itemHeightForDisplay +=  rowHeightNonTitle; 
912         }
913         
914         /* add in subtitle lines for each subtitle in the SubtitleList array */
915         itemHeightForDisplay +=  rowHeightNonTitle * [[queueItemToCheck objectForKey:@"SubtitleList"] count];
916         
917         return itemHeightForDisplay;
918         
919     }
920     else
921     {
922         return HB_ROW_HEIGHT_TITLE_ONLY;
923     }
924 }
925
926 - (CGFloat) heightOfDescriptionForWidth:(CGFloat)width
927 {
928     // Try to return the cached value if no changes have happened since the last time
929     //if ((width == fLastDescriptionWidth) && (fLastDescriptionHeight != 0) && !fNeedsDescription)
930        // return fLastDescriptionHeight;
931
932     //if (fNeedsDescription)
933     //    [self updateDescription];
934
935     // Calculate the height
936     //NSRect bounds = [fDescription boundingRectWithSize:NSMakeSize(width, 10000) options:NSStringDrawingUsesLineFragmentOrigin];
937     //fLastDescriptionHeight = bounds.size.height + 6.0; // add some border to bottom
938     //fLastDescriptionWidth = width;
939     return HB_ROW_HEIGHT_FULL_DESCRIPTION;
940
941 /* supposedly another way to do this, in case boundingRectWithSize isn't working
942     NSTextView* tmpView = [[NSTextView alloc] initWithFrame:NSMakeRect(0, 0, width, 1)];
943     [[tmpView textStorage] setAttributedString:aString];
944     [tmpView setHorizontallyResizable:NO];
945     [tmpView setVerticallyResizable:YES];
946 //    [[tmpView textContainer] setHeightTracksTextView: YES];
947 //    [[tmpView textContainer] setContainerSize: NSMakeSize(width, 10000)];
948     [tmpView sizeToFit];
949     float height = [tmpView frame].size.height;
950     [tmpView release];
951     return height;
952 */
953 }
954
955 - (CGFloat) lastDescriptionHeight
956 {
957     return HB_ROW_HEIGHT_FULL_DESCRIPTION;
958 }
959
960 - (id)outlineView:(NSOutlineView *)fOutlineView objectValueForTableColumn:(NSTableColumn *)tableColumn byItem:(id)item
961 {
962     // nb: The "desc" column is currently an HBImageAndTextCell. However, we are longer
963     // using the image portion of the cell so we could switch back to a regular NSTextFieldCell.
964     
965     if ([[tableColumn identifier] isEqualToString:@"desc"])
966     {
967         
968         
969         /* Below should be put into a separate method but I am way too f'ing lazy right now */
970         NSMutableAttributedString * finalString = [[[NSMutableAttributedString alloc] initWithString: @""] autorelease];
971         // Attributes
972         NSMutableParagraphStyle * ps = [[[NSParagraphStyle defaultParagraphStyle] mutableCopy] retain];
973         [ps setHeadIndent: 40.0];
974         [ps setParagraphSpacing: 1.0];
975         [ps setTabStops:[NSArray array]];    // clear all tabs
976         [ps addTabStop: [[[NSTextTab alloc] initWithType: NSLeftTabStopType location: 20.0] autorelease]];
977         
978         
979         NSDictionary* detailAttr = [NSDictionary dictionaryWithObjectsAndKeys:
980                                     [NSFont systemFontOfSize:10.0], NSFontAttributeName,
981                                     ps, NSParagraphStyleAttributeName,
982                                     nil];
983         
984         NSDictionary* detailBoldAttr = [NSDictionary dictionaryWithObjectsAndKeys:
985                                         [NSFont boldSystemFontOfSize:10.0], NSFontAttributeName,
986                                         ps, NSParagraphStyleAttributeName,
987                                         nil];
988         
989         NSDictionary* titleAttr = [NSDictionary dictionaryWithObjectsAndKeys:
990                                    [NSFont systemFontOfSize:[NSFont systemFontSize]], NSFontAttributeName,
991                                    ps, NSParagraphStyleAttributeName,
992                                    nil];
993         
994         NSDictionary* shortHeightAttr = [NSDictionary dictionaryWithObjectsAndKeys:
995                                          [NSFont systemFontOfSize:2.0], NSFontAttributeName,
996                                          nil];
997         
998         /* First line, we should strip the destination path and just show the file name and add the title num and chapters (if any) */
999         //finalDescription = [finalDescription stringByAppendingString:[NSString stringWithFormat:@"Source: %@ Output: %@\n", [item objectForKey:@"SourceName"],[item objectForKey:@"DestinationPath"]]];
1000         NSString * summaryInfo;
1001         
1002         NSString * titleString = [NSString stringWithFormat:@"Title %d", [[item objectForKey:@"TitleNumber"] intValue]];
1003         
1004         NSString * startStopString = @"";
1005         if ([[item objectForKey:@"fEncodeStartStop"] intValue] == 0)
1006         {
1007             /* Start Stop is chapters */
1008             startStopString = ([[item objectForKey:@"ChapterStart"] intValue] == [[item objectForKey:@"ChapterEnd"] intValue]) ?
1009             [NSString stringWithFormat:@"Chapter %d", [[item objectForKey:@"ChapterStart"] intValue]] :
1010             [NSString stringWithFormat:@"Chapters %d through %d", [[item objectForKey:@"ChapterStart"] intValue], [[item objectForKey:@"ChapterEnd"] intValue]];
1011         }
1012         else if ([[item objectForKey:@"fEncodeStartStop"] intValue] == 1)
1013         {
1014             /* Start Stop is seconds */
1015             startStopString = [NSString stringWithFormat:@"Seconds %d through %d", [[item objectForKey:@"StartSeconds"] intValue], [[item objectForKey:@"StartSeconds"] intValue] + [[item objectForKey:@"StopSeconds"] intValue]];
1016         }
1017         else if ([[item objectForKey:@"fEncodeStartStop"] intValue] == 2)
1018         {
1019             /* Start Stop is Frames */
1020             startStopString = [NSString stringWithFormat:@"Frames %d through %d", [[item objectForKey:@"StartFrame"] intValue], [[item objectForKey:@"StartFrame"] intValue] + [[item objectForKey:@"StopFrame"] intValue]];
1021         }
1022         
1023         NSString * passesString = @"";
1024         /* check to see if our first subtitle track is Foreign Language Search, in which case there is an in depth scan */
1025         if ([item objectForKey:@"SubtitleList"] && [[[[item objectForKey:@"SubtitleList"] objectAtIndex:0] objectForKey:@"subtitleSourceTrackNum"] intValue] == 1)
1026         {
1027           passesString = [passesString stringByAppendingString:@"1 Foreign Language Search Pass - "];
1028         }
1029         if ([[item objectForKey:@"VideoTwoPass"] intValue] == 0)
1030         {
1031             passesString = [passesString stringByAppendingString:@"1 Video Pass"];
1032         }
1033         else
1034         {
1035             if ([[item objectForKey:@"VideoTurboTwoPass"] intValue] == 1)
1036             {
1037                 passesString = [passesString stringByAppendingString:@"2 Video Passes First Turbo"];
1038             }
1039             else
1040             {
1041                 passesString = [passesString stringByAppendingString:@"2 Video Passes"];
1042             }
1043         }
1044         
1045         [finalString appendString:[NSString stringWithFormat:@"%@", [item objectForKey:@"SourceName"]] withAttributes:titleAttr];
1046         
1047         /* lets add the output file name to the title string here */
1048         NSString * outputFilenameString = [[item objectForKey:@"DestinationPath"] lastPathComponent];
1049         
1050         summaryInfo = [NSString stringWithFormat: @" (%@, %@, %@) -> %@", titleString, startStopString, passesString, outputFilenameString];
1051         
1052         [finalString appendString:[NSString stringWithFormat:@"%@\n", summaryInfo] withAttributes:detailAttr];  
1053         
1054         // Insert a short-in-height line to put some white space after the title
1055         [finalString appendString:@"\n" withAttributes:shortHeightAttr];
1056         // End of Title Stuff
1057         
1058         /* Second Line  (Preset Name)*/
1059         [finalString appendString: @"Preset: " withAttributes:detailBoldAttr];
1060         [finalString appendString:[NSString stringWithFormat:@"%@\n", [item objectForKey:@"PresetName"]] withAttributes:detailAttr];
1061         
1062         /* Third Line  (Format Summary) */
1063         NSString * audioCodecSummary = @"";
1064         /* Lets also get our audio track detail since we are going through the logic for use later */
1065         NSString * audioDetail1 = @"";
1066         NSString * audioDetail2 = @"";
1067         NSString * audioDetail3 = @"";
1068         NSString * audioDetail4 = @"";
1069         if ([[item objectForKey:@"Audio1Track"] intValue] > 0)
1070         {
1071             audioCodecSummary = [NSString stringWithFormat:@"%@", [item objectForKey:@"Audio1Encoder"]];
1072             audioDetail1 = [NSString stringWithFormat:@"%@ Encoder: %@ Mixdown: %@ SampleRate: %@(khz) Bitrate: %@(kbps)",
1073                             [item objectForKey:@"Audio1TrackDescription"] ,
1074                             [item objectForKey:@"Audio1Encoder"],
1075                             [item objectForKey:@"Audio1Mixdown"] ,
1076                             [item objectForKey:@"Audio1Samplerate"],
1077                             [item objectForKey:@"Audio1Bitrate"]];
1078             
1079             if ([[item objectForKey:@"Audio1TrackDRCSlider"] floatValue] > 0.00)
1080             {
1081                 audioDetail1 = [NSString stringWithFormat:@"%@, DRC: %@",audioDetail1,[item objectForKey:@"Audio1TrackDRCSlider"]];
1082             }
1083             else
1084             {
1085                 audioDetail1 = [NSString stringWithFormat:@"%@, DRC: Off",audioDetail1];
1086             }
1087         }
1088         
1089         if ([[item objectForKey:@"Audio2Track"] intValue] > 0)
1090         {
1091             audioCodecSummary = [NSString stringWithFormat:@"%@, %@",audioCodecSummary ,[item objectForKey:@"Audio2Encoder"]];
1092             audioDetail2 = [NSString stringWithFormat:@"%@ Encoder: %@ Mixdown: %@ SampleRate: %@(khz) Bitrate: %@(kbps)",
1093                             [item objectForKey:@"Audio2TrackDescription"] ,
1094                             [item objectForKey:@"Audio2Encoder"],
1095                             [item objectForKey:@"Audio2Mixdown"] ,
1096                             [item objectForKey:@"Audio2Samplerate"],
1097                             [item objectForKey:@"Audio2Bitrate"]];
1098             
1099             if ([[item objectForKey:@"Audio2TrackDRCSlider"] floatValue] > 0.00)
1100             {
1101                 audioDetail2 = [NSString stringWithFormat:@"%@, DRC: %@",audioDetail2,[item objectForKey:@"Audio2TrackDRCSlider"]];
1102             }
1103             else
1104             {
1105                 audioDetail2 = [NSString stringWithFormat:@"%@, DRC: Off",audioDetail2];
1106             }
1107         }
1108         
1109         if ([[item objectForKey:@"Audio3Track"] intValue] > 0)
1110         {
1111             audioCodecSummary = [NSString stringWithFormat:@"%@, %@",audioCodecSummary ,[item objectForKey:@"Audio3Encoder"]];
1112             audioDetail3 = [NSString stringWithFormat:@"%@ Encoder: %@ Mixdown: %@ SampleRate: %@(khz) Bitrate: %@(kbps)",
1113                             [item objectForKey:@"Audio3TrackDescription"] ,
1114                             [item objectForKey:@"Audio3Encoder"],
1115                             [item objectForKey:@"Audio3Mixdown"] ,
1116                             [item objectForKey:@"Audio3Samplerate"],
1117                             [item objectForKey:@"Audio3Bitrate"]];
1118             
1119             if ([[item objectForKey:@"Audio3TrackDRCSlider"] floatValue] > 0.00)
1120             {
1121                 audioDetail3 = [NSString stringWithFormat:@"%@, DRC: %@",audioDetail3,[item objectForKey:@"Audio3TrackDRCSlider"]];
1122             }
1123             else
1124             {
1125                 audioDetail3 = [NSString stringWithFormat:@"%@, DRC: Off",audioDetail3];
1126             }
1127         }
1128         
1129         if ([[item objectForKey:@"Audio4Track"] intValue] > 0)
1130         {
1131             audioCodecSummary = [NSString stringWithFormat:@"%@, %@",audioCodecSummary ,[item objectForKey:@"Audio3Encoder"]];
1132             audioDetail4 = [NSString stringWithFormat:@"%@ Encoder: %@ Mixdown: %@ SampleRate: %@(khz) Bitrate: %@(kbps)",
1133                             [item objectForKey:@"Audio4TrackDescription"] ,
1134                             [item objectForKey:@"Audio4Encoder"],
1135                             [item objectForKey:@"Audio4Mixdown"] ,
1136                             [item objectForKey:@"Audio4Samplerate"],
1137                             [item objectForKey:@"Audio4Bitrate"]];
1138             
1139             if ([[item objectForKey:@"Audio4TrackDRCSlider"] floatValue] > 0.00)
1140             {
1141                 audioDetail4 = [NSString stringWithFormat:@"%@, DRC: %@",audioDetail4,[item objectForKey:@"Audio4TrackDRCSlider"]];
1142             }
1143             else
1144             {
1145                 audioDetail4 = [NSString stringWithFormat:@"%@, DRC: Off",audioDetail4];
1146             }
1147         }
1148         
1149         NSString * jobFormatInfo;
1150         if ([[item objectForKey:@"ChapterMarkers"] intValue] == 1)
1151             jobFormatInfo = [NSString stringWithFormat:@"%@ Container, %@ Video  %@ Audio, Chapter Markers\n", [item objectForKey:@"FileFormat"], [item objectForKey:@"VideoEncoder"], audioCodecSummary];
1152         else
1153             jobFormatInfo = [NSString stringWithFormat:@"%@ Container, %@ Video  %@ Audio\n", [item objectForKey:@"FileFormat"], [item objectForKey:@"VideoEncoder"], audioCodecSummary];
1154         
1155         
1156         [finalString appendString: @"Format: " withAttributes:detailBoldAttr];
1157         [finalString appendString: jobFormatInfo withAttributes:detailAttr];
1158         
1159         /* Optional String for mp4 options */
1160         if ([[item objectForKey:@"FileFormat"] isEqualToString: @"MP4 file"])
1161         {
1162             NSString * MP4Opts = @"";
1163             BOOL mp4OptsPresent = NO;
1164             if( [[item objectForKey:@"Mp4LargeFile"] intValue] == 1)
1165             {
1166                 mp4OptsPresent = YES;
1167                 MP4Opts = [MP4Opts stringByAppendingString:@" - Large file size"];
1168             }
1169             if( [[item objectForKey:@"Mp4HttpOptimize"] intValue] == 1)
1170             {
1171                 mp4OptsPresent = YES;
1172                 MP4Opts = [MP4Opts stringByAppendingString:@" - Web optimized"];
1173             }
1174             
1175             if( [[item objectForKey:@"Mp4iPodCompatible"] intValue] == 1)
1176             {
1177                 mp4OptsPresent = YES;
1178                 MP4Opts = [MP4Opts stringByAppendingString:@" - iPod 5G support "];
1179             }
1180             if (mp4OptsPresent == YES)
1181             {
1182                 [finalString appendString: @"MP4 Options: " withAttributes:detailBoldAttr];
1183                 [finalString appendString: MP4Opts withAttributes:detailAttr];
1184                 [finalString appendString:@"\n" withAttributes:detailAttr];
1185             }
1186         }
1187         
1188         /* Fourth Line (Destination Path)*/
1189         [finalString appendString: @"Destination: " withAttributes:detailBoldAttr];
1190         [finalString appendString: [item objectForKey:@"DestinationPath"] withAttributes:detailAttr];
1191         [finalString appendString:@"\n" withAttributes:detailAttr];
1192         
1193         /* Fifth Line Picture Details*/
1194         NSString * pictureInfo;
1195         pictureInfo = [NSString stringWithFormat:@"%@", [item objectForKey:@"PictureSizingSummary"]];
1196         if ([[item objectForKey:@"PictureKeepRatio"] intValue] == 1)
1197         {
1198             pictureInfo = [pictureInfo stringByAppendingString:@" Keep Aspect Ratio"];
1199         }
1200         
1201         if ([[item objectForKey:@"VideoGrayScale"] intValue] == 1)
1202         {
1203             pictureInfo = [pictureInfo stringByAppendingString:@", Grayscale"];
1204         }
1205         
1206         [finalString appendString: @"Picture: " withAttributes:detailBoldAttr];
1207         [finalString appendString: pictureInfo withAttributes:detailAttr];
1208         [finalString appendString:@"\n" withAttributes:detailAttr];
1209         
1210         /* Optional String for Picture Filters */
1211         
1212         NSString * pictureFilters = @"";
1213         BOOL pictureFiltersPresent = NO;
1214         
1215         if( [[item objectForKey:@"PictureDetelecine"] intValue] == 1)
1216         {
1217             pictureFiltersPresent = YES;
1218             pictureFilters = [pictureFilters stringByAppendingString:[NSString stringWithFormat:@" - Detelecine (%@)",[item objectForKey:@"PictureDetelecineCustom"]]];
1219         }
1220         else if( [[item objectForKey:@"PictureDetelecine"] intValue] == 2)
1221         {
1222             pictureFiltersPresent = YES;
1223             pictureFilters = [pictureFilters stringByAppendingString:@" - Detelecine (Default)"];
1224         }
1225         
1226         if( [[item objectForKey:@"PictureDecombDeinterlace"] intValue] == 1)
1227         {
1228             if ([[item objectForKey:@"PictureDecomb"] intValue] != 0)
1229             {
1230                 pictureFiltersPresent = YES;
1231                 if( [[item objectForKey:@"PictureDecomb"] intValue] == 1)
1232                 {
1233                     pictureFiltersPresent = YES;
1234                     pictureFilters = [pictureFilters stringByAppendingString:[NSString stringWithFormat:@" - Decomb (%@)",[item objectForKey:@"PictureDecombCustom"]]];
1235                 }
1236                 else if( [[item objectForKey:@"PictureDecomb"] intValue] == 2)
1237                 {
1238                     pictureFiltersPresent = YES;
1239                     pictureFilters = [pictureFilters stringByAppendingString:@" - Decomb (Default)"];
1240                 }
1241             }
1242         }
1243         else
1244         {
1245             if ([[item objectForKey:@"PictureDeinterlace"] intValue] != 0)
1246             {
1247                 pictureFiltersPresent = YES;
1248                 if ([[item objectForKey:@"PictureDeinterlace"] intValue] == 1)
1249                 {
1250                     pictureFilters = [pictureFilters stringByAppendingString:[NSString stringWithFormat:@" - Deinterlace (%@)",[item objectForKey:@"PictureDeinterlaceCustom"]]];            
1251                 }
1252                 else if ([[item objectForKey:@"PictureDeinterlace"] intValue] == 2)
1253                 {
1254                     pictureFilters = [pictureFilters stringByAppendingString:@" - Deinterlace (Fast)"];
1255                 }
1256                 else if ([[item objectForKey:@"PictureDeinterlace"] intValue] == 3)
1257                 {
1258                     pictureFilters = [pictureFilters stringByAppendingString:@" - Deinterlace (Slow)"];           
1259                 }
1260                 else if ([[item objectForKey:@"PictureDeinterlace"] intValue] == 4)
1261                 {
1262                     pictureFilters = [pictureFilters stringByAppendingString:@" - Deinterlace (Slower)"];            
1263                 }
1264                 
1265             }
1266         }
1267         if ([[item objectForKey:@"PictureDenoise"] intValue] != 0)
1268         {
1269             pictureFiltersPresent = YES;
1270             if ([[item objectForKey:@"PictureDenoise"] intValue] == 1)
1271             {
1272                 pictureFilters = [pictureFilters stringByAppendingString:[NSString stringWithFormat:@" - Denoise (%@)",[item objectForKey:@"PictureDenoiseCustom"]]];            
1273             }
1274             else if ([[item objectForKey:@"PictureDenoise"] intValue] == 2)
1275             {
1276                 pictureFilters = [pictureFilters stringByAppendingString:@" - Denoise (Weak)"];
1277             }
1278             else if ([[item objectForKey:@"PictureDenoise"] intValue] == 3)
1279             {
1280                 pictureFilters = [pictureFilters stringByAppendingString:@" - Denoise (Medium)"];           
1281             }
1282             else if ([[item objectForKey:@"PictureDenoise"] intValue] == 4)
1283             {
1284                 pictureFilters = [pictureFilters stringByAppendingString:@" - Denoise (Strong)"];            
1285             }
1286             
1287         }
1288         if ([[item objectForKey:@"PictureDeblock"] intValue] != 0)
1289         {
1290             pictureFiltersPresent = YES;
1291             pictureFilters = [pictureFilters stringByAppendingString: [NSString stringWithFormat:@" - Deblock (pp7) (%d)",[[item objectForKey:@"PictureDeblock"] intValue]]];
1292         }
1293         
1294         if ([[item objectForKey:@"VideoGrayScale"] intValue] == 1)
1295         {
1296             pictureFiltersPresent = YES;
1297             pictureFilters = [pictureFilters stringByAppendingString:@" - Grayscale"];
1298         }
1299         
1300         if (pictureFiltersPresent == YES)
1301         {
1302             [finalString appendString: @"Filters: " withAttributes:detailBoldAttr];
1303             [finalString appendString: pictureFilters withAttributes:detailAttr];
1304             [finalString appendString:@"\n" withAttributes:detailAttr];
1305         }
1306         
1307         /* Sixth Line Video Details*/
1308         NSString * videoInfo;
1309         videoInfo = [NSString stringWithFormat:@"Encoder: %@", [item objectForKey:@"VideoEncoder"]];
1310         
1311         /* for framerate look to see if we are using vfr detelecine */
1312         if ([[item objectForKey:@"JobIndexVideoFramerate"] intValue] == 0)
1313         {
1314             if ([[item objectForKey:@"PictureDetelecine"] intValue] == 1)
1315             {
1316                 /* we are using same as source with vfr detelecine */
1317                 videoInfo = [NSString stringWithFormat:@"%@ Framerate: Same as source (vfr detelecine)", videoInfo];
1318             }
1319             else
1320             {
1321                 /* we are using a variable framerate without dropping frames */
1322                 videoInfo = [NSString stringWithFormat:@"%@ Framerate: Same as source (variable)", videoInfo];
1323             }
1324         }
1325         else
1326         {
1327             /* we have a specified, constant framerate */
1328             videoInfo = [NSString stringWithFormat:@"%@ Framerate: %@ (constant framerate)", videoInfo ,[item objectForKey:@"VideoFramerate"]];
1329         }
1330         
1331         if ([[item objectForKey:@"VideoQualityType"] intValue] == 0)// Target Size MB
1332         {
1333             videoInfo = [NSString stringWithFormat:@"%@ Target Size: %@(MB) (%d(kbps) abr)", videoInfo ,[item objectForKey:@"VideoTargetSize"],[[item objectForKey:@"VideoAvgBitrate"] intValue]];
1334         }
1335         else if ([[item objectForKey:@"VideoQualityType"] intValue] == 1) // ABR
1336         {
1337             videoInfo = [NSString stringWithFormat:@"%@ Bitrate: %d(kbps)", videoInfo ,[[item objectForKey:@"VideoAvgBitrate"] intValue]];
1338         }
1339         else // CRF
1340         {
1341             videoInfo = [NSString stringWithFormat:@"%@ Constant Quality: %.2f", videoInfo ,[[item objectForKey:@"VideoQualitySlider"] floatValue]];
1342         }
1343         
1344         [finalString appendString: @"Video: " withAttributes:detailBoldAttr];
1345         [finalString appendString: videoInfo withAttributes:detailAttr];
1346         [finalString appendString:@"\n" withAttributes:detailAttr];
1347         
1348         if ([[item objectForKey:@"VideoEncoder"] isEqualToString: @"H.264 (x264)"])
1349         {
1350             [finalString appendString: @"x264 Options: " withAttributes:detailBoldAttr];
1351             [finalString appendString: [item objectForKey:@"x264Option"] withAttributes:detailAttr];
1352             [finalString appendString:@"\n" withAttributes:detailAttr];
1353         }
1354         
1355         
1356         
1357         /* Seventh Line Audio Details*/
1358         if ([audioDetail1 length] != 0)
1359         {
1360             [finalString appendString: @"Audio Track 1: " withAttributes:detailBoldAttr];
1361             [finalString appendString: audioDetail1 withAttributes:detailAttr];
1362             [finalString appendString:@"\n" withAttributes:detailAttr];
1363         }
1364         
1365         if ([audioDetail2 length] != 0)
1366         {
1367             [finalString appendString: @"Audio Track 2: " withAttributes:detailBoldAttr];
1368             [finalString appendString: audioDetail2 withAttributes:detailAttr];
1369             [finalString appendString:@"\n" withAttributes:detailAttr];
1370         }
1371         
1372         if ([audioDetail3 length] != 0)
1373         {
1374             [finalString appendString: @"Audio Track 3: " withAttributes:detailBoldAttr];
1375             [finalString appendString: audioDetail3 withAttributes:detailAttr];
1376             [finalString appendString:@"\n" withAttributes:detailAttr];
1377         }
1378         
1379         if ([audioDetail4 length] != 0)
1380         {
1381             [finalString appendString: @"Audio Track 4: " withAttributes:detailBoldAttr];
1382             [finalString appendString: audioDetail4 withAttributes:detailAttr];
1383             [finalString appendString:@"\n" withAttributes:detailAttr];
1384         }
1385         /* Eighth Line Subtitle Details */
1386         
1387         int i = 0;
1388         NSEnumerator *enumerator = [[item objectForKey:@"SubtitleList"] objectEnumerator];
1389         id tempObject;
1390         while (tempObject = [enumerator nextObject])
1391         {
1392             /* since the subtitleSourceTrackNum 0 is "None" in our array of the subtitle popups,
1393              * we want to ignore it for display as well as encoding.
1394              */
1395             if ([[tempObject objectForKey:@"subtitleSourceTrackNum"] intValue] > 0)
1396             { 
1397                 /* remember that index 0 of Subtitles can contain "Foreign Audio Search*/
1398                 [finalString appendString: @"Subtitle: " withAttributes:detailBoldAttr];
1399                 [finalString appendString: [tempObject objectForKey:@"subtitleSourceTrackName"] withAttributes:detailAttr];
1400                 if ([[tempObject objectForKey:@"subtitleTrackForced"] intValue] == 1)
1401                 {
1402                     [finalString appendString: @" - Forced Only" withAttributes:detailAttr];
1403                 }
1404                 if ([[tempObject objectForKey:@"subtitleTrackBurned"] intValue] == 1)
1405                 {
1406                     [finalString appendString: @" - Burned In" withAttributes:detailAttr];
1407                 }
1408                 if ([[tempObject objectForKey:@"subtitleTrackDefault"] intValue] == 1)
1409                 {
1410                     [finalString appendString: @" - Default" withAttributes:detailAttr];
1411                 }
1412                 [finalString appendString:@"\n" withAttributes:detailAttr];
1413             }
1414             i++;
1415         }      
1416         
1417         return finalString;
1418     }
1419     else if ([[tableColumn identifier] isEqualToString:@"icon"])
1420     {
1421         if ([[item objectForKey:@"Status"] intValue] == 0)
1422         {
1423             return [NSImage imageNamed:@"EncodeComplete"];
1424         }
1425         else if ([[item objectForKey:@"Status"] intValue] == 1)
1426         {
1427             return [NSImage imageNamed: [NSString stringWithFormat: @"EncodeWorking%d", fAnimationIndex]];
1428         }
1429         else if ([[item objectForKey:@"Status"] intValue] == 3)
1430         {
1431             return [NSImage imageNamed:@"EncodeCanceled"];
1432         }
1433         else
1434         {
1435             return [NSImage imageNamed:@"JobSmall"];
1436         }
1437         
1438     }
1439     else
1440     {
1441         return @"";
1442     }
1443 }
1444
1445 - (void)outlineView:(NSOutlineView *)outlineView willDisplayCell:(id)cell forTableColumn:(NSTableColumn *)tableColumn item:(id)item
1446 {
1447     if ([[tableColumn identifier] isEqualToString:@"desc"])
1448     {
1449
1450
1451         // nb: The "desc" column is currently an HBImageAndTextCell. However, we are longer
1452         // using the image portion of the cell so we could switch back to a regular NSTextFieldCell.
1453
1454         // Set the image here since the value returned from outlineView:objectValueForTableColumn: didn't specify the image part
1455         [cell setImage:nil];
1456     }
1457     else if ([[tableColumn identifier] isEqualToString:@"action"])
1458     {
1459         [cell setEnabled: YES];
1460         BOOL highlighted = [outlineView isRowSelected:[outlineView rowForItem: item]] && [[outlineView window] isKeyWindow] && ([[outlineView window] firstResponder] == outlineView);
1461         if ([[item objectForKey:@"Status"] intValue] == 0)
1462         {
1463             [cell setAction: @selector(revealSelectedQueueItem:)];
1464             if (highlighted)
1465             {
1466                 [cell setImage:[NSImage imageNamed:@"RevealHighlight"]];
1467                 [cell setAlternateImage:[NSImage imageNamed:@"RevealHighlightPressed"]];
1468             }
1469             else
1470                 [cell setImage:[NSImage imageNamed:@"Reveal"]];
1471         }
1472         else
1473         {
1474             [cell setAction: @selector(removeSelectedQueueItem:)];
1475             if (highlighted)
1476             {
1477                 [cell setImage:[NSImage imageNamed:@"DeleteHighlight"]];
1478                 [cell setAlternateImage:[NSImage imageNamed:@"DeleteHighlightPressed"]];
1479             }
1480             else
1481                 [cell setImage:[NSImage imageNamed:@"Delete"]];
1482         }
1483     }
1484 }
1485
1486 - (void)outlineView:(NSOutlineView *)outlineView willDisplayOutlineCell:(id)cell forTableColumn:(NSTableColumn *)tableColumn item:(id)item
1487 {
1488     // By default, the discolsure image gets centered vertically in the cell. We want
1489     // always at the top.
1490     if ([outlineView isItemExpanded: item])
1491         [cell setImagePosition: NSImageAbove];
1492     else
1493         [cell setImagePosition: NSImageOnly];
1494 }
1495
1496 #pragma mark -
1497 #pragma mark NSOutlineView delegate (dragging related)
1498
1499 //------------------------------------------------------------------------------------
1500 // NSTableView delegate
1501 //------------------------------------------------------------------------------------
1502
1503
1504 - (BOOL)outlineView:(NSOutlineView *)outlineView writeItems:(NSArray *)items toPasteboard:(NSPasteboard *)pboard
1505 {
1506     // Dragging is only allowed of the pending items.
1507     if ([[[items objectAtIndex:0] objectForKey:@"Status"] integerValue] != 2) // 2 is pending
1508     {
1509         return NO;
1510     }
1511     
1512     // Don't retain since this is just holding temporaral drag information, and it is
1513     //only used during a drag!  We could put this in the pboard actually.
1514     fDraggedNodes = items;
1515     
1516     // Provide data for our custom type, and simple NSStrings.
1517     [pboard declareTypes:[NSArray arrayWithObjects: DragDropSimplePboardType, nil] owner:self];
1518     
1519     // the actual data doesn't matter since DragDropSimplePboardType drags aren't recognized by anyone but us!.
1520     [pboard setData:[NSData data] forType:DragDropSimplePboardType];
1521     
1522     return YES;
1523 }
1524
1525
1526 /* This method is used to validate the drops. */
1527 - (NSDragOperation)outlineView:(NSOutlineView *)outlineView validateDrop:(id <NSDraggingInfo>)info proposedItem:(id)item proposedChildIndex:(NSInteger)index
1528 {
1529     // Don't allow dropping ONTO an item since they can't really contain any children.
1530     BOOL isOnDropTypeProposal = index == NSOutlineViewDropOnItemIndex;
1531     if (isOnDropTypeProposal)
1532     {
1533         return NSDragOperationNone;
1534     }
1535     
1536     // Don't allow dropping INTO an item since they can't really contain any children.
1537     if (item != nil)
1538     {
1539         index = [fOutlineView rowForItem: item] + 1;
1540         item = nil;
1541     }
1542     
1543     // NOTE: Should we allow dropping a pending job *above* the
1544     // finished or already encoded jobs ?
1545     // We do not let the user drop a pending job before or *above*
1546     // already finished or currently encoding jobs.
1547     if (index <= fEncodingQueueItem)
1548     {
1549         return NSDragOperationNone;
1550         index = MAX (index, fEncodingQueueItem);
1551         }
1552     
1553     [outlineView setDropItem:item dropChildIndex:index];
1554     return NSDragOperationGeneric;
1555 }
1556
1557 - (BOOL)outlineView:(NSOutlineView *)outlineView acceptDrop:(id <NSDraggingInfo>)info item:(id)item childIndex:(NSInteger)index
1558 {
1559     NSMutableIndexSet *moveItems = [NSMutableIndexSet indexSet];
1560
1561     for( id obj in fDraggedNodes )
1562         [moveItems addIndex:[fJobGroups indexOfObject:obj]];
1563
1564     // Successful drop, we use moveObjectsInQueueArray:... in fHBController
1565     // to properly rearrange the queue array, save it to plist and then send it back here.
1566     // since Controller.mm is handling all queue array manipulation.
1567     // We *could do this here, but I think we are better served keeping that code together.
1568     [fHBController moveObjectsInQueueArray:fJobGroups fromIndexes:moveItems toIndex: index];
1569     return YES;
1570 }
1571
1572
1573 @end