OSDN Git Service

MacGui: Move x264 advanced options code to a new class (HBAdvancedController) and...
[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
9 /**
10  * Returns the number of jobs groups in the queue.
11  * @param h Handle to hb_handle_t.
12  * @return Number of job groups.
13  */
14 static int hb_group_count(hb_handle_t * h)    
15 {
16     hb_job_t * job;
17     int count = 0;
18     int index = 0;
19     while( ( job = hb_job( h, index++ ) ) )
20     {
21         if (job->sequence_id == 0)
22             count++;
23     }
24     return count;
25 }
26
27 /**
28  * Returns handle to the first job in the i-th group within the job list.
29  * @param h Handle to hb_handle_t.
30  * @param i Index of group.
31  * @returns Handle to hb_job_t of desired job.
32  */
33 static hb_job_t * hb_group(hb_handle_t * h, int i)    
34 {
35     hb_job_t * job;
36     int count = 0;
37     int index = 0;
38     while( ( job = hb_job( h, index++ ) ) )
39     {
40         if (job->sequence_id == 0)
41         {
42             if (count == i)
43                 return job;
44             count++;
45         }
46     }
47     return NULL;
48 }
49
50 /**
51  * Removes a groups of jobs from the job list.
52  * @param h Handle to hb_handle_t.
53  * @param job Handle to the first job in the group.
54  */
55 static void hb_rem_group( hb_handle_t * h, hb_job_t * job )
56 {
57     // Find job in list
58     hb_job_t * j;
59     int index = 0;
60     while( ( j = hb_job( h, index ) ) )
61     {
62         if (j == job)
63         {
64             // Delete this job plus the following ones in the sequence
65             hb_rem( h, job );
66             while( ( j = hb_job( h, index ) ) && (j->sequence_id != 0) )
67                 hb_rem( h, j );
68             return;
69         }
70         else
71             index++;
72     }
73 }
74
75 /**
76  * Returns handle to the next job after the given job.
77  * @param h Handle to hb_handle_t.
78  * @param job Handle to the a job in the group.
79  * @returns Handle to hb_job_t of desired job or NULL if no such job.
80  */
81 static hb_job_t * hb_next_job( hb_handle_t * h, hb_job_t * job )
82 {
83     hb_job_t * j = NULL;
84     int index = 0;
85     while( ( j = hb_job( h, index++ ) ) )
86     {
87         if (j == job)
88             return hb_job( h, index+1 );
89     }
90     return NULL;
91 }
92
93 #pragma mark -
94
95 // Toolbar identifiers
96 static NSString*    HBQueueToolbar                            = @"HBQueueToolbar";
97 static NSString*    HBStartPauseResumeToolbarIdentifier       = @"HBStartPauseResumeToolbarIdentifier";
98 static NSString*    HBShowDetailToolbarIdentifier             = @"HBShowDetailToolbarIdentifier";
99 static NSString*    HBShowGroupsToolbarIdentifier             = @"HBShowGroupsToolbarIdentifier";
100
101
102 @implementation HBQueueController
103
104 //------------------------------------------------------------------------------------
105 // init
106 //------------------------------------------------------------------------------------
107 - (id)init
108 {
109     if (self = [super init])
110     {
111         // Our defaults
112         [[NSUserDefaults standardUserDefaults] registerDefaults:[NSDictionary dictionaryWithObjectsAndKeys:
113             @"NO",      @"QueueWindowIsOpen",
114             @"NO",      @"QueueShowsDetail",
115             @"YES",     @"QueueShowsJobsAsGroups",
116             nil]];
117
118         fShowsDetail = [[NSUserDefaults standardUserDefaults] boolForKey:@"QueueShowsDetail"];
119         fShowsJobsAsGroups = [[NSUserDefaults standardUserDefaults] boolForKey:@"QueueShowsJobsAsGroups"];
120
121     }
122     return self; 
123 }
124
125 //------------------------------------------------------------------------------------
126 // dealloc
127 //------------------------------------------------------------------------------------
128 - (void)dealloc
129 {
130     [fAnimation release];
131     
132     // clear the delegate so that windowWillClose is not attempted
133     if ([fQueueWindow delegate] == self)
134         [fQueueWindow setDelegate:nil];
135     
136     [super dealloc];
137 }
138
139 //------------------------------------------------------------------------------------
140 // Receive HB handle
141 //------------------------------------------------------------------------------------
142 - (void)setHandle: (hb_handle_t *)handle
143 {
144     fHandle = handle;
145 }
146
147 //------------------------------------------------------------------------------------
148 // Displays and brings the queue window to the front
149 //------------------------------------------------------------------------------------
150 - (IBAction) showQueueWindow: (id)sender
151 {
152     if (!fQueueWindow)
153     {
154         BOOL loadSucceeded = [NSBundle loadNibNamed:@"Queue" owner:self] && fQueueWindow;
155         NSAssert(loadSucceeded, @"Could not open Queue nib file");
156     }
157
158     [self updateQueueUI];
159     [self updateCurrentJobUI];
160
161     [fQueueWindow makeKeyAndOrderFront: self];
162
163     [[NSUserDefaults standardUserDefaults] setBool:YES forKey:@"QueueWindowIsOpen"];
164 }
165 //------------------------------------------------------------------------------------
166 // Show or hide the current job pane (fCurrentJobPane).
167 //------------------------------------------------------------------------------------
168 - (void) showCurrentJobPane: (BOOL)showPane
169 {
170     if (showPane != fCurrentJobHidden)
171         return;
172     
173     // Things to keep in mind:
174     // - When the current job pane is shown, it occupies the upper portion of the
175     //   window with the queue occupying the bottom portion of the window.
176     // - When the current job pane is hidden, it slides up and out of view.
177     //   NSView setHidden is NOT used. The queue pane is resized to occupy the full
178     //   window.
179     
180     NSRect windowFrame = [[fCurrentJobPane superview] frame];
181     NSRect queueFrame, jobFrame;
182     if (showPane)
183         NSDivideRect(windowFrame, &jobFrame, &queueFrame, NSHeight([fCurrentJobPane frame]), NSMaxYEdge);
184     else
185     {
186         queueFrame = windowFrame;
187         jobFrame = [fCurrentJobPane frame];
188         jobFrame.origin.y = NSHeight(windowFrame);
189     }
190     
191     // Move fCurrentJobPane
192     NSDictionary * dict1 = [NSDictionary dictionaryWithObjectsAndKeys:
193         fCurrentJobPane, NSViewAnimationTargetKey,
194         [NSValue valueWithRect:jobFrame], NSViewAnimationEndFrameKey,
195         nil];
196
197     // Resize fQueuePane
198     NSDictionary * dict2 = [NSDictionary dictionaryWithObjectsAndKeys:
199         fQueuePane, NSViewAnimationTargetKey,
200         [NSValue valueWithRect:queueFrame], NSViewAnimationEndFrameKey,
201         nil];
202
203     if (!fAnimation)
204         fAnimation = [[NSViewAnimation alloc] initWithViewAnimations:nil];
205
206     [fAnimation setViewAnimations:[NSArray arrayWithObjects:dict1, dict2, nil]];
207     [fAnimation setDuration:0.25];
208     [fAnimation setAnimationBlockingMode:NSAnimationBlocking]; // prevent user from resizing the window during an animation
209     [fAnimation startAnimation];
210     fCurrentJobHidden = !showPane;
211 }
212
213 //------------------------------------------------------------------------------------
214 // Enables or disables the display of detail information for each job.
215 //------------------------------------------------------------------------------------
216 - (void)setShowsDetail: (BOOL)showsDetail
217 {
218     fShowsDetail = showsDetail;
219     
220     [[NSUserDefaults standardUserDefaults] setBool:showsDetail forKey:@"QueueShowsDetail"];
221     [[NSUserDefaults standardUserDefaults] synchronize];
222
223     // clumsy - have to update UI
224     [fDetailCheckbox setState:showsDetail ? NSOnState : NSOffState];
225     
226     [fTaskView setRowHeight:showsDetail ? 110.0 : 17.0];
227     if ([fTaskView selectedRow] != -1)
228         [fTaskView scrollRowToVisible:[fTaskView selectedRow]];
229 }
230
231 //------------------------------------------------------------------------------------
232 // Enables or disables the grouping of job passes into one item in the UI.
233 //------------------------------------------------------------------------------------
234 - (void)setShowsJobsAsGroups: (BOOL)showsGroups
235 {
236     fShowsJobsAsGroups = showsGroups;
237     
238     [[NSUserDefaults standardUserDefaults] setBool:showsGroups forKey:@"QueueShowsJobsAsGroups"];
239     [[NSUserDefaults standardUserDefaults] synchronize];
240
241     // clumsy - have to update UI
242     [fJobGroupsCheckbox setState:showsGroups ? NSOnState : NSOffState];
243     
244     [self updateQueueUI];
245     if ([fTaskView selectedRow] != -1)
246         [fTaskView scrollRowToVisible:[fTaskView selectedRow]];
247 }
248
249 //------------------------------------------------------------------------------------
250 // Generates a multi-line text string that includes the job name on the first line
251 // followed by details of the job on subsequent lines. If the text is to be drawn as
252 // part of a highlighted cell, set isHighlighted to true. The returned string may
253 // contain multiple fonts and paragraph formating.
254 //------------------------------------------------------------------------------------
255 - (NSAttributedString *)attributedDescriptionForJob: (hb_job_t *)job
256                                          withDetail: (BOOL)detail
257                                    withHighlighting: (BOOL)highlighted
258 {
259     NSMutableAttributedString * finalString;   // the return value
260     NSAttributedString* anAttributedString;    // a temp string for building up attributed substrings
261     NSMutableString* aMutableString;           // a temp string for non-attributed substrings
262     hb_title_t * title = job->title;
263     
264     NSMutableParagraphStyle *ps = [[NSParagraphStyle defaultParagraphStyle] mutableCopy];
265     [ps setLineBreakMode:NSLineBreakByClipping];
266
267     static NSDictionary* detailAttribute = [[NSDictionary dictionaryWithObjectsAndKeys:
268                 [NSFont systemFontOfSize:10.0], NSFontAttributeName,
269                 [NSColor darkGrayColor], NSForegroundColorAttributeName,
270                 ps, NSParagraphStyleAttributeName,
271                 nil] retain];
272     static NSDictionary* detailHighlightedAttribute = [[NSDictionary dictionaryWithObjectsAndKeys:
273                 [NSFont systemFontOfSize:10.0], NSFontAttributeName,
274                 [NSColor whiteColor], NSForegroundColorAttributeName,
275                 ps, NSParagraphStyleAttributeName,
276                 nil] retain];
277     static NSDictionary* titleAttribute = [[NSDictionary dictionaryWithObject:
278                 [NSFont systemFontOfSize:[NSFont systemFontSize]] forKey:NSFontAttributeName] retain];
279
280     finalString = [[[NSMutableAttributedString alloc] init] autorelease];
281
282     // Title, in bold
283     // Show the name of the source Note: use title->name instead of title->dvd since
284     // name is just the chosen folder, instead of dvd which is the full path
285     anAttributedString = [[[NSAttributedString alloc] initWithString:[NSString stringWithUTF8String:title->name] attributes:titleAttribute] autorelease];
286     [finalString appendAttributedString:anAttributedString];
287
288     if (!detail)
289         return finalString;
290         
291     // Other info in plain
292     aMutableString = [NSMutableString stringWithCapacity:200];
293     
294     BOOL jobGroups = [[NSUserDefaults standardUserDefaults] boolForKey:@"QueueShowsJobsAsGroups"];
295
296     // The subtitle scan doesn't contain all the stuff we need (like x264opts).
297     // So grab the next job in the group for display purposes.
298     if (jobGroups && job->pass == -1)
299     {
300         // When job is the one currently being processed, then the next in its group
301         // is the the first job in the queue.
302         hb_job_t * nextjob;
303         if (job == hb_current_job(fHandle))
304             nextjob = hb_job(fHandle, 0);
305         else
306             nextjob = hb_next_job(fHandle, job);
307         if (nextjob)    // Overly cautious in case there is no next job!
308             job = nextjob;
309     }
310
311     NSString * chapterString = (job->chapter_start == job->chapter_end) ?
312             [NSString stringWithFormat:@"Chapter %d", job->chapter_start] :
313             [NSString stringWithFormat:@"Chapters %d through %d", job->chapter_start, job->chapter_end];
314     
315     // Scan pass
316     if (job->pass == -1)
317     {
318         [aMutableString appendString:[NSString stringWithFormat:
319                 @"\nTitle %d, %@, Pass: Scan", title->index, chapterString]];
320     }
321
322     // Normal pass
323     else
324     {
325         if (jobGroups)
326             [aMutableString appendString:[NSString stringWithFormat:
327                     @"\nTitle %d, %@, %d-Pass",
328                     title->index, chapterString, MIN( 2, job->pass + 1 )]];
329         else
330             [aMutableString appendString:[NSString stringWithFormat:
331                     @"\nTitle %d, %@, Pass %d of %d",
332                     title->index, chapterString, MAX( 1, job->pass ), MIN( 2, job->pass + 1 )]];
333         
334
335         NSString * jobFormat;
336         NSString * jobPictureDetail;
337         NSString * jobVideoDetail;
338         NSString * jobVideoCodec;
339         NSString * jobVideoQuality;
340         NSString * jobAudioDetail;
341         NSString * jobAudioCodec;
342
343         /* Muxer settings (File Format in the gui) */
344         if (job->mux == 65536 || job->mux == 131072 || job->mux == 1048576)
345             jobFormat = @"MP4"; // HB_MUX_MP4,HB_MUX_PSP,HB_MUX_IPOD
346         else if (job->mux == 262144)
347             jobFormat = @"AVI"; // HB_MUX_AVI
348         else if (job->mux == 524288)
349             jobFormat = @"OGM"; // HB_MUX_OGM
350         else if (job->mux == 2097152)
351             jobFormat = @"MKV"; // HB_MUX_MKV
352         else
353             jobFormat = @"unknown";
354         
355         // 2097152
356         /* Video Codec settings (Encoder in the gui) */
357         if (job->vcodec == 1)
358             jobVideoCodec = @"FFmpeg"; // HB_VCODEC_FFMPEG
359         else if (job->vcodec == 2)
360             jobVideoCodec = @"XviD"; // HB_VCODEC_XVID
361         else if (job->vcodec == 4)
362         {
363             /* Deterimine for sure how we are now setting iPod uuid atom */
364             if (job->h264_level) // We are encoding for iPod
365                 jobVideoCodec = @"x264 (H.264 iPod)"; // HB_VCODEC_X264    
366             else
367                 jobVideoCodec = @"x264 (H.264 Main)"; // HB_VCODEC_X264
368         }
369         else
370             jobVideoCodec = @"unknown";
371         
372         /* Audio Codecs (Second half of Codecs in the gui) */
373         if (job->acodec == 256)
374             jobAudioCodec = @"AAC"; // HB_ACODEC_FAAC
375         else if (job->acodec == 512)
376             jobAudioCodec = @"MP3"; // HB_ACODEC_LAME
377         else if (job->acodec == 1024)
378             jobAudioCodec = @"Vorbis"; // HB_ACODEC_VORBIS
379         else if (job->acodec == 2048)
380             jobAudioCodec = @"AC3"; // HB_ACODEC_AC3
381         else
382             jobAudioCodec = @"unknown";
383         /* Show Basic File info */
384         if (job->chapter_markers == 1)
385             [aMutableString appendString:[NSString stringWithFormat:@"\nFormat: %@ Container, %@ Video + %@ Audio, Chapter Markers", jobFormat, jobVideoCodec, jobAudioCodec]];
386         else
387             [aMutableString appendString:[NSString stringWithFormat:@"\nFormat: %@ Container, %@ Video + %@ Audio", jobFormat, jobVideoCodec, jobAudioCodec]];
388             
389         /*Picture info*/
390         /*integers for picture values deinterlace, crop[4], keep_ratio, grayscale, pixel_ratio, pixel_aspect_width, pixel_aspect_height,
391          maxWidth, maxHeight */
392         if (job->pixel_ratio == 1)
393         {
394             int titlewidth = title->width - job->crop[2] - job->crop[3];
395             int displayparwidth = titlewidth * job->pixel_aspect_width / job->pixel_aspect_height;
396             int displayparheight = title->height - job->crop[0] - job->crop[1];
397             jobPictureDetail = [NSString stringWithFormat:@"Picture: %dx%d (%dx%d Anamorphic)", displayparwidth, displayparheight, job->width, displayparheight];
398         }
399         else
400             jobPictureDetail = [NSString stringWithFormat:@"Picture: %dx%d", job->width, job->height];
401         if (job->keep_ratio == 1)
402             jobPictureDetail = [jobPictureDetail stringByAppendingString:@" Keep Aspect Ratio"];
403         
404         if (job->grayscale == 1)
405             jobPictureDetail = [jobPictureDetail stringByAppendingString:@", Grayscale"];
406         
407         if (job->deinterlace == 1)
408             jobPictureDetail = [jobPictureDetail stringByAppendingString:@", Deinterlace"];
409         /* Show Picture info */    
410         [aMutableString appendString:[NSString stringWithFormat:@"\n%@", jobPictureDetail]];
411         
412         /* Detailed Video info */
413         if (job->vquality <= 0 || job->vquality >= 1)
414             jobVideoQuality =[NSString stringWithFormat:@"%d kbps", job->vbitrate];
415         else
416         {
417             NSNumber * vidQuality;
418             vidQuality = [NSNumber numberWithInt:job->vquality * 100];
419             /* this is screwed up kind of. Needs to be formatted properly */
420             if (job->crf == 1)
421                 jobVideoQuality =[NSString stringWithFormat:@"%@%% CRF", vidQuality];            
422             else
423                 jobVideoQuality =[NSString stringWithFormat:@"%@%% CQP", vidQuality];
424         }
425         
426         if (job->vrate_base == 1126125)
427         {
428             /* NTSC FILM 23.976 */
429             jobVideoDetail = [NSString stringWithFormat:@"Video: %@, %@, 23.976 fps", jobVideoCodec, jobVideoQuality];
430         }
431         else if (job->vrate_base == 900900)
432         {
433             /* NTSC 29.97 */
434             jobVideoDetail = [NSString stringWithFormat:@"Video: %@, %@, 29.97 fps", jobVideoCodec, jobVideoQuality];
435         }
436         else
437         {
438             /* Everything else */
439             jobVideoDetail = [NSString stringWithFormat:@"Video: %@, %@, %d fps", jobVideoCodec, jobVideoQuality, job->vrate / job->vrate_base];
440         }
441         
442         /* Add the video detail string to the job filed in the window */
443         [aMutableString appendString:[NSString stringWithFormat:@"\n%@", jobVideoDetail]];
444         
445         /* if there is an x264 option string, lets add it here*/
446         /*NOTE: Due to size, lets get this in a tool tip*/
447         
448         if (job->x264opts)
449             [aMutableString appendString:[NSString stringWithFormat:@"\nx264 Options: %@", [NSString stringWithUTF8String:job->x264opts]]];
450         
451         /* Audio Detail */
452         if ([jobAudioCodec isEqualToString: @"AC3"])
453             jobAudioDetail = [NSString stringWithFormat:@"Audio: %@, Pass-Through", jobAudioCodec];
454         else
455             jobAudioDetail = [NSString stringWithFormat:@"Audio: %@, %d kbps, %d Hz", jobAudioCodec, job->abitrate, job->arate];
456         
457         /* we now get the audio mixdown info for each of the two gui audio tracks */
458         /* lets do it the long way here to get a handle on things.
459             Hardcoded for two tracks for gui: audio_mixdowns[i] audio_mixdowns[i] */
460         int ai; // counter for each audios [] , macgui only allows for two audio tracks currently
461         for( ai = 0; ai < 2; ai++ )
462         {
463             if (job->audio_mixdowns[ai] == HB_AMIXDOWN_MONO)
464                 jobAudioDetail = [jobAudioDetail stringByAppendingString:[NSString stringWithFormat:@", Track %d: Mono",ai + 1]];
465             if (job->audio_mixdowns[ai] == HB_AMIXDOWN_STEREO)
466                 jobAudioDetail = [jobAudioDetail stringByAppendingString:[NSString stringWithFormat:@", Track %d: Stereo",ai + 1]];
467             if (job->audio_mixdowns[ai] == HB_AMIXDOWN_DOLBY)
468                 jobAudioDetail = [jobAudioDetail stringByAppendingString:[NSString stringWithFormat:@", Track %d: Dolby Surround",ai + 1]];
469             if (job->audio_mixdowns[ai] == HB_AMIXDOWN_DOLBYPLII)
470                 jobAudioDetail = [jobAudioDetail stringByAppendingString:[NSString stringWithFormat:@", Track %d: Dolby Pro Logic II",ai + 1]];
471             if (job->audio_mixdowns[ai] == HB_AMIXDOWN_6CH)
472                 jobAudioDetail = [jobAudioDetail stringByAppendingString:[NSString stringWithFormat:@", Track %d: 6-channel discreet",ai + 1]];
473         }
474         
475         /* Add the Audio detail string to the job filed in the window */
476         [aMutableString appendString:[NSString stringWithFormat: @"\n%@", jobAudioDetail]];
477         
478         /*Destination Field */
479         [aMutableString appendString:[NSString stringWithFormat:@"\nDestination: %@", [NSString stringWithUTF8String:job->file]]];
480     }
481     
482     anAttributedString = [[[NSAttributedString alloc] initWithString:aMutableString attributes:highlighted ? detailHighlightedAttribute : detailAttribute] autorelease];
483     [finalString appendAttributedString:anAttributedString];
484
485             
486     return finalString;
487 }
488
489 //------------------------------------------------------------------------------------
490 // Generate string to display in UI.
491 //------------------------------------------------------------------------------------
492 - (NSString *) progressStatusStringForJob: (hb_job_t *)job state: (hb_state_t *)s
493 {
494     if (s->state == HB_STATE_WORKING)
495     {
496         NSString * msg;
497         if (job->pass == -1)
498             msg = NSLocalizedString( @"Analyzing subtitles", nil );
499         else if (job->pass == 1)
500             msg = NSLocalizedString( @"Analyzing video", nil );
501         else if ((job->pass == 0) ||  (job->pass == 2))
502             msg = NSLocalizedString( @"Encoding movie", nil );
503         else
504             return @""; // unknown condition!
505             
506         if( s->param.working.seconds > -1 )
507         {
508             return [NSString stringWithFormat:
509                 NSLocalizedString( @"%@ (%.2f fps, avg %.2f fps)", nil ),
510                 msg, s->param.working.rate_cur, s->param.working.rate_avg];
511         }
512         else
513             return msg;
514
515     }
516
517     else if (s->state == HB_STATE_MUXING)
518         return NSLocalizedString( @"Muxing", nil );
519
520     else if (s->state == HB_STATE_PAUSED)
521         return NSLocalizedString( @"Paused", nil );
522
523     else if (s->state == HB_STATE_WORKDONE)
524         return NSLocalizedString( @"Done", nil );
525     
526     return @"";
527 }
528
529 //------------------------------------------------------------------------------------
530 // Generate string to display in UI.
531 //------------------------------------------------------------------------------------
532 - (NSString *) progressTimeRemainingStringForJob: (hb_job_t *)job state: (hb_state_t *)s
533 {
534     if (s->state == HB_STATE_WORKING)
535     {
536         #define p s->param.working
537         if (p.seconds < 0)
538             return @"";
539         
540         // Minutes always needed
541         NSString * minutes;
542         if (p.minutes > 1)
543           minutes = [NSString stringWithFormat:NSLocalizedString( @"%d minutes ", nil ), p.minutes];
544         else if (p.minutes == 1)
545           minutes = NSLocalizedString( @"1 minute ", nil );
546         else
547           minutes = @"";
548         
549         if (p.hours >= 1)
550         {
551             NSString * hours;
552             if (p.hours > 1)
553               hours = [NSString stringWithFormat:NSLocalizedString( @"%d hours ", nil ), p.hours];
554             else
555               hours = NSLocalizedString( @"1 hour ", nil );
556
557             return [NSString stringWithFormat:NSLocalizedString( @"%@%@remaining", nil ), hours, minutes];
558         }
559         
560         else
561         {
562             NSString * seconds;
563             if (p.seconds > 1)
564               seconds = [NSString stringWithFormat:NSLocalizedString( @"%d seconds ", nil ), p.seconds];
565             else
566               seconds = NSLocalizedString( @"1 second ", nil );
567
568             return [NSString stringWithFormat:NSLocalizedString( @"%@%@remaining", nil ), minutes, seconds];
569         }
570
571 /* here is code that does it more like the Finder
572         if( p.seconds > -1 )
573         {
574             float estHours = (p.hours + (p.minutes / 60.0));
575             float estMinutes = (p.minutes + (p.seconds / 60.0));
576
577             if (estHours > 1.5)
578                 return [NSString stringWithFormat:NSLocalizedString( @"Time remaining: About %d hours", nil ), lrintf(estHours)];
579             else if (estHours > 0.983)    // 59 minutes
580                 return NSLocalizedString( @"Time remaining: About 1 hour", nil );
581             else if (estMinutes > 1.5)
582                 return [NSString stringWithFormat:NSLocalizedString( @"Time remaining: About %d minutes", nil ), lrintf(estMinutes)];
583             else if (estMinutes > 0.983)    // 59 seconds
584                 return NSLocalizedString( @"Time remaining: About 1 minute", nil );
585             else if (p.seconds <= 5)
586                 return NSLocalizedString( @"Time remaining: Less than 5 seconds", nil );
587             else if (p.seconds <= 10)
588                 return NSLocalizedString( @"Time remaining: Less than 10 seconds", nil );
589             else
590                 return NSLocalizedString( @"Time remaining: Less than 1 minute", nil );
591         }
592         else
593             return NSLocalizedString( @"Time remaining: Calculating...", nil );
594 */
595         #undef p
596     }
597     
598     return @"";
599 }
600
601 //------------------------------------------------------------------------------------
602 // Refresh progress bar (fProgressBar) from current state.
603 //------------------------------------------------------------------------------------
604 - (void) updateProgressBarWithState: (hb_state_t *)s
605 {
606     if (s->state == HB_STATE_WORKING)
607     {
608         #define p s->param.working
609         [fProgressBar setIndeterminate:NO];
610
611         BOOL jobGroups = [[NSUserDefaults standardUserDefaults] boolForKey:@"QueueShowsJobsAsGroups"];
612         float progress_total = jobGroups ?
613                 100.0 * ( p.progress + p.job_cur - 1 ) / p.job_count :
614                 100.0 * p.progress;
615
616         [fProgressBar setDoubleValue:progress_total];
617         #undef p
618     }
619     
620     else if (s->state == HB_STATE_MUXING)
621     {
622         #define p s->param.muxing
623         [fProgressBar setIndeterminate:YES];
624         [fProgressBar startAnimation:nil];
625         #undef p
626     }
627
628     else if (s->state == HB_STATE_WORKDONE)
629     {
630         [fProgressBar setIndeterminate:NO];
631         [fProgressBar setDoubleValue:0.0];
632     }
633 }
634
635 //------------------------------------------------------------------------------------
636 // Refresh start/pause button (fStartPauseButton) from current state.
637 //------------------------------------------------------------------------------------
638 - (void) updateStartPauseButton
639 {
640
641 // ************* THIS METHOD CAN DISAPPEAR. THE BUTTON IS NOW HIDDEN AND CAN BE DELETED
642     if (!fHandle) return;
643
644     hb_state_t s;
645     hb_get_state2 (fHandle, &s);
646
647     if (s.state == HB_STATE_PAUSED)
648     {
649         [fStartPauseButton setEnabled:YES];
650 //        [fStartPauseButton setTitle:NSLocalizedString(@"Resume", nil)];
651         [fStartPauseButton setImage:[NSImage imageNamed: @"Play"]];
652    }
653     
654     else if ((s.state == HB_STATE_WORKING) || (s.state == HB_STATE_MUXING))
655     {
656         [fStartPauseButton setEnabled:YES];
657 //        [fStartPauseButton setTitle:NSLocalizedString(@"Pause", nil)];
658         [fStartPauseButton setImage:[NSImage imageNamed: @"Pause"]];
659     }
660
661     else if (hb_count(fHandle) > 0)
662     {
663         [fStartPauseButton setEnabled:YES];
664 //        [fStartPauseButton setTitle:NSLocalizedString(@"Start", nil)];
665         [fStartPauseButton setImage:[NSImage imageNamed: @"Play"]];
666     }
667
668     else
669     {
670         [fStartPauseButton setEnabled:NO];
671 //        [fStartPauseButton setTitle:NSLocalizedString(@"Start", nil)];
672         [fStartPauseButton setImage:[NSImage imageNamed: @"Play"]];
673     }
674 }
675
676 //------------------------------------------------------------------------------------
677 // Refresh queue count text field (fQueueCountField).
678 //------------------------------------------------------------------------------------
679 - (void)updateQueueCountField
680 {
681     NSString * msg;
682     int jobCount;
683     BOOL jobGroups = [[NSUserDefaults standardUserDefaults] boolForKey:@"QueueShowsJobsAsGroups"];
684     
685     if (jobGroups)
686     {
687         jobCount = fHandle ? hb_group_count(fHandle) : 0;
688         if (jobCount == 1)
689             msg = NSLocalizedString(@"1 pending encode", nil);
690         else
691             msg = [NSString stringWithFormat:NSLocalizedString(@"%d pending encodes", nil), jobCount];
692     }
693     else
694     {
695         jobCount = fHandle ? hb_count(fHandle) : 0;
696         if (jobCount == 1)
697             msg = NSLocalizedString(@"1 pending pass", nil);
698         else
699             msg = [NSString stringWithFormat:NSLocalizedString(@"%d pending passes", nil), jobCount];
700
701     }
702
703     [fQueueCountField setStringValue:msg];
704 }
705
706 //------------------------------------------------------------------------------------
707 // Refresh the UI in the current job pane. Should be called whenever the current job
708 // being processed has changed or when progress has changed.
709 //------------------------------------------------------------------------------------
710 - (void)updateCurrentJobUI
711 {
712     hb_state_t s;
713     hb_job_t * job = nil;
714     
715     if (fHandle)
716     {
717         hb_get_state( fHandle, &s );
718         job = hb_current_job(fHandle);
719     }
720
721     if (job)
722     {
723         [fJobDescTextField setAttributedStringValue:[self attributedDescriptionForJob:job withDetail:YES withHighlighting:NO]];
724
725         [self showCurrentJobPane:YES];
726         [fJobIconView setImage: fShowsJobsAsGroups ? [NSImage imageNamed:@"JobLarge"] : [NSImage imageNamed:@"JobPassLarge"] ];
727         
728         NSString * statusMsg = [self progressStatusStringForJob:job state:&s];
729         NSString * timeMsg = [self progressTimeRemainingStringForJob:job state:&s];
730         if ([timeMsg length] > 0)
731             statusMsg = [NSString stringWithFormat:@"%@ - %@", statusMsg, timeMsg];
732         [fProgressTextField setStringValue:statusMsg];
733         [self updateProgressBarWithState:&s];
734     }
735     else
736     {
737         [fJobDescTextField setStringValue:NSLocalizedString(@"No job processing", nil)];
738
739         [self showCurrentJobPane:NO];
740         [fProgressBar stopAnimation:nil];    // just in case in was animating
741     }
742
743     // Gross hack. Also update start/pause button. Have to do it here since we don't
744     // have any other periodic chance to update the button.
745     [self updateStartPauseButton];
746 }
747
748 //------------------------------------------------------------------------------------
749 // Refresh the UI in the queue pane. Should be called whenever the content of HB's job
750 // list has changed so that HBQueueController can sync up.
751 //------------------------------------------------------------------------------------
752 - (void)updateQueueUI
753 {
754     [fTaskView noteNumberOfRowsChanged];
755     [fTaskView reloadData];
756     
757     [self updateQueueCountField];
758     [self updateStartPauseButton];
759 }
760
761 //------------------------------------------------------------------------------------
762 // Deletes the selected job from HB and the queue UI
763 //------------------------------------------------------------------------------------
764 - (IBAction)removeSelectedJob: (id)sender
765 {
766     if (!fHandle) return;
767     
768     int row = [sender selectedRow];
769     if (row != -1)
770     {
771         BOOL jobGroups = [[NSUserDefaults standardUserDefaults] boolForKey:@"QueueShowsJobsAsGroups"];
772         if (jobGroups)
773             hb_rem_group( fHandle, hb_group( fHandle, row ) );
774         else
775             hb_rem( fHandle, hb_job( fHandle, row ) );
776         [self updateQueueUI];
777     }
778 }
779
780 //------------------------------------------------------------------------------------
781 // Prompts user if the want to cancel encoding of current job. If so, hb_stop gets
782 // called.
783 //------------------------------------------------------------------------------------
784 - (IBAction)cancelCurrentJob: (id)sender
785 {
786     if (!fHandle) return;
787     
788     hb_job_t * job = hb_current_job(fHandle);
789     if (!job) return;
790
791     // If command key is down, don't prompt
792     BOOL hasCmdKeyMask = ([[NSApp currentEvent] modifierFlags] & NSCommandKeyMask) != 0;
793     if (hasCmdKeyMask)
794         hb_stop(fHandle);
795     else
796     {
797         NSString * alertTitle = [NSString stringWithFormat:NSLocalizedString(@"Do you want to stop processing of %@?", nil),
798                 [NSString stringWithUTF8String:job->title->name]];
799         
800         NSBeginCriticalAlertSheet(
801                 alertTitle,
802                 NSLocalizedString(@"Stop Processing", nil), NSLocalizedString(@"Keep Processing", nil), nil, fQueueWindow, self,
803                 @selector(cancelCurrentJob:returnCode:contextInfo:), nil, nil,
804                 NSLocalizedString(@"Your movie will be lost if you don't continue processing.", nil),
805                 [NSString stringWithUTF8String:job->title->name]);
806
807         // cancelCurrentJob:returnCode:contextInfo: will be called when the dialog is dismissed
808     }
809 }
810
811 - (void) cancelCurrentJob: (NSWindow *)sheet returnCode: (int)returnCode contextInfo: (void *)contextInfo
812 {
813     if (returnCode == NSAlertDefaultReturn)
814         hb_stop(fHandle);
815 }
816
817 //------------------------------------------------------------------------------------
818 // Enables or disables the display of detail information for each job based on the 
819 // state of the sender.
820 //------------------------------------------------------------------------------------
821 - (IBAction)detailChanged: (id)sender
822 {
823     if ([sender isMemberOfClass:[NSButton class]])
824     {
825         BOOL detail = [sender state] == NSOnState;
826         [[NSUserDefaults standardUserDefaults] setBool:detail forKey:@"QueueShowsDetail"];
827
828         [self setShowsDetail:detail];
829     }
830 }
831
832 //------------------------------------------------------------------------------------
833 // Enables or disables the display of job groups based on the state of the sender.
834 //------------------------------------------------------------------------------------
835 - (IBAction)jobGroupsChanged: (id)sender
836 {
837     if ([sender isMemberOfClass:[NSButton class]])
838     {
839         BOOL groups = [sender state] == NSOnState;
840         [[NSUserDefaults standardUserDefaults] setBool:groups forKey:@"QueueShowsJobsAsGroups"];
841
842         [self setShowsJobsAsGroups:groups];
843     }
844     else if ([sender isMemberOfClass:[NSSegmentedControl class]])
845     {
846         BOOL groups = [sender selectedSegment] == 0;
847         [[NSUserDefaults standardUserDefaults] setBool:groups forKey:@"QueueShowsJobsAsGroups"];
848
849         [self setShowsJobsAsGroups:groups];
850     }
851     else if ([sender isMemberOfClass:[NSMatrix class]])
852     {
853         BOOL groups = [sender selectedColumn] == 0;
854         [[NSUserDefaults standardUserDefaults] setBool:groups forKey:@"QueueShowsJobsAsGroups"];
855
856         [self setShowsJobsAsGroups:groups];
857     }
858 }
859
860 //------------------------------------------------------------------------------------
861 // Toggles the Shows Detail setting.
862 //------------------------------------------------------------------------------------
863 - (IBAction)toggleShowsDetail: (id)sender
864 {
865     [self setShowsDetail:!fShowsDetail];
866 }
867
868 //------------------------------------------------------------------------------------
869 // Toggles the Shows Jobs As Groups setting.
870 //------------------------------------------------------------------------------------
871 - (IBAction)toggleShowsJobsAsGroups: (id)sender
872 {
873     [self setShowsJobsAsGroups:!fShowsJobsAsGroups];
874 }
875
876 //------------------------------------------------------------------------------------
877 // Toggles the processing of jobs on or off depending on the current state
878 //------------------------------------------------------------------------------------
879 - (IBAction)toggleStartPause: (id)sender
880 {
881     if (!fHandle) return;
882     
883     hb_state_t s;
884     hb_get_state2 (fHandle, &s);
885
886     if (s.state == HB_STATE_PAUSED)
887         hb_resume (fHandle);
888     else if ((s.state == HB_STATE_WORKING) || (s.state == HB_STATE_MUXING))
889         hb_pause (fHandle);
890     else
891     {
892         BOOL jobGroups = [[NSUserDefaults standardUserDefaults] boolForKey:@"QueueShowsJobsAsGroups"];
893         if (jobGroups)
894         {
895             if (hb_group_count(fHandle) > 0)
896                 hb_start (fHandle);
897         }
898         else if (hb_count(fHandle) > 0)
899             hb_start (fHandle);
900     }    
901 }
902
903 #pragma mark -
904 #pragma mark Toolbar
905
906 //------------------------------------------------------------------------------------
907 // setupToolbar
908 //------------------------------------------------------------------------------------
909 - (void)setupToolbar
910 {
911     // Create a new toolbar instance, and attach it to our window 
912     NSToolbar *toolbar = [[[NSToolbar alloc] initWithIdentifier: HBQueueToolbar] autorelease];
913     
914     // Set up toolbar properties: Allow customization, give a default display mode, and remember state in user defaults 
915     [toolbar setAllowsUserCustomization: YES];
916     [toolbar setAutosavesConfiguration: YES];
917     [toolbar setDisplayMode: NSToolbarDisplayModeIconAndLabel];
918     
919     // We are the delegate
920     [toolbar setDelegate: self];
921     
922     // Attach the toolbar to our window 
923     [fQueueWindow setToolbar: toolbar];
924 }
925
926 //------------------------------------------------------------------------------------
927 // toolbar:itemForItemIdentifier:willBeInsertedIntoToolbar:
928 //------------------------------------------------------------------------------------
929 - (NSToolbarItem *)toolbar:(NSToolbar *)toolbar
930         itemForItemIdentifier:(NSString *)itemIdentifier
931         willBeInsertedIntoToolbar:(BOOL)flag
932 {
933     // Required delegate method: Given an item identifier, this method returns an item.
934     // The toolbar will use this method to obtain toolbar items that can be displayed
935     // in the customization sheet, or in the toolbar itself.
936     
937     NSToolbarItem *toolbarItem = nil;
938     
939     if ([itemIdentifier isEqual: HBStartPauseResumeToolbarIdentifier])
940     {
941         toolbarItem = [[[NSToolbarItem alloc] initWithItemIdentifier: itemIdentifier] autorelease];
942                 
943         // Set the text label to be displayed in the toolbar and customization palette 
944                 [toolbarItem setLabel: @"Start"];
945                 [toolbarItem setPaletteLabel: @"Start/Pause"];
946                 
947                 // Set up a reasonable tooltip, and image
948                 [toolbarItem setToolTip: @"Start Encoding"];
949                 [toolbarItem setImage: [NSImage imageNamed: @"Play"]];
950                 
951                 // Tell the item what message to send when it is clicked 
952                 [toolbarItem setTarget: self];
953                 [toolbarItem setAction: @selector(toggleStartPause:)];
954         }
955     
956     else if ([itemIdentifier isEqual: HBShowDetailToolbarIdentifier])
957     {
958         toolbarItem = [[[NSToolbarItem alloc] initWithItemIdentifier: itemIdentifier] autorelease];
959                 
960         // Set the text label to be displayed in the toolbar and customization palette 
961                 [toolbarItem setLabel: @"Show Detail"];
962                 [toolbarItem setPaletteLabel: @"Show Detail"];
963                 
964                 // Set up a reasonable tooltip, and image
965                 [toolbarItem setToolTip: @"Show Detail"];
966                 [toolbarItem setImage: [NSImage imageNamed: @"Info"]];
967                 
968                 // Tell the item what message to send when it is clicked 
969                 [toolbarItem setTarget: self];
970                 [toolbarItem setAction: @selector(toggleShowsDetail:)];
971         }
972     
973     else if ([itemIdentifier isEqual: HBShowGroupsToolbarIdentifier])
974     {
975         toolbarItem = [[[NSToolbarItem alloc] initWithItemIdentifier: itemIdentifier] autorelease];
976                 
977         // Set the text label to be displayed in the toolbar and customization palette 
978                 [toolbarItem setLabel: @"View"];
979                 [toolbarItem setPaletteLabel: @"View"];
980                 
981                 // Set up a reasonable tooltip, and image
982                 [toolbarItem setToolTip: @"View"];
983 //              [toolbarItem setImage: [NSImage imageNamed: @"Disc"]];
984         
985         
986         NSButtonCell * buttonCell = [[[NSButtonCell alloc] initImageCell:nil] autorelease];
987         [buttonCell setBezelStyle:NSShadowlessSquareBezelStyle];//NSShadowlessSquareBezelStyle
988         [buttonCell setButtonType:NSToggleButton];
989         [buttonCell setBordered:NO];
990         [buttonCell setImagePosition:NSImageOnly];
991
992         NSMatrix * matrix = [[[NSMatrix alloc] initWithFrame:NSMakeRect(0,0,54,25)
993                 mode:NSRadioModeMatrix
994                 prototype:buttonCell
995                 numberOfRows:1
996                 numberOfColumns:2] autorelease];
997         [matrix setCellSize:NSMakeSize(27, 25)];
998         [matrix setIntercellSpacing:NSMakeSize(0, 0)];
999         [matrix selectCellAtRow:0 column:(fShowsJobsAsGroups ? 0 : 1)];
1000
1001         buttonCell = [matrix cellAtRow:0 column:0];
1002         [buttonCell setTitle:@""];
1003         [buttonCell setImage:[NSImage imageNamed: @"Encodes"]];
1004         [buttonCell setAlternateImage:[NSImage imageNamed: @"EncodesPressed"]];
1005         buttonCell = [matrix cellAtRow:0 column:1];
1006         [buttonCell setTitle:@""];
1007         [buttonCell setImage:[NSImage imageNamed: @"Passes"]];
1008         [buttonCell setAlternateImage:[NSImage imageNamed: @"PassesPressed"]];
1009         [toolbarItem setMinSize: [matrix frame].size];
1010         [toolbarItem setMaxSize: [matrix frame].size];
1011                 [toolbarItem setView: matrix];
1012
1013 /*
1014         NSSegmentedControl * segControl = [[[NSSegmentedControl alloc] initWithFrame:NSMakeRect(0,0,20,20)] autorelease];
1015         [[segControl cell] setControlSize:NSSmallControlSize];
1016         [segControl setSegmentCount:2];
1017         [segControl setLabel:@"Encodes" forSegment:0];
1018         [segControl setLabel:@"Passes" forSegment:1];
1019         [segControl setImage:[NSImage imageNamed:@"Delete"] forSegment:0];
1020         [segControl setImage:[NSImage imageNamed:@"Delete"] forSegment:1];
1021         [segControl setSelectedSegment: (fShowsJobsAsGroups ? 0 : 1)];
1022         [segControl sizeToFit];
1023         [toolbarItem setMinSize: [segControl frame].size];
1024         [toolbarItem setMaxSize: [segControl frame].size];
1025                 [toolbarItem setView: segControl];
1026 */
1027
1028 /*
1029         NSButton * button = [[[NSButton alloc] initWithFrame:NSMakeRect(0,0,20,20)] autorelease];
1030         [button setButtonType:NSSwitchButton];
1031         [button setTitle:@""];
1032         [button setState: fShowsJobsAsGroups ? NSOnState : NSOffState];
1033         [toolbarItem setMinSize: NSMakeSize(20,20)];
1034         [toolbarItem setMaxSize: NSMakeSize(20,20)];
1035                 [toolbarItem setView: button];
1036 */
1037                 
1038                 // Tell the item what message to send when it is clicked 
1039                 [toolbarItem setTarget: self];
1040                 [toolbarItem setAction: @selector(jobGroupsChanged:)];
1041         }
1042     
1043     return toolbarItem;
1044 }
1045
1046 //------------------------------------------------------------------------------------
1047 // toolbarDefaultItemIdentifiers:
1048 //------------------------------------------------------------------------------------
1049 - (NSArray *) toolbarDefaultItemIdentifiers: (NSToolbar *) toolbar
1050 {
1051     // Required delegate method: Returns the ordered list of items to be shown in the
1052     // toolbar by default.
1053     
1054     return [NSArray arrayWithObjects:
1055         HBStartPauseResumeToolbarIdentifier,
1056                 NSToolbarSeparatorItemIdentifier,
1057                 HBShowGroupsToolbarIdentifier,
1058         HBShowDetailToolbarIdentifier,
1059         nil];
1060 }
1061
1062 //------------------------------------------------------------------------------------
1063 // toolbarAllowedItemIdentifiers:
1064 //------------------------------------------------------------------------------------
1065 - (NSArray *) toolbarAllowedItemIdentifiers: (NSToolbar *) toolbar
1066 {
1067     // Required delegate method: Returns the list of all allowed items by identifier.
1068     // By default, the toolbar does not assume any items are allowed, even the
1069     // separator. So, every allowed item must be explicitly listed.
1070
1071     return [NSArray arrayWithObjects:
1072         HBStartPauseResumeToolbarIdentifier,
1073                 HBShowGroupsToolbarIdentifier,
1074         HBShowDetailToolbarIdentifier,
1075                 NSToolbarCustomizeToolbarItemIdentifier,
1076                 NSToolbarFlexibleSpaceItemIdentifier,
1077         NSToolbarSpaceItemIdentifier,
1078                 NSToolbarSeparatorItemIdentifier,
1079         nil];
1080 }
1081
1082 //------------------------------------------------------------------------------------
1083 // validateToolbarItem:
1084 //------------------------------------------------------------------------------------
1085 - (BOOL) validateToolbarItem: (NSToolbarItem *) toolbarItem
1086 {
1087     // Optional method: This message is sent to us since we are the target of some
1088     // toolbar item actions.
1089
1090     if (!fHandle) return NO;
1091
1092     BOOL enable = NO;
1093
1094     hb_state_t s;
1095     hb_get_state2 (fHandle, &s);
1096
1097     if ([[toolbarItem itemIdentifier] isEqual: HBStartPauseResumeToolbarIdentifier])
1098     {
1099         if (s.state == HB_STATE_PAUSED)
1100         {
1101             enable = YES;
1102             [toolbarItem setImage:[NSImage imageNamed: @"Play"]];
1103                         [toolbarItem setLabel: @"Resume"];
1104                         [toolbarItem setPaletteLabel: @"Resume"];
1105                         [toolbarItem setToolTip: @"Resume Encoding"];
1106        }
1107         
1108         else if ((s.state == HB_STATE_WORKING) || (s.state == HB_STATE_MUXING))
1109         {
1110             enable = YES;
1111             [toolbarItem setImage:[NSImage imageNamed: @"Pause"]];
1112                         [toolbarItem setLabel: @"Pause"];
1113                         [toolbarItem setPaletteLabel: @"Pause"];
1114                         [toolbarItem setToolTip: @"Pause Encoding"];
1115         }
1116
1117         else if (hb_count(fHandle) > 0)
1118         {
1119             enable = YES;
1120             [toolbarItem setImage:[NSImage imageNamed: @"Play"]];
1121                         [toolbarItem setLabel: @"Start"];
1122                         [toolbarItem setPaletteLabel: @"Start"];
1123                         [toolbarItem setToolTip: @"Start Encoding"];
1124         }
1125
1126         else
1127         {
1128             enable = NO;
1129             [toolbarItem setImage:[NSImage imageNamed: @"Play"]];
1130                         [toolbarItem setLabel: @"Start"];
1131                         [toolbarItem setPaletteLabel: @"Start"];
1132                         [toolbarItem setToolTip: @"Start Encoding"];
1133         }
1134         }
1135     
1136 /* not used because HBShowGroupsToolbarIdentifier is now a custom view
1137     else if ([[toolbarItem itemIdentifier] isEqual: HBShowGroupsToolbarIdentifier])
1138     {
1139         enable = hb_count(fHandle) > 0;
1140         if (fShowsJobsAsGroups)
1141         {
1142             [toolbarItem setLabel: @"View Passes"];
1143             [toolbarItem setPaletteLabel: @"View Passes"];
1144             [toolbarItem setToolTip: @"Displays items in the queue as individual passes"];
1145         }
1146         else
1147         {
1148             [toolbarItem setLabel: @"View Encodes"];
1149             [toolbarItem setPaletteLabel: @"View Encodes"];
1150             [toolbarItem setToolTip: @"Displays items in the queue as encodes"];
1151         }
1152     }
1153 */
1154     
1155     else if ([[toolbarItem itemIdentifier] isEqual: HBShowDetailToolbarIdentifier])
1156     {
1157         enable = hb_count(fHandle) > 0;
1158         if (fShowsDetail)
1159         {
1160             [toolbarItem setLabel: @"Hide Detail"];
1161             [toolbarItem setPaletteLabel: @"Hide Detail"];
1162             [toolbarItem setToolTip: @"Displays detailed information in the queue"];
1163         }
1164         else
1165         {
1166             [toolbarItem setLabel: @"Show Detail"];
1167             [toolbarItem setPaletteLabel: @"Show Detail"];
1168             [toolbarItem setToolTip: @"Displays detailed information in the queue"];
1169         }
1170     }
1171
1172         return enable;
1173 }
1174
1175 #pragma mark -
1176
1177 //------------------------------------------------------------------------------------
1178 // awakeFromNib
1179 //------------------------------------------------------------------------------------
1180 - (void)awakeFromNib
1181 {
1182     [self setupToolbar];
1183     
1184     if (![fQueueWindow setFrameUsingName:@"Queue"])
1185         [fQueueWindow center];
1186     [fQueueWindow setFrameAutosaveName: @"Queue"];
1187     [fQueueWindow setExcludedFromWindowsMenu:YES];
1188     
1189     // Show/hide UI elements
1190     [self setShowsDetail:fShowsDetail];
1191     [self setShowsJobsAsGroups:fShowsJobsAsGroups];
1192     [self showCurrentJobPane:NO];
1193 }
1194
1195
1196 //------------------------------------------------------------------------------------
1197 // windowWillClose
1198 //------------------------------------------------------------------------------------
1199 - (void)windowWillClose:(NSNotification *)aNotification
1200 {
1201     [[NSUserDefaults standardUserDefaults] setBool:NO forKey:@"QueueWindowIsOpen"];
1202 }
1203
1204 #pragma mark -
1205 #pragma mark NSTableView delegate
1206
1207 //------------------------------------------------------------------------------------
1208 // NSTableView delegate
1209 //------------------------------------------------------------------------------------
1210 - (int)numberOfRowsInTableView: (NSTableView *)aTableView
1211 {
1212     BOOL jobGroups = [[NSUserDefaults standardUserDefaults] boolForKey:@"QueueShowsJobsAsGroups"];
1213     if (jobGroups)
1214         return hb_group_count(fHandle);
1215     else
1216         return hb_count(fHandle);
1217 }
1218
1219 //------------------------------------------------------------------------------------
1220 // NSTableView delegate
1221 //------------------------------------------------------------------------------------
1222 - (id)tableView: (NSTableView *)aTableView
1223       objectValueForTableColumn: (NSTableColumn *)aTableColumn
1224                             row: (int)rowIndex
1225 {
1226     if (!fHandle)
1227         return @"";    // fatal error!
1228         
1229     hb_job_t * job;
1230
1231     BOOL jobGroups = [[NSUserDefaults standardUserDefaults] boolForKey:@"QueueShowsJobsAsGroups"];
1232     if (jobGroups)
1233         job = hb_group(fHandle, rowIndex);
1234     else
1235         job = hb_job(fHandle, rowIndex);
1236
1237     if (!job)
1238         return @"";    // fatal error!
1239
1240     if ([[aTableColumn identifier] isEqualToString:@"desc"])
1241     {
1242         BOOL highlighted = [aTableView isRowSelected:rowIndex] && [[aTableView window] isKeyWindow] && ([[aTableView window] firstResponder] == aTableView);
1243         return [self attributedDescriptionForJob:job withDetail:fShowsDetail withHighlighting:highlighted];    
1244     }
1245     
1246     else if ([[aTableColumn identifier] isEqualToString:@"delete"])
1247         return @"";
1248
1249     else if ([[aTableColumn identifier] isEqualToString:@"icon"])
1250         return fShowsJobsAsGroups ? [NSImage imageNamed:@"JobSmall"] : [NSImage imageNamed:@"JobPassSmall"];
1251
1252     return @"";
1253 }
1254
1255 //------------------------------------------------------------------------------------
1256 // NSTableView delegate
1257 //------------------------------------------------------------------------------------
1258 - (void)tableView: (NSTableView *)aTableView
1259         willDisplayCell: (id)aCell
1260          forTableColumn: (NSTableColumn *)aTableColumn
1261                     row: (int)rowIndex
1262 {
1263     if ([[aTableColumn identifier] isEqualToString:@"delete"])
1264     {
1265         BOOL highlighted = [aTableView isRowSelected:rowIndex] && [[aTableView window] isKeyWindow] && ([[aTableView window] firstResponder] == aTableView);
1266         if (highlighted)
1267         {
1268             [aCell setImage:[NSImage imageNamed:@"DeleteHighlight"]];
1269             [aCell setAlternateImage:[NSImage imageNamed:@"DeleteHighlightPressed"]];
1270         }
1271         else
1272         {
1273             [aCell setImage:[NSImage imageNamed:@"Delete"]];
1274         }
1275     }
1276 }
1277
1278 @end