OSDN Git Service

WinGui:
[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.m0k.org/>.
5     It may be used under the terms of the GNU General Public License. */
6
7 #include "HBQueueController.h"
8 #include "Controller.h"
9 #import "HBImageAndTextCell.h"
10
11 #define HB_QUEUE_DRAGGING 0        // <--- NOT COMPLETELY FUNCTIONAL YET
12 #define HBQueueDataType            @"HBQueueDataType"
13
14 // UNI_QUEUE turns on the feature where the first item in the queue NSTableView is the
15 // current job followed by the jobs in hblib's queue. In this scheme, fCurrentJobPane
16 // disappers.
17 #define HB_UNI_QUEUE 0             // <--- NOT COMPLETELY FUNCTIONAL YET
18
19 #define HB_ROW_HEIGHT_TITLE_ONLY           17.0
20 #define HB_ROW_HEIGHT_TITLE_WITH_SUMMARY   45.0
21 #define HB_ROW_HEIGHT_INDEPTH_PASS         17.0
22 #define HB_ROW_HEIGHT_1ST_PASS             54.0
23 #define HB_ROW_HEIGHT_2ND_PASS             67.0
24
25 //#define HB_ROW_HEIGHT_NO_DETAIL    17.0
26 //#define HB_ROW_HEIGHT_ACTIVE_JOB   60.0
27
28 //------------------------------------------------------------------------------------
29 #pragma mark Job group functions
30 //------------------------------------------------------------------------------------
31 // These could be part of hblib if we think hblib should have knowledge of groups.
32 // For now, I see groups as a metaphor that HBQueueController provides.
33
34 /**
35  * Returns the number of jobs groups in the queue.
36  * @param h Handle to hb_handle_t.
37  * @return Number of job groups.
38  */
39 static int hb_group_count(hb_handle_t * h)    
40 {
41     hb_job_t * job;
42     int count = 0;
43     int index = 0;
44     while( ( job = hb_job( h, index++ ) ) )
45     {
46         if (job->sequence_id == 0)
47             count++;
48     }
49     return count;
50 }
51
52 /**
53  * Returns handle to the first job in the i-th group within the job list.
54  * @param h Handle to hb_handle_t.
55  * @param i Index of group.
56  * @returns Handle to hb_job_t of desired job.
57  */
58 static hb_job_t * hb_group(hb_handle_t * h, int i)    
59 {
60     hb_job_t * job;
61     int count = 0;
62     int index = 0;
63     while( ( job = hb_job( h, index++ ) ) )
64     {
65         if (job->sequence_id == 0)
66         {
67             if (count == i)
68                 return job;
69             count++;
70         }
71     }
72     return NULL;
73 }
74
75 /**
76  * Removes a groups of jobs from the job list.
77  * @param h Handle to hb_handle_t.
78  * @param job Handle to the first job in the group.
79  */
80 static void hb_rem_group( hb_handle_t * h, hb_job_t * job )
81 {
82     // Find job in list
83     hb_job_t * j;
84     int index = 0;
85     while( ( j = hb_job( h, index ) ) )
86     {
87         if (j == job)
88         {
89             // Delete this job plus the following ones in the sequence
90             hb_rem( h, job );
91             while( ( j = hb_job( h, index ) ) && (j->sequence_id != 0) )
92                 hb_rem( h, j );
93             return;
94         }
95         else
96             index++;
97     }
98 }
99
100 #if HB_OUTLINE_QUEUE
101 /**
102  * Returns handle to the next job after the given job.
103  * @param h Handle to hb_handle_t.
104  * @param job Handle to the a job in the group.
105  * @returns Handle to hb_job_t of desired job or NULL if no such job.
106  */
107 static hb_job_t * hb_next_job( hb_handle_t * h, hb_job_t * job )
108 {
109     hb_job_t * j = NULL;
110     int index = 0;
111     while( ( j = hb_job( h, index++ ) ) )
112     {
113         if (j == job)
114             return hb_job( h, index );
115     }
116     return NULL;
117 }
118 #endif
119
120 #pragma mark -
121 //------------------------------------------------------------------------------------
122 // HBJob
123 //------------------------------------------------------------------------------------
124
125 #if HB_OUTLINE_QUEUE
126
127 @interface HBJob : NSObject
128 {
129     hb_job_t                *fJob;
130 }
131 + (HBJob*) jobWithJob: (hb_job_t *) job;
132 - (id) initWithJob: (hb_job_t *) job;
133 - (hb_job_t *) job;
134 @end
135
136 @implementation HBJob
137 + (HBJob*) jobWithJob: (hb_job_t *) job
138 {
139     return [[[HBJob alloc] initWithJob:job] autorelease];
140 }
141
142 - (id) initWithJob: (hb_job_t *) job
143 {
144     if (self = [super init])
145     {
146         // job is not owned by HBJob. It does not get dealloacted when HBJob is released.
147         fJob = job;
148     }
149     return self; 
150 }
151
152 - (hb_job_t*) job
153 {
154     return fJob;
155 }
156
157 @end
158
159
160
161
162
163 #endif // HB_OUTLINE_QUEUE
164
165 #pragma mark -
166
167 // Toolbar identifiers
168 static NSString*    HBQueueToolbar                            = @"HBQueueToolbar1";
169 static NSString*    HBQueueStartCancelToolbarIdentifier       = @"HBQueueStartCancelToolbarIdentifier";
170 static NSString*    HBQueuePauseResumeToolbarIdentifier       = @"HBQueuePauseResumeToolbarIdentifier";
171 #if !HB_OUTLINE_QUEUE
172 static NSString*    HBShowDetailToolbarIdentifier             = @"HBQueueShowDetailToolbarIdentifier";
173 static NSString*    HBShowGroupsToolbarIdentifier             = @"HBQueueShowGroupsToolbarIdentifier";
174 #endif
175
176
177 @implementation HBQueueController
178
179 //------------------------------------------------------------------------------------
180 // init
181 //------------------------------------------------------------------------------------
182 - (id)init
183 {
184     if (self = [super init])
185     {
186         // Our defaults
187         [[NSUserDefaults standardUserDefaults] registerDefaults:[NSDictionary dictionaryWithObjectsAndKeys:
188             @"NO",      @"QueueWindowIsOpen",
189             @"NO",      @"QueueShowsDetail",
190             @"YES",     @"QueueShowsJobsAsGroups",
191             nil]];
192
193 #if HB_OUTLINE_QUEUE
194         fShowsDetail = YES;
195         fShowsJobsAsGroups = YES;
196 #else
197         fShowsDetail = [[NSUserDefaults standardUserDefaults] boolForKey:@"QueueShowsDetail"];
198         fShowsJobsAsGroups = [[NSUserDefaults standardUserDefaults] boolForKey:@"QueueShowsJobsAsGroups"];
199 #endif
200
201 #if HB_OUTLINE_QUEUE
202         fEncodes = [[NSMutableArray arrayWithCapacity:0] retain];
203 #endif
204     }
205     return self; 
206 }
207
208 //------------------------------------------------------------------------------------
209 // dealloc
210 //------------------------------------------------------------------------------------
211 - (void)dealloc
212 {
213     [fAnimation release];
214     
215     // clear the delegate so that windowWillClose is not attempted
216     if ([fQueueWindow delegate] == self)
217         [fQueueWindow setDelegate:nil];
218     
219 #if HB_OUTLINE_QUEUE
220     [fEncodes release];
221     [fSavedExpandedItems release];
222 #endif
223
224     [super dealloc];
225 }
226
227 //------------------------------------------------------------------------------------
228 // Receive HB handle
229 //------------------------------------------------------------------------------------
230 - (void)setHandle: (hb_handle_t *)handle
231 {
232     fHandle = handle;
233 }
234
235 //------------------------------------------------------------------------------------
236 // Receive HBController
237 //------------------------------------------------------------------------------------
238 - (void)setHBController: (HBController *)controller
239 {
240     fHBController = controller;
241 }
242
243 //------------------------------------------------------------------------------------
244 // Displays and brings the queue window to the front
245 //------------------------------------------------------------------------------------
246 - (IBAction) showQueueWindow: (id)sender
247 {
248     if (!fQueueWindow)
249     {
250         BOOL loadSucceeded = [NSBundle loadNibNamed:@"Queue" owner:self] && fQueueWindow;
251         NSAssert(loadSucceeded, @"Could not open Queue nib file");
252     }
253
254     [self updateQueueUI];
255     [self updateCurrentJobUI];
256
257     [fQueueWindow makeKeyAndOrderFront: self];
258
259     [[NSUserDefaults standardUserDefaults] setBool:YES forKey:@"QueueWindowIsOpen"];
260 }
261 //------------------------------------------------------------------------------------
262 // Show or hide the current job pane (fCurrentJobPane).
263 //------------------------------------------------------------------------------------
264 - (void) showCurrentJobPane: (BOOL)showPane
265 {
266     if (showPane != fCurrentJobHidden)
267         return;
268     
269     // Things to keep in mind:
270     // - When the current job pane is shown, it occupies the upper portion of the
271     //   window with the queue occupying the bottom portion of the window.
272     // - When the current job pane is hidden, it slides up and out of view.
273     //   NSView setHidden is NOT used. The queue pane is resized to occupy the full
274     //   window.
275     
276     NSRect windowFrame = [[fCurrentJobPane superview] frame];
277     NSRect queueFrame, jobFrame;
278     if (showPane)
279         NSDivideRect(windowFrame, &jobFrame, &queueFrame, NSHeight([fCurrentJobPane frame]), NSMaxYEdge);
280     else
281     {
282         queueFrame = windowFrame;
283         jobFrame = [fCurrentJobPane frame];
284         jobFrame.origin.y = NSHeight(windowFrame);
285     }
286     
287     // Move fCurrentJobPane
288     NSDictionary * dict1 = [NSDictionary dictionaryWithObjectsAndKeys:
289         fCurrentJobPane, NSViewAnimationTargetKey,
290         [NSValue valueWithRect:jobFrame], NSViewAnimationEndFrameKey,
291         nil];
292
293     // Resize fQueuePane
294     NSDictionary * dict2 = [NSDictionary dictionaryWithObjectsAndKeys:
295         fQueuePane, NSViewAnimationTargetKey,
296         [NSValue valueWithRect:queueFrame], NSViewAnimationEndFrameKey,
297         nil];
298
299     if (!fAnimation)
300         fAnimation = [[NSViewAnimation alloc] initWithViewAnimations:nil];
301
302     [fAnimation setViewAnimations:[NSArray arrayWithObjects:dict1, dict2, nil]];
303     [fAnimation setDuration:0.25];
304     [fAnimation setAnimationBlockingMode:NSAnimationBlocking]; // prevent user from resizing the window during an animation
305     [fAnimation startAnimation];
306     fCurrentJobHidden = !showPane;
307 }
308
309 //------------------------------------------------------------------------------------
310 // Enables or disables the display of detail information for each job.
311 //------------------------------------------------------------------------------------
312 - (void)setShowsDetail: (BOOL)showsDetail
313 {
314 #if HB_OUTLINE_QUEUE
315     return; // Can't modify this value. It's always YES.
316 #else
317     fShowsDetail = showsDetail;
318     
319     [[NSUserDefaults standardUserDefaults] setBool:showsDetail forKey:@"QueueShowsDetail"];
320     [[NSUserDefaults standardUserDefaults] synchronize];
321
322     [fTaskView setRowHeight:showsDetail ? HB_ROW_HEIGHT_DETAIL : HB_ROW_HEIGHT_NO_DETAIL];
323   #if HB_UNI_QUEUE
324     if (hb_count(fHandle))
325         [fTaskView noteHeightOfRowsWithIndexesChanged:[NSIndexSet indexSetWithIndex:0]];
326   #endif
327     if ([fTaskView selectedRow] != -1)
328         [fTaskView scrollRowToVisible:[fTaskView selectedRow]];
329 #endif
330 }
331
332 //------------------------------------------------------------------------------------
333 // Enables or disables the grouping of job passes into one item in the UI.
334 //------------------------------------------------------------------------------------
335 - (void)setShowsJobsAsGroups: (BOOL)showsGroups
336 {
337 #if HB_OUTLINE_QUEUE
338     return; // Can't modify this value. It's always YES.
339 #else
340     fShowsJobsAsGroups = showsGroups;
341     
342     [[NSUserDefaults standardUserDefaults] setBool:showsGroups forKey:@"QueueShowsJobsAsGroups"];
343     [[NSUserDefaults standardUserDefaults] synchronize];
344
345     [self updateQueueUI];
346     if ([fTaskView selectedRow] != -1)
347         [fTaskView scrollRowToVisible:[fTaskView selectedRow]];
348 #endif
349 }
350
351 //------------------------------------------------------------------------------------
352 // Returns a 16x16 image that represents a job pass.
353 //------------------------------------------------------------------------------------
354 - (NSImage *)smallImageForPass: (int)pass
355 {
356     switch (pass)
357     {
358         case -1: return [NSImage imageNamed: @"JobPassSubtitleSmall"];
359         case  0: return [NSImage imageNamed: @"JobPassFirstSmall"];
360         case  1: return [NSImage imageNamed: @"JobPassFirstSmall"];
361         case  2: return [NSImage imageNamed: @"JobPassSecondSmall"];
362         default: return [NSImage imageNamed: @"JobPassUnknownSmall"];
363     }
364 }
365
366 //------------------------------------------------------------------------------------
367 // Returns a 64x64 image that represents a job pass.
368 //------------------------------------------------------------------------------------
369 - (NSImage *)largeImageForPass: (int)pass
370 {
371     switch (pass)
372     {
373         case -1: return [NSImage imageNamed: @"JobPassSubtitleLarge"];
374         case  0: return [NSImage imageNamed: @"JobPassFirstLarge"];
375         case  1: return [NSImage imageNamed: @"JobPassFirstLarge"];
376         case  2: return [NSImage imageNamed: @"JobPassSecondLarge"];
377         default: return [NSImage imageNamed: @"JobPassUnknownLarge"];
378     }
379 }
380
381 #if HB_OUTLINE_QUEUE
382 //------------------------------------------------------------------------------------
383 // Rebuilds the contents of fEncodes which is a array of encodes and HBJobs.
384 //------------------------------------------------------------------------------------
385 - (void)rebuildEncodes
386 {
387     [fEncodes autorelease];
388     fEncodes = [[NSMutableArray arrayWithCapacity:0] retain];
389
390     NSMutableArray * aJobGroup = [NSMutableArray arrayWithCapacity:0];
391
392     hb_job_t * nextJob = hb_group( fHandle, 0 );
393     while( nextJob )
394     {
395         if (nextJob->sequence_id == 0)
396         {
397             // Encountered a new group. Add the current one to fEncodes and then start a new one.
398             if ([aJobGroup count] > 0)
399             {
400                 [fEncodes addObject:aJobGroup];
401                 aJobGroup = [NSMutableArray arrayWithCapacity:0];
402             }
403         }
404         [aJobGroup addObject: [HBJob jobWithJob:nextJob]];
405         nextJob = hb_next_job (fHandle, nextJob);
406     }
407     if ([aJobGroup count] > 0)
408     {
409         [fEncodes addObject:aJobGroup];
410     }
411 }
412 #endif
413
414 #if HB_OUTLINE_QUEUE
415 //------------------------------------------------------------------------------------
416 // Saves the state of the items that are currently expanded. Calling restoreOutlineViewState
417 // will restore the state of all items to match what was saved by saveOutlineViewState.
418 //------------------------------------------------------------------------------------
419 - (void) saveOutlineViewState
420 {
421     if (!fSavedExpandedItems)
422         fSavedExpandedItems = [[NSMutableIndexSet alloc] init];
423     else
424         [fSavedExpandedItems removeAllIndexes];
425     
426     // NB: This code is stuffing the address of each job into an index set. While it
427     // works 99.9% of the time, it's not the ideal solution. We need unique ids in
428     // each job, possibly using the existing sequence_id field. Could use the high
429     // word as a unique encode id and the low word the sequence number.
430     
431     id anEncode;
432     NSEnumerator * e = [fEncodes objectEnumerator];
433     while ( (anEncode = [e nextObject]) )
434     {
435         if ([fOutlineView isItemExpanded: anEncode])
436             [fSavedExpandedItems addIndex: (unsigned int)[[anEncode objectAtIndex:0] job]];
437     }
438     
439     // Save the selection also. This is really UGLY code. Since I have to rebuild the
440     // entire outline hierachy every time hblib changes its job list, there's no easy
441     // way for me to remember the selection state. So here's the strategy. If a *group*
442     // object is selected, then the first hb_job_t item in the group is saved in
443     // fSavedSelectedItem. If a job is selected, then its hb_job_t item is saved. To
444     // distinguish between a group being selected vs an actual job, the high bit of
445     // fSavedSelectedItem is set when it refers to a job object. I know, not pretty.
446     // This could go away if I'd save a unique id in each job object.
447
448     int selection = [fOutlineView selectedRow];
449     if (selection == -1)
450         fSavedSelectedItem = 0;
451     else
452     {
453         id obj = [fOutlineView itemAtRow: selection];
454         if ([obj isKindOfClass:[HBJob class]])
455             fSavedSelectedItem = (unsigned int)[obj job] | 0x80000000;  // set high bit!
456         else
457             fSavedSelectedItem = (unsigned int)[[obj objectAtIndex:0] job];
458     }
459     
460 }
461 #endif
462
463 #if HB_OUTLINE_QUEUE
464 //------------------------------------------------------------------------------------
465 // Restores the expanded state of items in the outline view to match those saved by a
466 // previous call to saveOutlineViewState.
467 //------------------------------------------------------------------------------------
468 - (void) restoreOutlineViewState
469 {
470     if (fSavedExpandedItems)
471     {
472         id anEncode;
473         NSEnumerator * e = [fEncodes objectEnumerator];
474         while ( (anEncode = [e nextObject]) )
475         {
476             hb_job_t * j = [[anEncode objectAtIndex:0] job];
477             if ([fSavedExpandedItems containsIndex: (unsigned int)j])
478                 [fOutlineView expandItem: anEncode];
479         }
480     }
481     
482     if (fSavedSelectedItem)
483     {
484         // Ugh. Have to cycle through each row looking for the previously selected job.
485         // See the explanation in saveExpandedItems about the logic here.
486         
487         // Find out if the selection was a job or a group.
488         BOOL isJob = (fSavedSelectedItem & 0x80000000) == 0x80000000;
489         // Find out what hb_job_t was selected
490         hb_job_t * j = (hb_job_t *)(fSavedSelectedItem & ~0x80000000); // strip high bit
491         
492         int rowToSelect = -1;
493         for (int i = 0; i < [fOutlineView numberOfRows]; i++)
494         {
495             id obj = [fOutlineView itemAtRow: i];
496             if (isJob && [obj isKindOfClass:[HBJob class]])
497             {
498                 // For a job in the outline view, test to see if it is a match
499                 if ([obj job] == j)
500                 {
501                     rowToSelect = i;
502                     break;
503                 }
504             }
505             else if (!isJob && ![obj isKindOfClass:[HBJob class]])
506             {
507                 // For a group, test to see if the group's first job is a match
508                 if ([[obj objectAtIndex:0] job] == j)
509                 {
510                     rowToSelect = i;
511                     break;
512                 }
513             }
514         }
515         if (rowToSelect == -1)
516             [fOutlineView deselectAll: nil];
517         else
518             [fOutlineView selectRow:rowToSelect byExtendingSelection:NO];
519     }
520 }
521 #endif
522
523 //------------------------------------------------------------------------------------
524 // Generates a multi-line text string that includes the job name on the first line
525 // followed by details of the job on subsequent lines. If the text is to be drawn as
526 // part of a highlighted cell, set isHighlighted to true. The returned string may
527 // contain multiple fonts and paragraph formating.
528 //------------------------------------------------------------------------------------
529 - (NSAttributedString *)attributedDescriptionForJob: (hb_job_t *)job
530                                           withTitle: (BOOL)withTitle
531                                          withDetail: (BOOL)withDetail
532                                    withHighlighting: (BOOL)highlighted
533 {
534     NSMutableAttributedString * finalString;   // the return value
535     NSAttributedString* anAttributedString;    // a temp string for building up attributed substrings
536     NSMutableString* aMutableString;           // a temp string for non-attributed substrings
537     hb_title_t * title = job->title;
538     
539     NSMutableParagraphStyle *ps = [[NSParagraphStyle defaultParagraphStyle] mutableCopy];
540     [ps setLineBreakMode:NSLineBreakByClipping];
541
542     static NSDictionary* detailAttribute = [[NSDictionary dictionaryWithObjectsAndKeys:
543                 [NSFont systemFontOfSize:11.0], NSFontAttributeName,
544                 [NSColor darkGrayColor], NSForegroundColorAttributeName,
545                 ps, NSParagraphStyleAttributeName,
546                 nil] retain];
547     static NSDictionary* detailHighlightedAttribute = [[NSDictionary dictionaryWithObjectsAndKeys:
548                 [NSFont systemFontOfSize:11.0], NSFontAttributeName,
549                 [NSColor whiteColor], NSForegroundColorAttributeName,
550                 ps, NSParagraphStyleAttributeName,
551                 nil] retain];
552     static NSDictionary* titleAttribute = [[NSDictionary dictionaryWithObjectsAndKeys:
553                 [NSFont systemFontOfSize:[NSFont systemFontSize]], NSFontAttributeName,
554                 ps, NSParagraphStyleAttributeName,
555                 nil] retain];
556
557     finalString = [[[NSMutableAttributedString alloc] init] autorelease];
558
559     // Title, in bold
560     // Show the name of the source Note: use title->name instead of title->dvd since
561     // name is just the chosen folder, instead of dvd which is the full path
562     if (withTitle)
563     {
564         anAttributedString = [[[NSAttributedString alloc] initWithString:[NSString stringWithUTF8String:title->name] attributes:titleAttribute] autorelease];
565         [finalString appendAttributedString:anAttributedString];
566     }
567     
568     // Other info in plain
569     
570     aMutableString = [NSMutableString stringWithCapacity:200];
571
572     // The subtitle scan doesn't contain all the stuff we need (like x264opts).
573     // So grab the next job in the group for display purposes.
574 /*
575     if (fShowsJobsAsGroups && job->pass == -1)
576     {
577         // When job is the one currently being processed, then the next in its group
578         // is the the first job in the queue.
579         hb_job_t * nextjob;
580         if (job == hb_current_job(fHandle))
581             nextjob = hb_job(fHandle, 0);
582         else
583             nextjob = hb_next_job(fHandle, job);
584         if (nextjob)    // Overly cautious in case there is no next job!
585             job = nextjob;
586     }
587 */
588
589     if (withTitle)
590     {
591         NSString * chapterString = (job->chapter_start == job->chapter_end) ?
592                 [NSString stringWithFormat:@"Chapter %d", job->chapter_start] :
593                 [NSString stringWithFormat:@"Chapters %d through %d", job->chapter_start, job->chapter_end];
594     
595         // Scan pass
596         if (job->pass == -1)
597         {
598             [aMutableString appendString:[NSString stringWithFormat:
599                     @"  (Title %d, %@, In-depth Scan)", title->index, chapterString]];
600         }
601         else
602         {
603             int numPasses = MIN( 2, job->pass + 1 );
604             if (fShowsJobsAsGroups)
605             {
606                 if (numPasses == 1)
607                     [aMutableString appendString:[NSString stringWithFormat:
608                         @"  (Title %d, %@, 1 Passes)",
609                         title->index, chapterString]];
610                 else
611                     [aMutableString appendString:[NSString stringWithFormat:
612                         @"  (Title %d, %@, %d Passes)",
613                         title->index, chapterString, numPasses]];
614             }
615             else
616                 [aMutableString appendString:[NSString stringWithFormat:
617                         @"  (Title %d, %@, Pass %d of %d)",
618                         title->index, chapterString, MAX( 1, job->pass ), numPasses]];
619         }
620     }
621     
622     // End of title stuff
623     
624     
625     // Normal pass - show detail
626     if (withDetail && job->pass == -1)
627     {
628         [aMutableString appendString:@"Subtitle Pass"];
629     }
630     
631     else if (withDetail && job->pass != -1)
632     {
633         NSString * jobFormat;
634         NSString * jobPictureDetail;
635         NSString * jobVideoDetail;
636         NSString * jobVideoCodec;
637         NSString * jobVideoQuality;
638         NSString * jobAudioDetail;
639         NSString * jobAudioCodec;
640
641         if ([aMutableString length] != 0)
642             [aMutableString appendString:@"\n"];
643         
644 //        if (job->pass == -1)
645 //            [aMutableString appendString:@"Subtitle Pass"];
646 //        else
647         {
648             int passNum = MAX( 1, job->pass );
649             if (passNum == 1)
650                 [aMutableString appendString:@"1st Pass"];
651             else if (passNum == 2)
652                 [aMutableString appendString:@"2nd Pass"];
653             else
654                 [aMutableString appendString:[NSString stringWithFormat: @"Pass %d", passNum]];
655         }
656
657         /* Muxer settings (File Format in the gui) */
658         if (job->mux == 65536 || job->mux == 131072 || job->mux == 1048576)
659             jobFormat = @"MP4"; // HB_MUX_MP4,HB_MUX_PSP,HB_MUX_IPOD
660         else if (job->mux == 262144)
661             jobFormat = @"AVI"; // HB_MUX_AVI
662         else if (job->mux == 524288)
663             jobFormat = @"OGM"; // HB_MUX_OGM
664         else if (job->mux == 2097152)
665             jobFormat = @"MKV"; // HB_MUX_MKV
666         else
667             jobFormat = @"unknown";
668         
669         // 2097152
670         /* Video Codec settings (Encoder in the gui) */
671         if (job->vcodec == 1)
672             jobVideoCodec = @"FFmpeg"; // HB_VCODEC_FFMPEG
673         else if (job->vcodec == 2)
674             jobVideoCodec = @"XviD"; // HB_VCODEC_XVID
675         else if (job->vcodec == 4)
676         {
677             /* Deterimine for sure how we are now setting iPod uuid atom */
678             if (job->h264_level) // We are encoding for iPod
679                 jobVideoCodec = @"x264 (H.264 iPod)"; // HB_VCODEC_X264    
680             else
681                 jobVideoCodec = @"x264 (H.264 Main)"; // HB_VCODEC_X264
682         }
683         else
684             jobVideoCodec = @"unknown";
685         
686         /* Audio Codecs (Second half of Codecs in the gui) */
687         if (job->acodec == 256)
688             jobAudioCodec = @"AAC"; // HB_ACODEC_FAAC
689         else if (job->acodec == 512)
690             jobAudioCodec = @"MP3"; // HB_ACODEC_LAME
691         else if (job->acodec == 1024)
692             jobAudioCodec = @"Vorbis"; // HB_ACODEC_VORBIS
693         else if (job->acodec == 2048)
694             jobAudioCodec = @"AC3"; // HB_ACODEC_AC3
695         else
696             jobAudioCodec = @"unknown";
697         /* Show Basic File info */
698         if (job->chapter_markers == 1)
699             [aMutableString appendString:[NSString stringWithFormat:@"\nFormat: %@ Container, %@ Video + %@ Audio, Chapter Markers", jobFormat, jobVideoCodec, jobAudioCodec]];
700         else
701             [aMutableString appendString:[NSString stringWithFormat:@"\nFormat: %@ Container, %@ Video + %@ Audio", jobFormat, jobVideoCodec, jobAudioCodec]];
702             
703         /*Picture info*/
704         /*integers for picture values deinterlace, crop[4], keep_ratio, grayscale, pixel_ratio, pixel_aspect_width, pixel_aspect_height,
705          maxWidth, maxHeight */
706         if (job->pixel_ratio == 1)
707         {
708             int titlewidth = title->width - job->crop[2] - job->crop[3];
709             int displayparwidth = titlewidth * job->pixel_aspect_width / job->pixel_aspect_height;
710             int displayparheight = title->height - job->crop[0] - job->crop[1];
711             jobPictureDetail = [NSString stringWithFormat:@"Picture: %dx%d (%dx%d Anamorphic)", displayparwidth, displayparheight, job->width, displayparheight];
712         }
713         else
714             jobPictureDetail = [NSString stringWithFormat:@"Picture: %dx%d", job->width, job->height];
715         if (job->keep_ratio == 1)
716             jobPictureDetail = [jobPictureDetail stringByAppendingString:@" Keep Aspect Ratio"];
717         
718         if (job->grayscale == 1)
719             jobPictureDetail = [jobPictureDetail stringByAppendingString:@", Grayscale"];
720         
721         if (job->deinterlace == 1)
722             jobPictureDetail = [jobPictureDetail stringByAppendingString:@", Deinterlace"];
723         /* Show Picture info */    
724         [aMutableString appendString:[NSString stringWithFormat:@"\n%@", jobPictureDetail]];
725         
726         /* Detailed Video info */
727         if (job->vquality <= 0 || job->vquality >= 1)
728             jobVideoQuality =[NSString stringWithFormat:@"%d kbps", job->vbitrate];
729         else
730         {
731             NSNumber * vidQuality;
732             vidQuality = [NSNumber numberWithInt:job->vquality * 100];
733             /* this is screwed up kind of. Needs to be formatted properly */
734             if (job->crf == 1)
735                 jobVideoQuality =[NSString stringWithFormat:@"%@%% CRF", vidQuality];            
736             else
737                 jobVideoQuality =[NSString stringWithFormat:@"%@%% CQP", vidQuality];
738         }
739         
740         if (job->vrate_base == 1126125)
741         {
742             /* NTSC FILM 23.976 */
743             jobVideoDetail = [NSString stringWithFormat:@"Video: %@, %@, 23.976 fps", jobVideoCodec, jobVideoQuality];
744         }
745         else if (job->vrate_base == 900900)
746         {
747             /* NTSC 29.97 */
748             jobVideoDetail = [NSString stringWithFormat:@"Video: %@, %@, 29.97 fps", jobVideoCodec, jobVideoQuality];
749         }
750         else
751         {
752             /* Everything else */
753             jobVideoDetail = [NSString stringWithFormat:@"Video: %@, %@, %d fps", jobVideoCodec, jobVideoQuality, job->vrate / job->vrate_base];
754         }
755         
756         /* Add the video detail string to the job filed in the window */
757         [aMutableString appendString:[NSString stringWithFormat:@"\n%@", jobVideoDetail]];
758         
759         /* if there is an x264 option string, lets add it here*/
760         /*NOTE: Due to size, lets get this in a tool tip*/
761         
762         if (job->x264opts)
763             [aMutableString appendString:[NSString stringWithFormat:@"\nx264 Options: %@", [NSString stringWithUTF8String:job->x264opts]]];
764         
765         /* Audio Detail */
766         if ([jobAudioCodec isEqualToString: @"AC3"])
767             jobAudioDetail = [NSString stringWithFormat:@"Audio: %@, Pass-Through", jobAudioCodec];
768         else
769             jobAudioDetail = [NSString stringWithFormat:@"Audio: %@, %d kbps, %d Hz", jobAudioCodec, job->abitrate, job->arate];
770         
771         /* we now get the audio mixdown info for each of the two gui audio tracks */
772         /* lets do it the long way here to get a handle on things.
773             Hardcoded for two tracks for gui: audio_mixdowns[i] audio_mixdowns[i] */
774         int ai; // counter for each audios [] , macgui only allows for two audio tracks currently
775         for( ai = 0; ai < 2; ai++ )
776         {
777             if (job->audio_mixdowns[ai] == HB_AMIXDOWN_MONO)
778                 jobAudioDetail = [jobAudioDetail stringByAppendingString:[NSString stringWithFormat:@", Track %d: Mono",ai + 1]];
779             if (job->audio_mixdowns[ai] == HB_AMIXDOWN_STEREO)
780                 jobAudioDetail = [jobAudioDetail stringByAppendingString:[NSString stringWithFormat:@", Track %d: Stereo",ai + 1]];
781             if (job->audio_mixdowns[ai] == HB_AMIXDOWN_DOLBY)
782                 jobAudioDetail = [jobAudioDetail stringByAppendingString:[NSString stringWithFormat:@", Track %d: Dolby Surround",ai + 1]];
783             if (job->audio_mixdowns[ai] == HB_AMIXDOWN_DOLBYPLII)
784                 jobAudioDetail = [jobAudioDetail stringByAppendingString:[NSString stringWithFormat:@", Track %d: Dolby Pro Logic II",ai + 1]];
785             if (job->audio_mixdowns[ai] == HB_AMIXDOWN_6CH)
786                 jobAudioDetail = [jobAudioDetail stringByAppendingString:[NSString stringWithFormat:@", Track %d: 6-channel discreet",ai + 1]];
787         }
788         
789         /* Add the Audio detail string to the job field in the window */
790         [aMutableString appendString:[NSString stringWithFormat: @"\n%@", jobAudioDetail]];
791         
792         /*Destination Field */
793         [aMutableString appendString:[NSString stringWithFormat:@"\nDestination: %@", [NSString stringWithUTF8String:job->file]]];
794     }
795     
796     anAttributedString = [[[NSAttributedString alloc] initWithString:aMutableString attributes:highlighted ? detailHighlightedAttribute : detailAttribute] autorelease];
797     [finalString appendAttributedString:anAttributedString];
798
799             
800     return finalString;
801 }
802
803
804
805 - (NSAttributedString *)attributedDescriptionForJob: (hb_job_t *)job
806                                           withTitle: (BOOL)withTitle
807                                        withPassName: (BOOL)withPassName
808                                      withFormatInfo: (BOOL)withFormatInfo
809                                     withDestination: (BOOL)withDestination
810                                     withPictureInfo: (BOOL)withPictureInfo
811                                       withVideoInfo: (BOOL)withVideoInfo
812                                        withx264Info: (BOOL)withx264Info
813                                       withAudioInfo: (BOOL)withAudioInfo
814                                    withHighlighting: (BOOL)highlighted
815 {
816     NSMutableArray * stringParts = [NSMutableArray arrayWithCapacity:0];
817     
818     hb_title_t * title = job->title;
819     
820     // Attributes
821     static NSMutableParagraphStyle *ps = [[[NSParagraphStyle defaultParagraphStyle] mutableCopy] retain];
822     [ps setLineBreakMode:NSLineBreakByClipping];
823
824     static NSDictionary* detailAttribute = [[NSDictionary dictionaryWithObjectsAndKeys:
825                 [NSFont systemFontOfSize:10.0], NSFontAttributeName,
826                 [NSColor darkGrayColor], NSForegroundColorAttributeName,
827                 ps, NSParagraphStyleAttributeName,
828                 nil] retain];
829     static NSDictionary* detailHighlightedAttribute = [[NSDictionary dictionaryWithObjectsAndKeys:
830                 [NSFont systemFontOfSize:10.0], NSFontAttributeName,
831                 [NSColor whiteColor], NSForegroundColorAttributeName,
832                 ps, NSParagraphStyleAttributeName,
833                 nil] retain];
834     static NSDictionary* titleAttribute = [[NSDictionary dictionaryWithObjectsAndKeys:
835                 [NSFont systemFontOfSize:[NSFont systemFontSize]], NSFontAttributeName,
836                 ps, NSParagraphStyleAttributeName,
837                 nil] retain];
838
839
840     // Title with summary
841     NSString * titleString;
842     if (withTitle)
843     {
844         // Note: use title->name instead of title->dvd since name is just the chosen
845         // folder, instead of dvd which is the full path
846         titleString = [NSString stringWithUTF8String:title->name];
847
848         NSString * summaryInfo;
849     
850         NSString * chapterString = (job->chapter_start == job->chapter_end) ?
851                 [NSString stringWithFormat:@"Chapter %d", job->chapter_start] :
852                 [NSString stringWithFormat:@"Chapters %d through %d", job->chapter_start, job->chapter_end];
853
854         BOOL hasIndepthScan = (job->pass == -1);
855         int numVideoPasses = 0;
856
857         // To determine number of video passes, we need to skip past the in-depth scan.
858         if (fShowsJobsAsGroups && hasIndepthScan)
859         {
860             // When job is the one currently being processed, then the next in its group
861             // is the the first job in the queue.
862             hb_job_t * nextjob;
863             if (job == hb_current_job(fHandle))
864                 nextjob = hb_job(fHandle, 0);
865             else
866                 nextjob = hb_next_job(fHandle, job);
867             if (nextjob)    // Overly cautious in case there is no next job!
868                 numVideoPasses = MIN( 2, nextjob->pass + 1 );
869         }
870         else
871             numVideoPasses = MIN( 2, job->pass + 1 );
872
873         if (hasIndepthScan && numVideoPasses == 1)
874             summaryInfo = [NSString stringWithFormat: @"  (Title %d, %@, In-depth Scan, Single Video Pass)", title->index, chapterString];
875         else if (hasIndepthScan && numVideoPasses > 1)
876             summaryInfo = [NSString stringWithFormat: @"  (Title %d, %@, In-depth Scan, %d Video Passes)", title->index, chapterString, numVideoPasses];
877         else if (numVideoPasses == 1)
878             summaryInfo = [NSString stringWithFormat: @"  (Title %d, %@, Single Video Pass)", title->index, chapterString];
879         else
880             summaryInfo = [NSString stringWithFormat: @"  (Title %d, %@, %d Video Passes)", title->index, chapterString, numVideoPasses];
881
882         [stringParts addObject: [NSString stringWithFormat:@"%@%@", titleString, summaryInfo]];
883     }
884     
885     // End of title stuff
886     
887
888     // Pass Name
889     if (withPassName)
890     {
891         NSString * jobPassName;
892         if (job->pass == -1)
893             jobPassName = NSLocalizedString (@"In-depth Scan", nil);
894         else
895         {
896             int passNum = MAX( 1, job->pass );
897             if (passNum == 0)
898                 jobPassName = NSLocalizedString (@"Encode Pass", nil);
899             else if (passNum == 1)
900                 jobPassName = NSLocalizedString (@"1st Pass", nil);
901             else if (passNum == 2)
902                 jobPassName = NSLocalizedString (@"2nd Pass", nil);
903             else
904                 jobPassName = [NSString stringWithFormat: NSLocalizedString(@"Pass %d", nil), passNum];
905         }
906         [stringParts addObject: jobPassName];
907     }
908
909     // Video Codec needed by FormatInfo and withVideoInfo
910     NSString * jobVideoCodec;
911     if (withFormatInfo || withVideoInfo)
912     {
913         // 2097152
914         /* Video Codec settings (Encoder in the gui) */
915         if (job->vcodec == 1)
916             jobVideoCodec = @"FFmpeg"; // HB_VCODEC_FFMPEG
917         else if (job->vcodec == 2)
918             jobVideoCodec = @"XviD"; // HB_VCODEC_XVID
919         else if (job->vcodec == 4)
920         {
921             /* Deterimine for sure how we are now setting iPod uuid atom */
922             if (job->h264_level) // We are encoding for iPod
923                 jobVideoCodec = @"x264 (H.264 iPod)"; // HB_VCODEC_X264    
924             else
925                 jobVideoCodec = @"x264 (H.264 Main)"; // HB_VCODEC_X264
926         }
927         else
928             jobVideoCodec = @"unknown";
929     }
930     
931     // Audio Codec needed by FormatInfo and AudioInfo
932     NSString * jobAudioCodec;
933     if (withFormatInfo || withAudioInfo)
934     {
935         if (job->acodec == 256)
936             jobAudioCodec = @"AAC"; // HB_ACODEC_FAAC
937         else if (job->acodec == 512)
938             jobAudioCodec = @"MP3"; // HB_ACODEC_LAME
939         else if (job->acodec == 1024)
940             jobAudioCodec = @"Vorbis"; // HB_ACODEC_VORBIS
941         else if (job->acodec == 2048)
942             jobAudioCodec = @"AC3"; // HB_ACODEC_AC3
943         else
944             jobAudioCodec = @"unknown";
945     }
946
947
948     if (withFormatInfo)
949     {
950         NSString * jobFormatInfo;
951         // Muxer settings (File Format in the gui)
952         if (job->mux == 65536 || job->mux == 131072 || job->mux == 1048576)
953             jobFormatInfo = @"MP4"; // HB_MUX_MP4,HB_MUX_PSP,HB_MUX_IPOD
954         else if (job->mux == 262144)
955             jobFormatInfo = @"AVI"; // HB_MUX_AVI
956         else if (job->mux == 524288)
957             jobFormatInfo = @"OGM"; // HB_MUX_OGM
958         else if (job->mux == 2097152)
959             jobFormatInfo = @"MKV"; // HB_MUX_MKV
960         else
961             jobFormatInfo = @"unknown";
962                 
963         if (job->chapter_markers == 1)
964             jobFormatInfo = [NSString stringWithFormat:@"Format: %@ Container, %@ Video + %@ Audio, Chapter Markers", jobFormatInfo, jobVideoCodec, jobAudioCodec];
965         else
966             jobFormatInfo = [NSString stringWithFormat:@"Format: %@ Container, %@ Video + %@ Audio", jobFormatInfo, jobVideoCodec, jobAudioCodec];
967             
968         [stringParts addObject: jobFormatInfo];
969     }
970
971     if (withDestination)
972     {
973         NSString * jobDestinationInfo = [NSString stringWithFormat:@"Destination: %@", [NSString stringWithUTF8String:job->file]];
974         [stringParts addObject: jobDestinationInfo];
975     }
976
977
978     if (withPictureInfo)
979     {
980         NSString * jobPictureInfo;
981         /*integers for picture values deinterlace, crop[4], keep_ratio, grayscale, pixel_ratio, pixel_aspect_width, pixel_aspect_height,
982          maxWidth, maxHeight */
983         if (job->pixel_ratio == 1)
984         {
985             int titlewidth = title->width - job->crop[2] - job->crop[3];
986             int displayparwidth = titlewidth * job->pixel_aspect_width / job->pixel_aspect_height;
987             int displayparheight = title->height - job->crop[0] - job->crop[1];
988             jobPictureInfo = [NSString stringWithFormat:@"Picture: %dx%d (%dx%d Anamorphic)", displayparwidth, displayparheight, job->width, displayparheight];
989         }
990         else
991             jobPictureInfo = [NSString stringWithFormat:@"Picture: %dx%d", job->width, job->height];
992         if (job->keep_ratio == 1)
993             jobPictureInfo = [jobPictureInfo stringByAppendingString:@" Keep Aspect Ratio"];
994         
995         if (job->grayscale == 1)
996             jobPictureInfo = [jobPictureInfo stringByAppendingString:@", Grayscale"];
997         
998         if (job->deinterlace == 1)
999             jobPictureInfo = [jobPictureInfo stringByAppendingString:@", Deinterlace"];
1000         [stringParts addObject: jobPictureInfo];
1001     }
1002     
1003     if (withVideoInfo)
1004     {
1005         NSString * jobVideoQuality;
1006         NSString * jobVideoDetail;
1007         
1008         if (job->vquality <= 0 || job->vquality >= 1)
1009             jobVideoQuality = [NSString stringWithFormat:@"%d kbps", job->vbitrate];
1010         else
1011         {
1012             NSNumber * vidQuality;
1013             vidQuality = [NSNumber numberWithInt:job->vquality * 100];
1014             // this is screwed up kind of. Needs to be formatted properly.
1015             if (job->crf == 1)
1016                 jobVideoQuality = [NSString stringWithFormat:@"%@%% CRF", vidQuality];            
1017             else
1018                 jobVideoQuality = [NSString stringWithFormat:@"%@%% CQP", vidQuality];
1019         }
1020         
1021         if (job->vrate_base == 1126125)
1022         {
1023             /* NTSC FILM 23.976 */
1024             jobVideoDetail = [NSString stringWithFormat:@"Video: %@, %@, 23.976 fps", jobVideoCodec, jobVideoQuality];
1025         }
1026         else if (job->vrate_base == 900900)
1027         {
1028             /* NTSC 29.97 */
1029             jobVideoDetail = [NSString stringWithFormat:@"Video: %@, %@, 29.97 fps", jobVideoCodec, jobVideoQuality];
1030         }
1031         else
1032         {
1033             /* Everything else */
1034             jobVideoDetail = [NSString stringWithFormat:@"Video: %@, %@, %d fps", jobVideoCodec, jobVideoQuality, job->vrate / job->vrate_base];
1035         }
1036         [stringParts addObject: jobVideoDetail];
1037     }
1038     
1039     if (withx264Info)
1040     {
1041         NSString * jobx264Info;
1042         if (job->x264opts)
1043         {
1044             jobx264Info = [NSString stringWithFormat:@"x264 Options: %@", [NSString stringWithUTF8String:job->x264opts]];
1045             [stringParts addObject: jobx264Info];
1046         }
1047     }
1048
1049     if (withAudioInfo)
1050     {
1051         NSString * jobAudioInfo;
1052         if ([jobAudioCodec isEqualToString: @"AC3"])
1053             jobAudioInfo = [NSString stringWithFormat:@"Audio: %@, Pass-Through", jobAudioCodec];
1054         else
1055             jobAudioInfo = [NSString stringWithFormat:@"Audio: %@, %d kbps, %d Hz", jobAudioCodec, job->abitrate, job->arate];
1056         
1057         /* we now get the audio mixdown info for each of the two gui audio tracks */
1058         /* lets do it the long way here to get a handle on things.
1059             Hardcoded for two tracks for gui: audio_mixdowns[i] audio_mixdowns[i] */
1060         int ai; // counter for each audios [] , macgui only allows for two audio tracks currently
1061         for( ai = 0; ai < 2; ai++ )
1062         {
1063             if (job->audio_mixdowns[ai] == HB_AMIXDOWN_MONO)
1064                 jobAudioInfo = [jobAudioInfo stringByAppendingString:[NSString stringWithFormat:@", Track %d: Mono", ai + 1]];
1065             if (job->audio_mixdowns[ai] == HB_AMIXDOWN_STEREO)
1066                 jobAudioInfo = [jobAudioInfo stringByAppendingString:[NSString stringWithFormat:@", Track %d: Stereo", ai + 1]];
1067             if (job->audio_mixdowns[ai] == HB_AMIXDOWN_DOLBY)
1068                 jobAudioInfo = [jobAudioInfo stringByAppendingString:[NSString stringWithFormat:@", Track %d: Dolby Surround", ai + 1]];
1069             if (job->audio_mixdowns[ai] == HB_AMIXDOWN_DOLBYPLII)
1070                 jobAudioInfo = [jobAudioInfo stringByAppendingString:[NSString stringWithFormat:@", Track %d: Dolby Pro Logic II", ai + 1]];
1071             if (job->audio_mixdowns[ai] == HB_AMIXDOWN_6CH)
1072                 jobAudioInfo = [jobAudioInfo stringByAppendingString:[NSString stringWithFormat:@", Track %d: 6-channel discreet", ai + 1]];
1073         }
1074         [stringParts addObject: jobAudioInfo];
1075     }
1076     
1077     
1078     NSMutableAttributedString * anAttributedString = [[[NSMutableAttributedString alloc]
1079         initWithString:[stringParts componentsJoinedByString:@"\n"]
1080         attributes:highlighted ? detailHighlightedAttribute : detailAttribute] autorelease];
1081
1082     if (withTitle)
1083         [anAttributedString setAttributes: titleAttribute range: NSMakeRange(0, [titleString length])];
1084             
1085     return anAttributedString;
1086 }
1087
1088 //------------------------------------------------------------------------------------
1089 // Generate string to display in UI.
1090 //------------------------------------------------------------------------------------
1091 - (NSString *) progressStatusStringForJob: (hb_job_t *)job state: (hb_state_t *)s
1092 {
1093     if (s->state == HB_STATE_WORKING)
1094     {
1095         NSString * msg;
1096         if (job->pass == -1)
1097             msg = NSLocalizedString( @"Analyzing subtitles", nil );
1098         else if (job->pass == 1)
1099             msg = NSLocalizedString( @"Analyzing video", nil );
1100         else if ((job->pass == 0) ||  (job->pass == 2))
1101             msg = NSLocalizedString( @"Encoding movie", nil );
1102         else
1103             return @""; // unknown condition!
1104             
1105         if( s->param.working.seconds > -1 )
1106         {
1107             return [NSString stringWithFormat:
1108                 NSLocalizedString( @"%@ (%.2f fps, avg %.2f fps)", nil ),
1109                 msg, s->param.working.rate_cur, s->param.working.rate_avg];
1110         }
1111         else
1112             return msg;
1113
1114     }
1115
1116     else if (s->state == HB_STATE_MUXING)
1117         return NSLocalizedString( @"Muxing", nil );
1118
1119     else if (s->state == HB_STATE_PAUSED)
1120         return NSLocalizedString( @"Paused", nil );
1121
1122     else if (s->state == HB_STATE_WORKDONE)
1123         return NSLocalizedString( @"Done", nil );
1124     
1125     return @"";
1126 }
1127
1128 //------------------------------------------------------------------------------------
1129 // Generate string to display in UI.
1130 //------------------------------------------------------------------------------------
1131 - (NSString *) progressTimeRemainingStringForJob: (hb_job_t *)job state: (hb_state_t *)s
1132 {
1133     if (s->state == HB_STATE_WORKING)
1134     {
1135         #define p s->param.working
1136         if (p.seconds < 0)
1137             return @"";
1138         
1139         // Minutes always needed
1140         NSString * minutes;
1141         if (p.minutes > 1)
1142           minutes = [NSString stringWithFormat:NSLocalizedString( @"%d minutes ", nil ), p.minutes];
1143         else if (p.minutes == 1)
1144           minutes = NSLocalizedString( @"1 minute ", nil );
1145         else
1146           minutes = @"";
1147         
1148         if (p.hours >= 1)
1149         {
1150             NSString * hours;
1151             if (p.hours > 1)
1152               hours = [NSString stringWithFormat:NSLocalizedString( @"%d hours ", nil ), p.hours];
1153             else
1154               hours = NSLocalizedString( @"1 hour ", nil );
1155
1156             return [NSString stringWithFormat:NSLocalizedString( @"%@%@remaining", nil ), hours, minutes];
1157         }
1158         
1159         else
1160         {
1161             NSString * seconds;
1162             if (p.seconds > 1)
1163               seconds = [NSString stringWithFormat:NSLocalizedString( @"%d seconds ", nil ), p.seconds];
1164             else
1165               seconds = NSLocalizedString( @"1 second ", nil );
1166
1167             return [NSString stringWithFormat:NSLocalizedString( @"%@%@remaining", nil ), minutes, seconds];
1168         }
1169
1170 /* here is code that does it more like the Finder
1171         if( p.seconds > -1 )
1172         {
1173             float estHours = (p.hours + (p.minutes / 60.0));
1174             float estMinutes = (p.minutes + (p.seconds / 60.0));
1175
1176             if (estHours > 1.5)
1177                 return [NSString stringWithFormat:NSLocalizedString( @"Time remaining: About %d hours", nil ), lrintf(estHours)];
1178             else if (estHours > 0.983)    // 59 minutes
1179                 return NSLocalizedString( @"Time remaining: About 1 hour", nil );
1180             else if (estMinutes > 1.5)
1181                 return [NSString stringWithFormat:NSLocalizedString( @"Time remaining: About %d minutes", nil ), lrintf(estMinutes)];
1182             else if (estMinutes > 0.983)    // 59 seconds
1183                 return NSLocalizedString( @"Time remaining: About 1 minute", nil );
1184             else if (p.seconds <= 5)
1185                 return NSLocalizedString( @"Time remaining: Less than 5 seconds", nil );
1186             else if (p.seconds <= 10)
1187                 return NSLocalizedString( @"Time remaining: Less than 10 seconds", nil );
1188             else
1189                 return NSLocalizedString( @"Time remaining: Less than 1 minute", nil );
1190         }
1191         else
1192             return NSLocalizedString( @"Time remaining: Calculating...", nil );
1193 */
1194         #undef p
1195     }
1196     
1197     return @"";
1198 }
1199
1200 //------------------------------------------------------------------------------------
1201 // Refresh progress bar (fProgressBar) from current state.
1202 //------------------------------------------------------------------------------------
1203 - (void) updateProgressBarWithState: (hb_state_t *)s
1204 {
1205     if (s->state == HB_STATE_WORKING)
1206     {
1207         #define p s->param.working
1208         [fProgressBar setIndeterminate:NO];
1209
1210         float progress_total = fShowsJobsAsGroups ?
1211                 100.0 * ( p.progress + p.job_cur - 1 ) / p.job_count :
1212                 100.0 * p.progress;
1213
1214         [fProgressBar setDoubleValue:progress_total];
1215         #undef p
1216     }
1217     
1218     else if (s->state == HB_STATE_MUXING)
1219     {
1220         #define p s->param.muxing
1221         [fProgressBar setIndeterminate:YES];
1222         [fProgressBar startAnimation:nil];
1223         #undef p
1224     }
1225
1226     else if (s->state == HB_STATE_WORKDONE)
1227     {
1228         [fProgressBar setIndeterminate:NO];
1229         [fProgressBar setDoubleValue:0.0];
1230     }
1231 }
1232
1233 //------------------------------------------------------------------------------------
1234 // Refresh queue count text field (fQueueCountField).
1235 //------------------------------------------------------------------------------------
1236 - (void)updateQueueCountField
1237 {
1238     NSString * msg;
1239     int jobCount;
1240     
1241     if (fShowsJobsAsGroups)
1242     {
1243         jobCount = fHandle ? hb_group_count(fHandle) : 0;
1244         if (jobCount == 1)
1245             msg = NSLocalizedString(@"1 pending encode", nil);
1246         else
1247             msg = [NSString stringWithFormat:NSLocalizedString(@"%d pending encodes", nil), jobCount];
1248     }
1249     else
1250     {
1251         jobCount = fHandle ? hb_count(fHandle) : 0;
1252         if (jobCount == 1)
1253             msg = NSLocalizedString(@"1 pending pass", nil);
1254         else
1255             msg = [NSString stringWithFormat:NSLocalizedString(@"%d pending passes", nil), jobCount];
1256
1257     }
1258
1259     [fQueueCountField setStringValue:msg];
1260 }
1261
1262 //------------------------------------------------------------------------------------
1263 // Refresh the UI in the current job pane. Should be called whenever the current job
1264 // being processed has changed or when progress has changed.
1265 //------------------------------------------------------------------------------------
1266 - (void)updateCurrentJobUI
1267 {
1268     hb_state_t s;
1269     hb_job_t * job = nil;
1270     
1271     if (fHandle)
1272     {
1273         hb_get_state2( fHandle, &s );
1274         job = hb_current_job(fHandle);
1275     }
1276
1277     if (job)
1278     {
1279         if (fLastKnownCurrentJob != job)
1280         {
1281             switch (job->pass)
1282             {
1283                 case -1:  // in-depth scan
1284                     [fJobDescTextField setAttributedStringValue:
1285                         [self attributedDescriptionForJob:job
1286                                     withTitle: YES
1287                                  withPassName: YES
1288                                withFormatInfo: NO
1289                               withDestination: NO
1290                               withPictureInfo: NO
1291                                 withVideoInfo: NO
1292                                  withx264Info: NO
1293                                 withAudioInfo: NO
1294                              withHighlighting: NO]];
1295                     break;
1296                     
1297                 case 1:  // video 1st pass
1298                     [fJobDescTextField setAttributedStringValue:
1299                         [self attributedDescriptionForJob:job
1300                                     withTitle: YES
1301                                  withPassName: YES
1302                                withFormatInfo: NO
1303                               withDestination: NO
1304                               withPictureInfo: YES
1305                                 withVideoInfo: YES
1306                                  withx264Info: YES
1307                                 withAudioInfo: NO
1308                              withHighlighting: NO]];
1309                     break;
1310                 
1311                 case 0:  // single pass
1312                 case 2:  // video 2nd pass + audio
1313                     [fJobDescTextField setAttributedStringValue:
1314                         [self attributedDescriptionForJob:job
1315                                     withTitle: YES
1316                                  withPassName: YES
1317                                withFormatInfo: NO
1318                               withDestination: NO
1319                               withPictureInfo: YES
1320                                 withVideoInfo: YES
1321                                  withx264Info: YES
1322                                 withAudioInfo: YES
1323                              withHighlighting: NO]];
1324                     break;
1325                 
1326                 default: // unknown
1327                     [fJobDescTextField setAttributedStringValue:
1328                         [self attributedDescriptionForJob:job
1329                                     withTitle: YES
1330                                  withPassName: YES
1331                                withFormatInfo: NO
1332                               withDestination: NO
1333                               withPictureInfo: YES
1334                                 withVideoInfo: YES
1335                                  withx264Info: YES
1336                                 withAudioInfo: YES
1337                              withHighlighting: NO]];
1338             }
1339
1340             [self showCurrentJobPane:YES];
1341             [fJobIconView setImage: fShowsJobsAsGroups ? [NSImage imageNamed:@"JobLarge"] : [self largeImageForPass: job->pass] ];
1342         }
1343
1344         NSString * statusMsg = [self progressStatusStringForJob:job state:&s];
1345         NSString * timeMsg = [self progressTimeRemainingStringForJob:job state:&s];
1346         if ([timeMsg length] > 0)
1347             statusMsg = [NSString stringWithFormat:@"%@ - %@", statusMsg, timeMsg];
1348         [fProgressTextField setStringValue:statusMsg];
1349         [self updateProgressBarWithState:&s];
1350     }
1351     else
1352     {
1353         [fJobDescTextField setStringValue:NSLocalizedString(@"No job processing", nil)];
1354
1355         [self showCurrentJobPane:NO];
1356         [fProgressBar stopAnimation:nil];    // just in case in was animating
1357     }
1358         
1359     fLastKnownCurrentJob = job;
1360 }
1361
1362 //------------------------------------------------------------------------------------
1363 // Refresh the UI in the queue pane. Should be called whenever the content of HB's job
1364 // list has changed so that HBQueueController can sync up.
1365 //------------------------------------------------------------------------------------
1366 - (void)updateQueueUI
1367 {
1368 #if HB_OUTLINE_QUEUE
1369     [self saveOutlineViewState];
1370     [self rebuildEncodes];
1371     [fOutlineView noteNumberOfRowsChanged];
1372     [fOutlineView reloadData];
1373     [self restoreOutlineViewState];
1374 #else
1375     [fTaskView noteNumberOfRowsChanged];
1376     [fTaskView reloadData];
1377 #endif
1378     
1379     [self updateQueueCountField];
1380 }
1381
1382 //------------------------------------------------------------------------------------
1383 // Deletes the selected job from HB and the queue UI
1384 //------------------------------------------------------------------------------------
1385 - (IBAction)removeSelectedJob: (id)sender
1386 {
1387     if (!fHandle) return;
1388     
1389     int row = [sender selectedRow];
1390     if (row != -1)
1391     {
1392 #if HB_UNI_QUEUE
1393         if (row == 0)
1394         {
1395             [self cancelCurrentJob:sender];
1396         }
1397         else
1398         {
1399             row--;
1400             if (fShowsJobsAsGroups)
1401                 hb_rem_group( fHandle, hb_group( fHandle, row ) );
1402             else
1403                 hb_rem( fHandle, hb_job( fHandle, row ) );
1404         }
1405 #else
1406   #if HB_OUTLINE_QUEUE
1407         hb_job_t * job = [[[fOutlineView itemAtRow: row] objectAtIndex: 0] job];
1408         hb_rem_group( fHandle, job );
1409   #else
1410         if (fShowsJobsAsGroups)
1411             hb_rem_group( fHandle, hb_group( fHandle, row ) );
1412         else
1413             hb_rem( fHandle, hb_job( fHandle, row ) );
1414   #endif
1415 #endif
1416         [self updateQueueUI];
1417     }
1418 }
1419
1420 //------------------------------------------------------------------------------------
1421 // Prompts user if the want to cancel encoding of current job. If so, doCancelCurrentJob
1422 // gets called.
1423 //------------------------------------------------------------------------------------
1424 - (IBAction)cancelCurrentJob: (id)sender
1425 {
1426     [fHBController Cancel:sender];
1427 }
1428
1429 //------------------------------------------------------------------------------------
1430 // Turns on the display of detail information for each job. Does nothing if detail is
1431 // already turned on.
1432 //------------------------------------------------------------------------------------
1433 - (IBAction)showDetail: (id)sender
1434 {
1435     if (!fShowsDetail)
1436         [self setShowsDetail:YES];
1437 }
1438
1439 //------------------------------------------------------------------------------------
1440 // Turns off the display of detail information for each job. Does nothing if detail is
1441 // already turned off.
1442 //------------------------------------------------------------------------------------
1443 - (IBAction)hideDetail: (id)sender
1444 {
1445     if (fShowsDetail)
1446         [self setShowsDetail:NO];
1447 }
1448
1449 //------------------------------------------------------------------------------------
1450 // Turns on displaying of jobs as groups by calling setShowsJobsAsGroups:YES. Does
1451 // nothing if groups are already turned on. 
1452 //------------------------------------------------------------------------------------
1453 - (IBAction)showJobsAsGroups: (id)sender
1454 {
1455     if (!fShowsJobsAsGroups)
1456         [self setShowsJobsAsGroups:YES];
1457 }
1458
1459 //------------------------------------------------------------------------------------
1460 // Turns on displaying of jobs as individual items by calling setShowsJobsAsGroups:NO.
1461 // Does nothing if groups are already turned off. 
1462 //------------------------------------------------------------------------------------
1463 - (IBAction)showJobsAsPasses: (id)sender
1464 {
1465     if (fShowsJobsAsGroups)
1466         [self setShowsJobsAsGroups:NO];
1467 }
1468
1469 //------------------------------------------------------------------------------------
1470 // Starts or cancels the processing of jobs depending on the current state
1471 //------------------------------------------------------------------------------------
1472 - (IBAction)toggleStartCancel: (id)sender
1473 {
1474     if (!fHandle) return;
1475     
1476     hb_state_t s;
1477     hb_get_state2 (fHandle, &s);
1478
1479     if ((s.state == HB_STATE_PAUSED) || (s.state == HB_STATE_WORKING) || (s.state == HB_STATE_MUXING))
1480         [fHBController Cancel: fQueuePane]; // sender == fQueuePane so that warning alert shows up on queue window
1481
1482     else
1483     {
1484         if (fShowsJobsAsGroups)
1485         {
1486             if (hb_group_count(fHandle) > 0)
1487                 [fHBController doRip];
1488         }
1489         else if (hb_count(fHandle) > 0)
1490             [fHBController doRip];
1491     }    
1492 }
1493
1494 //------------------------------------------------------------------------------------
1495 // Toggles the pause/resume state of hblib
1496 //------------------------------------------------------------------------------------
1497 - (IBAction)togglePauseResume: (id)sender
1498 {
1499     if (!fHandle) return;
1500     
1501     hb_state_t s;
1502     hb_get_state2 (fHandle, &s);
1503
1504     if (s.state == HB_STATE_PAUSED)
1505         hb_resume (fHandle);
1506     else if ((s.state == HB_STATE_WORKING) || (s.state == HB_STATE_MUXING))
1507         hb_pause (fHandle);
1508 }
1509
1510 #if HB_OUTLINE_METRIC_CONTROLS
1511 static float spacingWidth = 3.0;
1512 - (IBAction)imageSpacingChanged: (id)sender;
1513 {
1514     spacingWidth = [sender floatValue];
1515     [fOutlineView setNeedsDisplay: YES];
1516 }
1517 - (IBAction)indentChanged: (id)sender
1518 {
1519     [fOutlineView setIndentationPerLevel: [sender floatValue]];
1520     [fOutlineView setNeedsDisplay: YES];
1521 }
1522 #endif
1523
1524
1525 #pragma mark -
1526 #pragma mark Toolbar
1527
1528 //------------------------------------------------------------------------------------
1529 // setupToolbar
1530 //------------------------------------------------------------------------------------
1531 - (void)setupToolbar
1532 {
1533     // Create a new toolbar instance, and attach it to our window 
1534     NSToolbar *toolbar = [[[NSToolbar alloc] initWithIdentifier: HBQueueToolbar] autorelease];
1535     
1536     // Set up toolbar properties: Allow customization, give a default display mode, and remember state in user defaults 
1537     [toolbar setAllowsUserCustomization: YES];
1538     [toolbar setAutosavesConfiguration: YES];
1539     [toolbar setDisplayMode: NSToolbarDisplayModeIconAndLabel];
1540     
1541     // We are the delegate
1542     [toolbar setDelegate: self];
1543     
1544     // Attach the toolbar to our window 
1545     [fQueueWindow setToolbar: toolbar];
1546 }
1547
1548 //------------------------------------------------------------------------------------
1549 // toolbar:itemForItemIdentifier:willBeInsertedIntoToolbar:
1550 //------------------------------------------------------------------------------------
1551 - (NSToolbarItem *)toolbar:(NSToolbar *)toolbar
1552         itemForItemIdentifier:(NSString *)itemIdentifier
1553         willBeInsertedIntoToolbar:(BOOL)flag
1554 {
1555     // Required delegate method: Given an item identifier, this method returns an item.
1556     // The toolbar will use this method to obtain toolbar items that can be displayed
1557     // in the customization sheet, or in the toolbar itself.
1558     
1559     NSToolbarItem *toolbarItem = nil;
1560     
1561     if ([itemIdentifier isEqual: HBQueueStartCancelToolbarIdentifier])
1562     {
1563         toolbarItem = [[[NSToolbarItem alloc] initWithItemIdentifier: itemIdentifier] autorelease];
1564                 
1565         // Set the text label to be displayed in the toolbar and customization palette 
1566                 [toolbarItem setLabel: @"Start"];
1567                 [toolbarItem setPaletteLabel: @"Start/Cancel"];
1568                 
1569                 // Set up a reasonable tooltip, and image
1570                 [toolbarItem setToolTip: @"Start Encoding"];
1571                 [toolbarItem setImage: [NSImage imageNamed: @"Play"]];
1572                 
1573                 // Tell the item what message to send when it is clicked 
1574                 [toolbarItem setTarget: self];
1575                 [toolbarItem setAction: @selector(toggleStartCancel:)];
1576         }
1577     
1578     if ([itemIdentifier isEqual: HBQueuePauseResumeToolbarIdentifier])
1579     {
1580         toolbarItem = [[[NSToolbarItem alloc] initWithItemIdentifier: itemIdentifier] autorelease];
1581                 
1582         // Set the text label to be displayed in the toolbar and customization palette 
1583                 [toolbarItem setLabel: @"Pause"];
1584                 [toolbarItem setPaletteLabel: @"Pause/Resume"];
1585                 
1586                 // Set up a reasonable tooltip, and image
1587                 [toolbarItem setToolTip: @"Pause Encoding"];
1588                 [toolbarItem setImage: [NSImage imageNamed: @"Pause"]];
1589                 
1590                 // Tell the item what message to send when it is clicked 
1591                 [toolbarItem setTarget: self];
1592                 [toolbarItem setAction: @selector(togglePauseResume:)];
1593         }
1594     
1595 #if !HB_OUTLINE_QUEUE
1596     else if ([itemIdentifier isEqual: HBShowDetailToolbarIdentifier])
1597     {
1598         toolbarItem = [[[NSToolbarItem alloc] initWithItemIdentifier: itemIdentifier] autorelease];
1599                 
1600         // Set the text label to be displayed in the toolbar and customization palette 
1601                 [toolbarItem setLabel: @"Detail"];
1602                 [toolbarItem setPaletteLabel: @"Detail"];
1603                 
1604                 // Set up a reasonable tooltip, and image
1605                 [toolbarItem setToolTip: @"Displays detailed information in the queue"];
1606                 [toolbarItem setImage: [NSImage imageNamed: @"Detail"]];
1607                 
1608                 // Tell the item what message to send when it is clicked 
1609                 [toolbarItem setTarget: self];
1610                 [toolbarItem setAction: fShowsDetail ? @selector(hideDetail:) : @selector(showDetail:)];
1611         }
1612     
1613     else if ([itemIdentifier isEqual: HBShowGroupsToolbarIdentifier])
1614     {
1615         toolbarItem = [[[NSToolbarItem alloc] initWithItemIdentifier: itemIdentifier] autorelease];
1616                 
1617 /*
1618         // Set the text label to be displayed in the toolbar and customization palette 
1619                 [toolbarItem setLabel: @"Passes"];
1620                 [toolbarItem setPaletteLabel: @"Passes"];
1621                 
1622                 // Set up a reasonable tooltip, and image
1623                 [toolbarItem setToolTip: @"Displays individual passes in the queue"];
1624                 [toolbarItem setImage: [NSImage imageNamed: @"Passes"]];
1625         
1626                 // Tell the item what message to send when it is clicked 
1627                 [toolbarItem setTarget: self];
1628                 [toolbarItem setAction: fShowsJobsAsGroups ? @selector(showJobsAsPasses:) : @selector(showJobsAsGroups:)];
1629 */
1630   
1631 // Various attempts at other button types in the toolbar. A matrix worked fine to display
1632 // a button for encodes & passes, but ultimately I decided to go with a single button
1633 // called "Passes" that toggles on or off. All these suffer from the fact taht you need
1634 // to override NSToolbarItem for them in order to validate their state.
1635                 [toolbarItem setLabel: @"View"];
1636                 [toolbarItem setPaletteLabel: @"View"];
1637
1638         NSButtonCell * buttonCell = [[[NSButtonCell alloc] initImageCell:nil] autorelease];
1639         [buttonCell setBezelStyle:NSShadowlessSquareBezelStyle];
1640         [buttonCell setButtonType:NSToggleButton];
1641         [buttonCell setBordered:NO];
1642         [buttonCell setImagePosition:NSImageOnly];
1643
1644         NSMatrix * matrix = [[[NSMatrix alloc] initWithFrame:NSMakeRect(0,0,54,25)
1645                 mode:NSRadioModeMatrix
1646                 prototype:buttonCell
1647                 numberOfRows:1
1648                 numberOfColumns:2] autorelease];
1649         [matrix setCellSize:NSMakeSize(27, 25)];
1650         [matrix setIntercellSpacing:NSMakeSize(0, 0)];
1651         [matrix selectCellAtRow:0 column:(fShowsJobsAsGroups ? 0 : 1)];
1652
1653         buttonCell = [matrix cellAtRow:0 column:0];
1654         [buttonCell setTitle:@""];
1655         [buttonCell setImage:[NSImage imageNamed: @"Encodes"]];
1656         [buttonCell setAlternateImage:[NSImage imageNamed: @"EncodesPressed"]];
1657                 [buttonCell setAction: @selector(showJobsAsGroups:)];
1658                 [matrix setToolTip: @"Displays encodes in the queue" forCell:buttonCell];
1659
1660         buttonCell = [matrix cellAtRow:0 column:1];
1661         [buttonCell setTitle:@""];
1662         [buttonCell setImage:[NSImage imageNamed: @"Passes"]];
1663         [buttonCell setAlternateImage:[NSImage imageNamed: @"PassesPressed"]];
1664                 [buttonCell setAction: @selector(showJobsAsPasses:)];
1665                 [matrix setToolTip: @"Displays individual passes in the queue" forCell:buttonCell];
1666
1667         [toolbarItem setMinSize: [matrix frame].size];
1668         [toolbarItem setMaxSize: [matrix frame].size];
1669                 [toolbarItem setView: matrix];
1670
1671 /*
1672         NSSegmentedControl * segControl = [[[NSSegmentedControl alloc] initWithFrame:NSMakeRect(0,0,20,20)] autorelease];
1673         [[segControl cell] setControlSize:NSSmallControlSize];
1674         [segControl setSegmentCount:2];
1675         [segControl setLabel:@"Encodes" forSegment:0];
1676         [segControl setLabel:@"Passes" forSegment:1];
1677         [segControl setImage:[NSImage imageNamed:@"Delete"] forSegment:0];
1678         [segControl setImage:[NSImage imageNamed:@"Delete"] forSegment:1];
1679         [segControl setSelectedSegment: (fShowsJobsAsGroups ? 0 : 1)];
1680         [segControl sizeToFit];
1681         [toolbarItem setMinSize: [segControl frame].size];
1682         [toolbarItem setMaxSize: [segControl frame].size];
1683                 [toolbarItem setView: segControl];
1684 */
1685
1686 /*
1687         NSButton * button = [[[NSButton alloc] initWithFrame:NSMakeRect(0,0,32,32)] autorelease];
1688         [button setButtonType:NSSwitchButton];
1689         [button setTitle:@""];
1690         [button setState: fShowsJobsAsGroups ? NSOnState : NSOffState];
1691         [toolbarItem setMinSize: NSMakeSize(20,20)];
1692         [toolbarItem setMaxSize: NSMakeSize(20,20)];
1693                 [toolbarItem setView: button];
1694                 
1695                 // Tell the item what message to send when it is clicked 
1696                 [toolbarItem setTarget: self];
1697                 [toolbarItem setAction: @selector(jobGroupsChanged:)];
1698 */
1699         }
1700 #endif
1701     
1702     return toolbarItem;
1703 }
1704
1705 //------------------------------------------------------------------------------------
1706 // toolbarDefaultItemIdentifiers:
1707 //------------------------------------------------------------------------------------
1708 - (NSArray *) toolbarDefaultItemIdentifiers: (NSToolbar *) toolbar
1709 {
1710     // Required delegate method: Returns the ordered list of items to be shown in the
1711     // toolbar by default.
1712     
1713     return [NSArray arrayWithObjects:
1714         HBQueueStartCancelToolbarIdentifier,
1715         HBQueuePauseResumeToolbarIdentifier,
1716 #if !HB_OUTLINE_QUEUE
1717                 NSToolbarSeparatorItemIdentifier,
1718                 HBShowGroupsToolbarIdentifier,
1719         HBShowDetailToolbarIdentifier,
1720 #endif
1721         nil];
1722 }
1723
1724 //------------------------------------------------------------------------------------
1725 // toolbarAllowedItemIdentifiers:
1726 //------------------------------------------------------------------------------------
1727 - (NSArray *) toolbarAllowedItemIdentifiers: (NSToolbar *) toolbar
1728 {
1729     // Required delegate method: Returns the list of all allowed items by identifier.
1730     // By default, the toolbar does not assume any items are allowed, even the
1731     // separator. So, every allowed item must be explicitly listed.
1732
1733     return [NSArray arrayWithObjects:
1734         HBQueueStartCancelToolbarIdentifier,
1735         HBQueuePauseResumeToolbarIdentifier,
1736 #if !HB_OUTLINE_QUEUE
1737                 HBShowGroupsToolbarIdentifier,
1738         HBShowDetailToolbarIdentifier,
1739 #endif
1740                 NSToolbarCustomizeToolbarItemIdentifier,
1741                 NSToolbarFlexibleSpaceItemIdentifier,
1742         NSToolbarSpaceItemIdentifier,
1743                 NSToolbarSeparatorItemIdentifier,
1744         nil];
1745 }
1746
1747 //------------------------------------------------------------------------------------
1748 // validateToolbarItem:
1749 //------------------------------------------------------------------------------------
1750 - (BOOL) validateToolbarItem: (NSToolbarItem *) toolbarItem
1751 {
1752     // Optional method: This message is sent to us since we are the target of some
1753     // toolbar item actions.
1754
1755     if (!fHandle) return NO;
1756
1757     BOOL enable = NO;
1758
1759     hb_state_t s;
1760     hb_get_state2 (fHandle, &s);
1761
1762     if ([[toolbarItem itemIdentifier] isEqual: HBQueueStartCancelToolbarIdentifier])
1763     {
1764         if ((s.state == HB_STATE_PAUSED) || (s.state == HB_STATE_WORKING) || (s.state == HB_STATE_MUXING))
1765         {
1766             enable = YES;
1767             [toolbarItem setImage:[NSImage imageNamed: @"Stop"]];
1768                         [toolbarItem setLabel: @"Stop"];
1769                         [toolbarItem setToolTip: @"Stop Encoding"];
1770         }
1771
1772         else if (hb_count(fHandle) > 0)
1773         {
1774             enable = YES;
1775             [toolbarItem setImage:[NSImage imageNamed: @"Play"]];
1776                         [toolbarItem setLabel: @"Start"];
1777                         [toolbarItem setToolTip: @"Start Encoding"];
1778         }
1779
1780         else
1781         {
1782             enable = NO;
1783             [toolbarItem setImage:[NSImage imageNamed: @"Play"]];
1784                         [toolbarItem setLabel: @"Start"];
1785                         [toolbarItem setToolTip: @"Start Encoding"];
1786         }
1787         }
1788     
1789     if ([[toolbarItem itemIdentifier] isEqual: HBQueuePauseResumeToolbarIdentifier])
1790     {
1791         if (s.state == HB_STATE_PAUSED)
1792         {
1793             enable = YES;
1794             [toolbarItem setImage:[NSImage imageNamed: @"Play"]];
1795                         [toolbarItem setLabel: @"Resume"];
1796                         [toolbarItem setToolTip: @"Resume Encoding"];
1797        }
1798         
1799         else if ((s.state == HB_STATE_WORKING) || (s.state == HB_STATE_MUXING))
1800         {
1801             enable = YES;
1802             [toolbarItem setImage:[NSImage imageNamed: @"Pause"]];
1803                         [toolbarItem setLabel: @"Pause"];
1804                         [toolbarItem setToolTip: @"Pause Encoding"];
1805         }
1806         else
1807         {
1808             enable = NO;
1809             [toolbarItem setImage:[NSImage imageNamed: @"Pause"]];
1810                         [toolbarItem setLabel: @"Pause"];
1811                         [toolbarItem setToolTip: @"Pause Encoding"];
1812         }
1813         }
1814     
1815 #if !HB_OUTLINE_QUEUE
1816     else if ([[toolbarItem itemIdentifier] isEqual: HBShowGroupsToolbarIdentifier])
1817     {
1818         enable = hb_count(fHandle) > 0;
1819                 [toolbarItem setAction: fShowsJobsAsGroups ? @selector(showJobsAsPasses:) : @selector(showJobsAsGroups:)];
1820         if (fShowsJobsAsGroups)
1821         {
1822             [toolbarItem setImage: [NSImage imageNamed: @"Passes"]];
1823             [toolbarItem setToolTip: @"Displays individual passes in the queue"];
1824         }
1825         else
1826         {
1827             [toolbarItem setImage: [NSImage imageNamed: @"PassesPressed"]];
1828             [toolbarItem setToolTip: @"Displays encodes in the queue"];
1829         }
1830     }
1831     
1832     else if ([[toolbarItem itemIdentifier] isEqual: HBShowDetailToolbarIdentifier])
1833     {
1834         enable = hb_count(fHandle) > 0;
1835                 [toolbarItem setAction: fShowsDetail ? @selector(hideDetail:) : @selector(showDetail:)];
1836         if (fShowsDetail)
1837         {
1838             [toolbarItem setImage: [NSImage imageNamed: @"DetailPressed"]];
1839             [toolbarItem setToolTip: @"Hides detailed information in the queue"];
1840         }
1841         else
1842         {
1843             [toolbarItem setImage: [NSImage imageNamed: @"Detail"]];
1844             [toolbarItem setToolTip: @"Displays detailed information in the queue"];
1845         }
1846     }
1847 #endif
1848
1849         return enable;
1850 }
1851
1852 #pragma mark -
1853
1854 //------------------------------------------------------------------------------------
1855 // awakeFromNib
1856 //------------------------------------------------------------------------------------
1857 - (void)awakeFromNib
1858 {
1859     [self setupToolbar];
1860     
1861     if (![fQueueWindow setFrameUsingName:@"Queue"])
1862         [fQueueWindow center];
1863     [fQueueWindow setFrameAutosaveName: @"Queue"];
1864     [fQueueWindow setExcludedFromWindowsMenu:YES];
1865
1866 #if HB_OUTLINE_QUEUE
1867     [[fOutlineView enclosingScrollView] setHidden: NO];
1868 #else
1869     [[fTaskView enclosingScrollView] setHidden: NO];
1870 #endif
1871
1872 #if HB_QUEUE_DRAGGING
1873   #if HB_OUTLINE_QUEUE
1874     [fOutlineView registerForDraggedTypes: [NSArray arrayWithObject:HBQueueDataType] ];
1875   #else
1876     [fTaskView registerForDraggedTypes: [NSArray arrayWithObject:HBQueueDataType] ];
1877   #endif
1878 #endif
1879
1880 #if HB_OUTLINE_QUEUE
1881     // Don't allow autoresizing of main column, else the "delete" column will get
1882     // pushed out of view.
1883     [fOutlineView setAutoresizesOutlineColumn: NO];
1884     [fOutlineView setIndentationPerLevel:21];
1885 #endif
1886 #if HB_OUTLINE_METRIC_CONTROLS
1887     [fIndentation setHidden: NO];
1888     [fSpacing setHidden: NO];
1889     [fIndentation setIntValue:[fOutlineView indentationPerLevel]];  // debug
1890     [fSpacing setIntValue:3];       // debug
1891 #endif
1892
1893     // Show/hide UI elements
1894     [self setShowsDetail:fShowsDetail];
1895     [self setShowsJobsAsGroups:fShowsJobsAsGroups];
1896     [self showCurrentJobPane:NO];
1897 }
1898
1899
1900 //------------------------------------------------------------------------------------
1901 // windowWillClose
1902 //------------------------------------------------------------------------------------
1903 - (void)windowWillClose:(NSNotification *)aNotification
1904 {
1905     [[NSUserDefaults standardUserDefaults] setBool:NO forKey:@"QueueWindowIsOpen"];
1906 }
1907
1908 #pragma mark -
1909 #pragma mark NSTableView delegate
1910
1911 //------------------------------------------------------------------------------------
1912 // NSTableView delegate
1913 //------------------------------------------------------------------------------------
1914 - (int)numberOfRowsInTableView: (NSTableView *)aTableView
1915 {
1916 #if HB_UNI_QUEUE
1917     int numItems = hb_current_job(fHandle) ? 1 : 0;
1918     if (fShowsJobsAsGroups)
1919         return numItems + hb_group_count(fHandle);
1920     else
1921         return numItems + hb_count(fHandle);
1922 #else
1923     if (fShowsJobsAsGroups)
1924         return hb_group_count(fHandle);
1925     else
1926         return hb_count(fHandle);
1927 #endif
1928 }
1929
1930 //------------------------------------------------------------------------------------
1931 // NSTableView delegate
1932 //------------------------------------------------------------------------------------
1933 - (id)tableView: (NSTableView *)aTableView
1934       objectValueForTableColumn: (NSTableColumn *)aTableColumn
1935                             row: (int)rowIndex
1936 {
1937     if (!fHandle)
1938         return @"";    // fatal error!
1939         
1940     hb_job_t * job = nil;
1941
1942 #if HB_UNI_QUEUE
1943     // Looking for the current job?
1944     int jobIndex = rowIndex;
1945     if (hb_current_job(fHandle))
1946     {
1947         if (rowIndex == 0)
1948             job = hb_current_job(fHandle);
1949         else
1950             jobIndex = rowIndex - 1;
1951     }
1952     
1953     if (!job)
1954     {
1955         if (fShowsJobsAsGroups)
1956             job = hb_group(fHandle, jobIndex);
1957         else
1958             job = hb_job(fHandle, jobIndex);
1959     }
1960 #else
1961     if (fShowsJobsAsGroups)
1962         job = hb_group(fHandle, rowIndex);
1963     else
1964         job = hb_job(fHandle, rowIndex);
1965 #endif
1966     
1967     if (!job)
1968         return @"";    // fatal error!
1969
1970     if ([[aTableColumn identifier] isEqualToString:@"desc"])
1971     {
1972         BOOL highlighted = [aTableView isRowSelected:rowIndex] && [[aTableView window] isKeyWindow] && ([[aTableView window] firstResponder] == aTableView);
1973         return [self attributedDescriptionForJob:job withTitle:YES withDetail:fShowsDetail withHighlighting:highlighted];    
1974     }
1975     
1976     else if ([[aTableColumn identifier] isEqualToString:@"delete"])
1977         return @"";
1978
1979     else if ([[aTableColumn identifier] isEqualToString:@"icon"])
1980         return fShowsJobsAsGroups ? [NSImage imageNamed:@"JobSmall"] : [self smallImageForPass: job->pass];
1981
1982     return @"";
1983 }
1984
1985 //------------------------------------------------------------------------------------
1986 // NSTableView delegate
1987 //------------------------------------------------------------------------------------
1988 - (void)tableView: (NSTableView *)aTableView
1989         willDisplayCell: (id)aCell
1990          forTableColumn: (NSTableColumn *)aTableColumn
1991                     row: (int)rowIndex
1992 {
1993     if ([[aTableColumn identifier] isEqualToString:@"delete"])
1994     {
1995         BOOL highlighted = [aTableView isRowSelected:rowIndex] && [[aTableView window] isKeyWindow] && ([[aTableView window] firstResponder] == aTableView);
1996         if (highlighted)
1997         {
1998             [aCell setImage:[NSImage imageNamed:@"DeleteHighlight"]];
1999             [aCell setAlternateImage:[NSImage imageNamed:@"DeleteHighlightPressed"]];
2000         }
2001         else
2002         {
2003             [aCell setImage:[NSImage imageNamed:@"Delete"]];
2004         }
2005     }
2006 }
2007
2008 //------------------------------------------------------------------------------------
2009 // NSTableView delegate
2010 //------------------------------------------------------------------------------------
2011 #if HB_UNI_QUEUE
2012 - (float)tableView:(NSTableView *)tableView heightOfRow:(int)row
2013 {
2014     if ((row == 0) && hb_current_job(fHandle))
2015         return HB_ROW_HEIGHT_ACTIVE_JOB;
2016     else 
2017         return fShowsDetail ? HB_ROW_HEIGHT_DETAIL : HB_ROW_HEIGHT_NO_DETAIL;
2018 }
2019 #endif
2020
2021 #if HB_QUEUE_DRAGGING
2022 - (BOOL)tableView:(NSTableView *)tv writeRowsWithIndexes:(NSIndexSet *)rowIndexes toPasteboard:(NSPasteboard*)pboard
2023 {
2024     // Copy the row numbers to the pasteboard.
2025     NSData *data = [NSKeyedArchiver archivedDataWithRootObject:rowIndexes];
2026     [pboard declareTypes:[NSArray arrayWithObject:HBQueueDataType] owner:self];
2027     [pboard setData:data forType:HBQueueDataType];
2028     return YES;
2029 }
2030 #endif
2031
2032 #if HB_QUEUE_DRAGGING
2033 - (NSDragOperation)tableView:(NSTableView*)tv validateDrop:(id <NSDraggingInfo>)info proposedRow:(int)row proposedDropOperation:(NSTableViewDropOperation)op
2034 {
2035     // Add code here to validate the drop
2036     NSLog(@"validate Drop");
2037     return NSDragOperationEvery;
2038 }
2039 #endif
2040
2041 #if HB_QUEUE_DRAGGING
2042 - (BOOL)tableView:(NSTableView *)aTableView acceptDrop:(id <NSDraggingInfo>)info
2043             row:(int)row dropOperation:(NSTableViewDropOperation)operation
2044 {
2045     NSPasteboard* pboard = [info draggingPasteboard];
2046     NSData* rowData = [pboard dataForType:HBQueueDataType];
2047     NSIndexSet* rowIndexes = [NSKeyedUnarchiver unarchiveObjectWithData:rowData];
2048     int dragRow = [rowIndexes firstIndex];
2049  
2050     // Move the specified row to its new location...
2051     
2052     return YES;
2053 }
2054 #endif
2055
2056 #pragma mark -
2057 #pragma mark NSOutlineView delegate
2058
2059 #if HB_OUTLINE_QUEUE
2060
2061 - (id)outlineView:(NSOutlineView *)outlineView child:(int)index ofItem:(id)item
2062 {
2063     if (item == nil)
2064         return [fEncodes objectAtIndex:index];
2065     else
2066         return [item objectAtIndex:index];
2067 }
2068
2069 - (BOOL)outlineView:(NSOutlineView *)outlineView isItemExpandable:(id)item
2070 {
2071     return ! [item isKindOfClass:[HBJob class]];
2072 }
2073
2074 - (int)outlineView:(NSOutlineView *)outlineView numberOfChildrenOfItem:(id)item
2075 {
2076     if (item == nil)
2077         return [fEncodes count];
2078     else
2079         return [item count];
2080 }
2081
2082 - (float)outlineView:(NSOutlineView *)outlineView heightOfRowByItem:(id)item
2083 {
2084 /*
2085     if (fShowsDetail && [item isKindOfClass:[HBJob class]] && ([item job]->pass != -1))
2086         return HB_ROW_HEIGHT_DETAIL;
2087     else
2088         return HB_ROW_HEIGHT_NO_DETAIL;
2089 */
2090
2091     if (fShowsDetail && [item isKindOfClass:[HBJob class]])
2092     {
2093         hb_job_t * j = [item job];
2094         NSAssert (j != nil, @"job is nil");
2095         if (j->pass == -1)
2096             return HB_ROW_HEIGHT_INDEPTH_PASS;
2097         else if (j->pass == 1)
2098             return HB_ROW_HEIGHT_1ST_PASS;
2099         else if ((j->pass == 2) || (j->pass == 0))
2100             return HB_ROW_HEIGHT_2ND_PASS;
2101         else
2102             return HB_ROW_HEIGHT_TITLE_ONLY;    // unknown!
2103     }
2104     else
2105     {
2106         if ([outlineView isItemExpanded: item])
2107             return HB_ROW_HEIGHT_TITLE_WITH_SUMMARY;
2108         else
2109             return HB_ROW_HEIGHT_TITLE_ONLY;
2110     }
2111 }
2112
2113 - (id)outlineView:(NSOutlineView *)outlineView objectValueForTableColumn:(NSTableColumn *)tableColumn byItem:(id)item
2114 {
2115     BOOL highlighted = [outlineView isRowSelected:[outlineView rowForItem: item]] && [[outlineView window] isKeyWindow] && ([[outlineView window] firstResponder] == outlineView);
2116     if ([item isKindOfClass:[HBJob class]])
2117     {
2118         if ([[tableColumn identifier] isEqualToString:@"desc"])
2119         {
2120             hb_job_t * job = [item job];
2121             switch (job->pass)
2122             {
2123                 case -1:  // in-depth scan
2124                     return [self attributedDescriptionForJob:job
2125                                     withTitle: NO
2126                                  withPassName: YES
2127                                withFormatInfo: NO
2128                               withDestination: NO
2129                               withPictureInfo: NO
2130                                 withVideoInfo: NO
2131                                  withx264Info: NO
2132                                 withAudioInfo: NO
2133                              withHighlighting: highlighted];
2134                     break;
2135                 
2136                 case 1:  // video 1st pass
2137                     return [self attributedDescriptionForJob:job
2138                                     withTitle: NO
2139                                  withPassName: YES
2140                                withFormatInfo: NO
2141                               withDestination: NO
2142                               withPictureInfo: YES
2143                                 withVideoInfo: YES
2144                                  withx264Info: YES
2145                                 withAudioInfo: NO
2146                              withHighlighting: highlighted];
2147                     break;
2148                 
2149                 case 0:  // single pass
2150                 case 2:  // video 2nd pass + audio
2151                     return [self attributedDescriptionForJob:job
2152                                     withTitle: NO
2153                                  withPassName: YES
2154                                withFormatInfo: NO
2155                               withDestination: NO
2156                               withPictureInfo: YES
2157                                 withVideoInfo: YES
2158                                  withx264Info: YES
2159                                 withAudioInfo: YES
2160                              withHighlighting: highlighted];
2161                     break;
2162                 
2163                 default:
2164                     return @"unknown";
2165             }
2166             
2167         }
2168     }
2169     
2170     else
2171     {
2172         hb_job_t * job = [[item objectAtIndex:0] job];
2173         if ([[tableColumn identifier] isEqualToString:@"desc"])
2174         {
2175             if ([fOutlineView isItemExpanded: item])
2176                 return [self attributedDescriptionForJob:job
2177                                         withTitle: YES
2178                                      withPassName: NO
2179                                    withFormatInfo: YES
2180                                   withDestination: YES
2181                                   withPictureInfo: NO
2182                                     withVideoInfo: NO
2183                                      withx264Info: NO
2184                                     withAudioInfo: NO
2185                                  withHighlighting: highlighted];
2186             else
2187                 return [self attributedDescriptionForJob:job
2188                                         withTitle: YES
2189                                      withPassName: NO
2190                                    withFormatInfo: NO
2191                                   withDestination: NO
2192                                   withPictureInfo: NO
2193                                     withVideoInfo: NO
2194                                      withx264Info: NO
2195                                     withAudioInfo: NO
2196                                  withHighlighting: highlighted];
2197         }
2198         
2199     }
2200
2201     return @"";
2202 }
2203
2204 - (void)outlineView:(NSOutlineView *)outlineView willDisplayCell:(id)cell forTableColumn:(NSTableColumn *)tableColumn item:(id)item
2205 {
2206     if ([[tableColumn identifier] isEqualToString:@"desc"])
2207     {
2208 #if HB_OUTLINE_METRIC_CONTROLS
2209         NSSize theSize = [cell imageSpacing];
2210         theSize.width = spacingWidth;
2211         [cell setImageSpacing: theSize];
2212 #endif
2213         
2214         // Set the image here since the value returned from outlineView:objectValueForTableColumn: didn't specify the image part
2215         if ([item isKindOfClass:[HBJob class]])
2216             [cell setImage:[self smallImageForPass: [item job]->pass]];
2217         else
2218             [cell setImage:[NSImage imageNamed:@"JobSmall"]];
2219     }
2220     
2221     else if ([[tableColumn identifier] isEqualToString:@"delete"])
2222     {
2223         // The Delete action can only be applied for group items, not indivdual jobs.
2224         if ([item isKindOfClass:[HBJob class]])
2225         {
2226             [cell setEnabled: NO];
2227             [cell setImage: nil];
2228         }
2229         else
2230         {
2231             [cell setEnabled: YES];
2232             BOOL highlighted = [outlineView isRowSelected:[outlineView rowForItem: item]] && [[outlineView window] isKeyWindow] && ([[outlineView window] firstResponder] == outlineView);
2233             if (highlighted)
2234             {
2235                 [cell setImage:[NSImage imageNamed:@"DeleteHighlight"]];
2236                 [cell setAlternateImage:[NSImage imageNamed:@"DeleteHighlightPressed"]];
2237             }
2238             else
2239                 [cell setImage:[NSImage imageNamed:@"Delete"]];
2240         }
2241     }
2242 }
2243
2244 - (void)outlineView:(NSOutlineView *)outlineView willDisplayOutlineCell:(id)cell forTableColumn:(NSTableColumn *)tableColumn item:(id)item
2245 {
2246     // By default, the discolsure image gets centered vertically in the cell. We want
2247     // always at the top.
2248     if ([outlineView isItemExpanded: item])
2249         [cell setImagePosition: NSImageAbove];
2250     else
2251         [cell setImagePosition: NSImageOnly];
2252 }
2253
2254 #endif
2255
2256 @end