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. */
7 #include "HBQueueController.h"
8 #include "Controller.h"
9 #import "HBImageAndTextCell.h"
11 // UNI_QUEUE turns on the feature where the first item in the queue NSTableView is the
12 // current job followed by the jobs in hblib's queue. In this scheme, fCurrentJobPane
14 #define HB_UNI_QUEUE 0 // <--- NOT COMPLETELY FUNCTIONAL YET
16 #define HB_ROW_HEIGHT_TITLE_ONLY 17.0
18 // Pasteboard type for or drag operations
19 #define HBQueuePboardType @"HBQueuePboardType"
22 //------------------------------------------------------------------------------------
23 // NSMutableAttributedString (HBAdditions)
24 //------------------------------------------------------------------------------------
26 @interface NSMutableAttributedString (HBAdditions)
27 - (void) appendString: (NSString*)aString withAttributes: (NSDictionary *)aDictionary;
30 @implementation NSMutableAttributedString (HBAdditions)
31 - (void) appendString: (NSString*)aString withAttributes: (NSDictionary *)aDictionary
33 NSAttributedString * s = [[[NSAttributedString alloc]
34 initWithString: aString
35 attributes: aDictionary] autorelease];
36 [self appendAttributedString: s];
40 //------------------------------------------------------------------------------------
42 //------------------------------------------------------------------------------------
44 @implementation HBQueueOutlineView
46 - (void)viewDidEndLiveResize
48 // Since we disabled calculating row heights during a live resize, force them to
50 [self noteHeightOfRowsWithIndexesChanged:
51 [NSIndexSet indexSetWithIndexesInRange: NSMakeRange(0, [self numberOfRows])]];
52 [super viewDidEndLiveResize];
57 //------------------------------------------------------------------------------------
59 #pragma mark Job group functions
60 //------------------------------------------------------------------------------------
61 // These could be part of hblib if we think hblib should have knowledge of groups.
62 // For now, I see groups as a metaphor that HBQueueController provides.
65 * Returns the number of jobs groups in the queue.
66 * @param h Handle to hb_handle_t.
67 * @return Number of job groups.
69 static int hb_group_count(hb_handle_t * h)
74 while( ( job = hb_job( h, index++ ) ) )
76 if (job->sequence_id == 0)
83 * Returns handle to the first job in the i-th group within the job list.
84 * @param h Handle to hb_handle_t.
85 * @param i Index of group.
86 * @returns Handle to hb_job_t of desired job.
88 static hb_job_t * hb_group(hb_handle_t * h, int i)
93 while( ( job = hb_job( h, index++ ) ) )
95 if (job->sequence_id == 0)
106 * Removes a groups of jobs from the job list.
107 * @param h Handle to hb_handle_t.
108 * @param job Handle to the first job in the group.
110 static void hb_rem_group( hb_handle_t * h, hb_job_t * job )
115 while( ( j = hb_job( h, index ) ) )
119 // Delete this job plus the following ones in the sequence
121 while( ( j = hb_job( h, index ) ) && (j->sequence_id != 0) )
131 * Returns handle to the next job after the given job.
132 * @param h Handle to hb_handle_t.
133 * @param job Handle to the a job in the group.
134 * @returns Handle to hb_job_t of desired job or NULL if no such job.
136 static hb_job_t * hb_next_job( hb_handle_t * h, hb_job_t * job )
140 while( ( j = hb_job( h, index++ ) ) )
143 return hb_job( h, index );
150 //------------------------------------------------------------------------------------
152 //------------------------------------------------------------------------------------
154 @implementation HBJob
156 + (HBJob*) jobWithJob: (hb_job_t *) job
158 return [[[HBJob alloc] initWithJob:job] autorelease];
161 - (id) initWithJob: (hb_job_t *) job
163 if (self = [super init])
165 // job is not owned by HBJob. It does not get dealloacted when HBJob is released.
176 //------------------------------------------------------------------------------------
177 // Generate string to display in UI.
178 //------------------------------------------------------------------------------------
180 - (NSMutableAttributedString *) attributedDescriptionWithHBHandle: (hb_handle_t *)handle
181 withIcon: (BOOL)withIcon
182 withTitle: (BOOL)withTitle
183 withPassName: (BOOL)withPassName
184 withFormatInfo: (BOOL)withFormatInfo
185 withDestination: (BOOL)withDestination
186 withPictureInfo: (BOOL)withPictureInfo
187 withVideoInfo: (BOOL)withVideoInfo
188 withx264Info: (BOOL)withx264Info
189 withAudioInfo: (BOOL)withAudioInfo
190 withSubtitleInfo: (BOOL)withSubtitleInfo
193 NSMutableAttributedString * finalString = [[[NSMutableAttributedString alloc] initWithString: @""] autorelease];
195 hb_title_t * title = hbJob->title;
198 static NSMutableParagraphStyle * ps = NULL;
201 ps = [[[NSParagraphStyle defaultParagraphStyle] mutableCopy] retain];
202 [ps setHeadIndent: 40.0];
203 [ps setParagraphSpacing: 1.0];
204 [ps setTabStops:[NSArray array]]; // clear all tabs
205 [ps addTabStop: [[[NSTextTab alloc] initWithType: NSLeftTabStopType location: 20.0] autorelease]];
208 static NSDictionary* detailAttribute = [[NSDictionary dictionaryWithObjectsAndKeys:
209 [NSFont systemFontOfSize:10.0], NSFontAttributeName,
210 ps, NSParagraphStyleAttributeName,
212 static NSDictionary* detailBoldAttribute = [[NSDictionary dictionaryWithObjectsAndKeys:
213 [NSFont boldSystemFontOfSize:10.0], NSFontAttributeName,
214 ps, NSParagraphStyleAttributeName,
216 static NSDictionary* titleAttribute = [[NSDictionary dictionaryWithObjectsAndKeys:
217 [NSFont systemFontOfSize:[NSFont systemFontSize]], NSFontAttributeName,
218 ps, NSParagraphStyleAttributeName,
220 static NSDictionary* shortHeightAttribute = [[NSDictionary dictionaryWithObjectsAndKeys:
221 [NSFont systemFontOfSize:2.0], NSFontAttributeName,
224 // Title with summary
229 NSFileWrapper * wrapper = [[[NSFileWrapper alloc] initWithPath:[[NSBundle mainBundle] pathForImageResource: @"JobSmall"]] autorelease];
230 NSTextAttachment * imageAttachment = [[[NSTextAttachment alloc] initWithFileWrapper:wrapper] autorelease];
232 NSDictionary* imageAttributes = [NSDictionary dictionaryWithObjectsAndKeys:
233 [NSNumber numberWithFloat: -2.0], NSBaselineOffsetAttributeName,
234 imageAttachment, NSAttachmentAttributeName,
235 ps, NSParagraphStyleAttributeName,
238 NSAttributedString * imageAsString = [[[NSAttributedString alloc]
239 initWithString: [NSString stringWithFormat:@"%C%C", NSAttachmentCharacter, NSTabCharacter]
240 attributes: imageAttributes] autorelease];
242 [finalString appendAttributedString:imageAsString];
245 // Note: use title->name instead of title->dvd since name is just the chosen
246 // folder, instead of dvd which is the full path
247 [finalString appendString:[NSString stringWithUTF8String:title->name] withAttributes:titleAttribute];
249 NSString * summaryInfo;
251 NSString * chapterString = (hbJob->chapter_start == hbJob->chapter_end) ?
252 [NSString stringWithFormat:@"Chapter %d", hbJob->chapter_start] :
253 [NSString stringWithFormat:@"Chapters %d through %d", hbJob->chapter_start, hbJob->chapter_end];
255 BOOL hasIndepthScan = (hbJob->pass == -1);
256 int numVideoPasses = 0;
258 // To determine number of video passes, we need to skip past the subtitle scan.
261 // When job is the one currently being processed, then the next in its group
262 // is the the first job in the queue.
264 if (hbJob == hb_current_job(handle))
265 nextjob = hb_job(handle, 0);
267 nextjob = hb_next_job(handle, hbJob);
268 if (nextjob) // Overly cautious in case there is no next job!
269 numVideoPasses = MIN( 2, nextjob->pass + 1 );
272 numVideoPasses = MIN( 2, hbJob->pass + 1 );
274 if (hasIndepthScan && numVideoPasses == 1)
275 summaryInfo = [NSString stringWithFormat: @" (Title %d, %@, Deep Scan, Single Video Pass)", title->index, chapterString];
276 else if (hasIndepthScan && numVideoPasses > 1)
277 summaryInfo = [NSString stringWithFormat: @" (Title %d, %@, Deep Scan, %d Video Passes)", title->index, chapterString, numVideoPasses];
278 else if (numVideoPasses == 1)
279 summaryInfo = [NSString stringWithFormat: @" (Title %d, %@, Single Video Pass)", title->index, chapterString];
281 summaryInfo = [NSString stringWithFormat: @" (Title %d, %@, %d Video Passes)", title->index, chapterString, numVideoPasses];
283 [finalString appendString:[NSString stringWithFormat:@"%@\n", summaryInfo] withAttributes:detailAttribute];
285 // Insert a short-in-height line to put some white space after the title
286 [finalString appendString:@"\n" withAttributes:shortHeightAttribute];
289 // End of title stuff
297 NSString * imageName;
300 case -1: imageName = @"JobPassSubtitleSmall"; break;
301 case 0: imageName = @"JobPassFirstSmall"; break;
302 case 1: imageName = @"JobPassFirstSmall"; break;
303 case 2: imageName = @"JobPassSecondSmall"; break;
304 default: imageName = @"JobPassUnknownSmall"; break;
307 NSFileWrapper * wrapper = [[[NSFileWrapper alloc] initWithPath:[[NSBundle mainBundle] pathForImageResource: imageName]] autorelease];
308 NSTextAttachment * imageAttachment = [[[NSTextAttachment alloc] initWithFileWrapper:wrapper] autorelease];
310 NSDictionary* imageAttributes = [NSDictionary dictionaryWithObjectsAndKeys:
311 [NSNumber numberWithFloat: -2.0], NSBaselineOffsetAttributeName,
312 imageAttachment, NSAttachmentAttributeName,
313 ps, NSParagraphStyleAttributeName,
316 NSAttributedString * imageAsString = [[[NSAttributedString alloc]
317 initWithString: [NSString stringWithFormat:@"%C%C", NSAttachmentCharacter, NSTabCharacter]
318 attributes: imageAttributes] autorelease];
320 [finalString appendAttributedString:imageAsString];
323 NSString * jobPassName;
324 if (hbJob->pass == -1)
325 jobPassName = NSLocalizedString (@"Deep Scan", nil);
328 int passNum = MAX( 1, hbJob->pass );
330 jobPassName = NSLocalizedString (@"1st Pass", nil);
331 else if (passNum == 1)
332 jobPassName = NSLocalizedString (@"1st Pass", nil);
333 else if (passNum == 2)
334 jobPassName = NSLocalizedString (@"2nd Pass", nil);
336 jobPassName = [NSString stringWithFormat: NSLocalizedString(@"Pass %d", nil), passNum];
338 [finalString appendString:[NSString stringWithFormat:@"%@\n", jobPassName] withAttributes:detailBoldAttribute];
341 // Video Codec needed by FormatInfo and withVideoInfo
342 NSString * jobVideoCodec = nil;
343 if (withFormatInfo || withVideoInfo)
346 /* Video Codec settings (Encoder in the gui) */
347 if (hbJob->vcodec == HB_VCODEC_FFMPEG)
348 jobVideoCodec = @"FFmpeg"; // HB_VCODEC_FFMPEG
349 else if (hbJob->vcodec == HB_VCODEC_XVID)
350 jobVideoCodec = @"XviD"; // HB_VCODEC_XVID
351 else if (hbJob->vcodec == HB_VCODEC_X264)
353 /* Deterimine for sure how we are now setting iPod uuid atom */
354 if (hbJob->h264_level) // We are encoding for iPod
355 jobVideoCodec = @"x264 (H.264 iPod)"; // HB_VCODEC_X264
357 jobVideoCodec = @"x264 (H.264 Main)"; // HB_VCODEC_X264
360 if (jobVideoCodec == nil)
361 jobVideoCodec = @"unknown";
363 // Audio Codec needed by FormatInfo and AudioInfo
364 NSString * jobAudioCodec = nil;
365 if (withFormatInfo || withAudioInfo)
367 if (hbJob->acodec == 256)
368 jobAudioCodec = @"AAC"; // HB_ACODEC_FAAC
369 else if (hbJob->acodec == 512)
370 jobAudioCodec = @"MP3"; // HB_ACODEC_LAME
371 else if (hbJob->acodec == 1024)
372 jobAudioCodec = @"Vorbis"; // HB_ACODEC_VORBIS
373 else if (hbJob->acodec == 2048)
374 jobAudioCodec = @"AC3"; // HB_ACODEC_AC3
376 if (jobAudioCodec == nil)
377 jobAudioCodec = @"unknown";
382 NSString * jobFormatInfo;
383 // Muxer settings (File Format in the gui)
384 if (hbJob->mux == 65536 || hbJob->mux == 131072 || hbJob->mux == 1048576)
385 jobFormatInfo = @"MP4"; // HB_MUX_MP4,HB_MUX_PSP,HB_MUX_IPOD
386 else if (hbJob->mux == 262144)
387 jobFormatInfo = @"AVI"; // HB_MUX_AVI
388 else if (hbJob->mux == 524288)
389 jobFormatInfo = @"OGM"; // HB_MUX_OGM
390 else if (hbJob->mux == 2097152)
391 jobFormatInfo = @"MKV"; // HB_MUX_MKV
393 jobFormatInfo = @"unknown";
395 if (hbJob->chapter_markers == 1)
396 jobFormatInfo = [NSString stringWithFormat:@"%@ Container, %@ Video + %@ Audio, Chapter Markers\n", jobFormatInfo, jobVideoCodec, jobAudioCodec];
398 jobFormatInfo = [NSString stringWithFormat:@"%@ Container, %@ Video + %@ Audio\n", jobFormatInfo, jobVideoCodec, jobAudioCodec];
400 [finalString appendString: @"Format: " withAttributes:detailBoldAttribute];
401 [finalString appendString: jobFormatInfo withAttributes:detailAttribute];
406 [finalString appendString: @"Destination: " withAttributes:detailBoldAttribute];
407 [finalString appendString:[NSString stringWithFormat:@"%@\n", [NSString stringWithUTF8String:hbJob->file]] withAttributes:detailAttribute];
413 NSString * jobPictureInfo;
414 /*integers for picture values deinterlace, crop[4], keep_ratio, grayscale, pixel_ratio, pixel_aspect_width, pixel_aspect_height,
415 maxWidth, maxHeight */
416 if (hbJob->pixel_ratio == 1)
418 int titlewidth = title->width - hbJob->crop[2] - hbJob->crop[3];
419 int displayparwidth = titlewidth * hbJob->pixel_aspect_width / hbJob->pixel_aspect_height;
420 int displayparheight = title->height - hbJob->crop[0] - hbJob->crop[1];
421 jobPictureInfo = [NSString stringWithFormat:@"%dx%d (%dx%d Anamorphic)", displayparwidth, displayparheight, hbJob->width, displayparheight];
424 jobPictureInfo = [NSString stringWithFormat:@"%dx%d", hbJob->width, hbJob->height];
425 if (hbJob->keep_ratio == 1)
426 jobPictureInfo = [jobPictureInfo stringByAppendingString:@" Keep Aspect Ratio"];
428 if (hbJob->grayscale == 1)
429 jobPictureInfo = [jobPictureInfo stringByAppendingString:@", Grayscale"];
431 if (hbJob->deinterlace == 1)
432 jobPictureInfo = [jobPictureInfo stringByAppendingString:@", Deinterlace"];
433 if (withIcon) // implies indent the info
434 [finalString appendString: @"\t" withAttributes:detailBoldAttribute];
435 [finalString appendString: @"Picture: " withAttributes:detailBoldAttribute];
436 [finalString appendString:[NSString stringWithFormat:@"%@\n", jobPictureInfo] withAttributes:detailAttribute];
441 NSString * jobVideoQuality;
442 NSString * jobVideoDetail;
444 if (hbJob->vquality <= 0 || hbJob->vquality >= 1)
445 jobVideoQuality = [NSString stringWithFormat:@"%d kbps", hbJob->vbitrate];
448 NSNumber * vidQuality;
449 vidQuality = [NSNumber numberWithInt:hbJob->vquality * 100];
450 // this is screwed up kind of. Needs to be formatted properly.
452 jobVideoQuality = [NSString stringWithFormat:@"%@%% CRF", vidQuality];
454 jobVideoQuality = [NSString stringWithFormat:@"%@%% CQP", vidQuality];
457 if (hbJob->vrate_base == 1126125)
459 /* NTSC FILM 23.976 */
460 jobVideoDetail = [NSString stringWithFormat:@"%@, %@, 23.976 fps", jobVideoCodec, jobVideoQuality];
462 else if (hbJob->vrate_base == 900900)
465 jobVideoDetail = [NSString stringWithFormat:@"%@, %@, 29.97 fps", jobVideoCodec, jobVideoQuality];
469 /* Everything else */
470 jobVideoDetail = [NSString stringWithFormat:@"%@, %@, %d fps", jobVideoCodec, jobVideoQuality, hbJob->vrate / hbJob->vrate_base];
472 if (withIcon) // implies indent the info
473 [finalString appendString: @"\t" withAttributes:detailBoldAttribute];
474 [finalString appendString: @"Video: " withAttributes:detailBoldAttribute];
475 [finalString appendString:[NSString stringWithFormat:@"%@\n", jobVideoDetail] withAttributes:detailAttribute];
480 if (hbJob->vcodec == HB_VCODEC_X264 && hbJob->x264opts)
482 if (withIcon) // implies indent the info
483 [finalString appendString: @"\t" withAttributes:detailBoldAttribute];
484 [finalString appendString: @"x264 Options: " withAttributes:detailBoldAttribute];
485 [finalString appendString:[NSString stringWithFormat:@"%@\n", [NSString stringWithUTF8String:hbJob->x264opts]] withAttributes:detailAttribute];
491 NSString * jobAudioInfo;
492 if ([jobAudioCodec isEqualToString: @"AC3"])
493 jobAudioInfo = [NSString stringWithFormat:@"%@, Pass-Through", jobAudioCodec];
495 jobAudioInfo = [NSString stringWithFormat:@"%@, %d kbps, %d Hz", jobAudioCodec, hbJob->abitrate, hbJob->arate];
497 /* we now get the audio mixdown info for each of the two gui audio tracks */
498 /* lets do it the long way here to get a handle on things.
499 Hardcoded for two tracks for gui: audio_mixdowns[i] audio_mixdowns[i] */
500 int ai; // counter for each audios [] , macgui only allows for two audio tracks currently
501 for( ai = 0; ai < 2; ai++ )
503 if (hbJob->audio_mixdowns[ai] == HB_AMIXDOWN_MONO)
504 jobAudioInfo = [jobAudioInfo stringByAppendingString:[NSString stringWithFormat:@", Track %d: Mono", ai + 1]];
505 if (hbJob->audio_mixdowns[ai] == HB_AMIXDOWN_STEREO)
506 jobAudioInfo = [jobAudioInfo stringByAppendingString:[NSString stringWithFormat:@", Track %d: Stereo", ai + 1]];
507 if (hbJob->audio_mixdowns[ai] == HB_AMIXDOWN_DOLBY)
508 jobAudioInfo = [jobAudioInfo stringByAppendingString:[NSString stringWithFormat:@", Track %d: Dolby Surround", ai + 1]];
509 if (hbJob->audio_mixdowns[ai] == HB_AMIXDOWN_DOLBYPLII)
510 jobAudioInfo = [jobAudioInfo stringByAppendingString:[NSString stringWithFormat:@", Track %d: Dolby Pro Logic II", ai + 1]];
511 if (hbJob->audio_mixdowns[ai] == HB_AMIXDOWN_6CH)
512 jobAudioInfo = [jobAudioInfo stringByAppendingString:[NSString stringWithFormat:@", Track %d: 6-channel discreet", ai + 1]];
514 if (withIcon) // implies indent the info
515 [finalString appendString: @"\t" withAttributes:detailBoldAttribute];
516 [finalString appendString: @"Audio: " withAttributes:detailBoldAttribute];
517 [finalString appendString:[NSString stringWithFormat:@"%@\n", jobAudioInfo] withAttributes:detailAttribute];
520 if (withSubtitleInfo)
522 // hbJob->subtitle can == -1 in two cases:
523 // autoselect: when pass == -1
524 // none: when pass != -1
525 if ((hbJob->subtitle == -1) && (hbJob->pass == -1))
527 if (withIcon) // implies indent the info
528 [finalString appendString: @"\t" withAttributes:detailBoldAttribute];
529 [finalString appendString: @"Subtitles: " withAttributes:detailBoldAttribute];
530 [finalString appendString: @"Autoselect " withAttributes:detailAttribute];
532 else if (hbJob->subtitle >= 0)
534 hb_subtitle_t * subtitle = (hb_subtitle_t *) hb_list_item( title->list_subtitle, 0 );
537 if (withIcon) // implies indent the info
538 [finalString appendString: @"\t" withAttributes:detailBoldAttribute];
539 [finalString appendString: @"Subtitles: " withAttributes:detailBoldAttribute];
540 [finalString appendString: [NSString stringWithCString: subtitle->lang] withAttributes:detailAttribute];
546 if ([[finalString string] hasSuffix: @"\n"])
547 [finalString deleteCharactersInRange: NSMakeRange([[finalString string] length]-1, 1)];
556 //------------------------------------------------------------------------------------
558 //------------------------------------------------------------------------------------
560 @implementation HBJobGroup
562 + (HBJobGroup *) jobGroup;
564 return [[[HBJobGroup alloc] init] autorelease];
569 if (self = [super init])
571 fJobs = [[NSMutableArray arrayWithCapacity:0] retain];
572 fDescription = [[NSMutableAttributedString alloc] initWithString: @""];
573 [self setNeedsDescription: NO];
584 - (unsigned int) count
586 return [fJobs count];
589 - (void) addJob: (HBJob *)aJob
591 [fJobs addObject: aJob];
592 [self setNeedsDescription: YES];
593 fLastDescriptionHeight = 0;
594 fLastDescriptionWidth = 0;
597 - (HBJob *) jobAtIndex: (unsigned)index
599 return [fJobs objectAtIndex: index];
602 - (unsigned) indexOfJob: (HBJob *)aJob;
604 return [fJobs indexOfObject: aJob];
607 - (NSEnumerator *) jobEnumerator
609 return [fJobs objectEnumerator];
612 - (void) setNeedsDescription: (BOOL)flag
614 fNeedsDescription = flag;
617 - (void) updateDescriptionWithHBHandle: (hb_handle_t *)handle
619 [fDescription deleteCharactersInRange: NSMakeRange(0, [fDescription length])];
621 HBJob * job = [self jobAtIndex:0];
623 [fDescription appendAttributedString: [job attributedDescriptionWithHBHandle: handle
633 withSubtitleInfo: NO]];
635 static NSAttributedString * carriageReturn = [[NSAttributedString alloc] initWithString:@"\n"];
637 NSEnumerator * e = [self jobEnumerator];
638 while ( (job = [e nextObject]) )
640 int pass = [job job]->pass;
641 [fDescription appendAttributedString:carriageReturn];
642 [fDescription appendAttributedString:
643 [job attributedDescriptionWithHBHandle: handle
649 withPictureInfo: pass != -1
650 withVideoInfo: pass != -1
651 withx264Info: pass != -1
652 withAudioInfo: pass == 0 || pass == 2
653 withSubtitleInfo: YES]];
656 fNeedsDescription = NO;
659 - (NSMutableAttributedString *) attributedDescriptionWithHBHandle: (hb_handle_t *)handle
661 if (fNeedsDescription)
662 [self updateDescriptionWithHBHandle: handle];
666 - (float) heightOfDescriptionForWidth:(float)width withHBHandle: (hb_handle_t *)handle
668 // Try to return the cached value if no changes have happened since the last time
669 if ((width == fLastDescriptionWidth) && (fLastDescriptionHeight != 0) && !fNeedsDescription)
670 return fLastDescriptionHeight;
672 if (fNeedsDescription)
673 [self updateDescriptionWithHBHandle: handle];
675 // Calculate the height
676 NSRect bounds = [fDescription boundingRectWithSize:NSMakeSize(width, 10000) options:NSStringDrawingUsesLineFragmentOrigin];
677 fLastDescriptionHeight = bounds.size.height + 6.0; // add some border to bottom
678 fLastDescriptionWidth = width;
679 return fLastDescriptionHeight;
681 /* supposedly another way to do this, in case boundingRectWithSize isn't working
682 NSTextView* tmpView = [[NSTextView alloc] initWithFrame:NSMakeRect(0, 0, width, 1)];
683 [[tmpView textStorage] setAttributedString:aString];
684 [tmpView setHorizontallyResizable:NO];
685 [tmpView setVerticallyResizable:YES];
686 // [[tmpView textContainer] setHeightTracksTextView: YES];
687 // [[tmpView textContainer] setContainerSize: NSMakeSize(width, 10000)];
689 float height = [tmpView frame].size.height;
695 - (float) lastDescriptionHeight
697 return fLastDescriptionHeight;
705 // Toolbar identifiers
706 static NSString* HBQueueToolbar = @"HBQueueToolbar1";
707 static NSString* HBQueueStartCancelToolbarIdentifier = @"HBQueueStartCancelToolbarIdentifier";
708 static NSString* HBQueuePauseResumeToolbarIdentifier = @"HBQueuePauseResumeToolbarIdentifier";
711 @implementation HBQueueController
713 //------------------------------------------------------------------------------------
715 //------------------------------------------------------------------------------------
718 if (self = [super init])
721 [[NSUserDefaults standardUserDefaults] registerDefaults:[NSDictionary dictionaryWithObjectsAndKeys:
722 @"NO", @"QueueWindowIsOpen",
723 @"NO", @"QueueShowsDetail",
724 @"YES", @"QueueShowsJobsAsGroups",
727 fJobGroups = [[NSMutableArray arrayWithCapacity:0] retain];
732 //------------------------------------------------------------------------------------
734 //------------------------------------------------------------------------------------
737 [fAnimation release];
739 // clear the delegate so that windowWillClose is not attempted
740 if ([fQueueWindow delegate] == self)
741 [fQueueWindow setDelegate:nil];
743 [fJobGroups release];
744 [fSavedExpandedItems release];
749 //------------------------------------------------------------------------------------
751 //------------------------------------------------------------------------------------
752 - (void)setHandle: (hb_handle_t *)handle
757 //------------------------------------------------------------------------------------
758 // Receive HBController
759 //------------------------------------------------------------------------------------
760 - (void)setHBController: (HBController *)controller
762 fHBController = controller;
765 //------------------------------------------------------------------------------------
766 // Displays and brings the queue window to the front
767 //------------------------------------------------------------------------------------
768 - (IBAction) showQueueWindow: (id)sender
772 BOOL loadSucceeded = [NSBundle loadNibNamed:@"Queue" owner:self] && fQueueWindow;
773 NSAssert(loadSucceeded, @"Could not open Queue nib file");
776 [self updateQueueUI];
777 [self updateCurrentJobUI];
779 [fQueueWindow makeKeyAndOrderFront: self];
781 [[NSUserDefaults standardUserDefaults] setBool:YES forKey:@"QueueWindowIsOpen"];
783 //------------------------------------------------------------------------------------
784 // Show or hide the current job pane (fCurrentJobPane).
785 //------------------------------------------------------------------------------------
786 - (void) showCurrentJobPane: (BOOL)showPane
788 if (showPane == fCurrentJobPaneShown)
791 // Things to keep in mind:
792 // - When the current job pane is shown, it occupies the upper portion of the
793 // window with the queue occupying the bottom portion of the window.
794 // - When the current job pane is hidden, it slides up and out of view.
795 // NSView setHidden is NOT used. The queue pane is resized to occupy the full
798 NSRect windowFrame = [[fCurrentJobPane superview] frame];
799 NSRect queueFrame, jobFrame;
801 NSDivideRect(windowFrame, &jobFrame, &queueFrame, NSHeight([fCurrentJobPane frame]), NSMaxYEdge);
804 queueFrame = windowFrame;
805 jobFrame = [fCurrentJobPane frame];
806 jobFrame.origin.y = NSHeight(windowFrame);
809 // Move fCurrentJobPane
810 NSDictionary * dict1 = [NSDictionary dictionaryWithObjectsAndKeys:
811 fCurrentJobPane, NSViewAnimationTargetKey,
812 [NSValue valueWithRect:jobFrame], NSViewAnimationEndFrameKey,
816 NSDictionary * dict2 = [NSDictionary dictionaryWithObjectsAndKeys:
817 fQueuePane, NSViewAnimationTargetKey,
818 [NSValue valueWithRect:queueFrame], NSViewAnimationEndFrameKey,
822 fAnimation = [[NSViewAnimation alloc] initWithViewAnimations:nil];
824 [fAnimation setViewAnimations:[NSArray arrayWithObjects:dict1, dict2, nil]];
825 [fAnimation setDuration:0.25];
826 [fAnimation setAnimationBlockingMode:NSAnimationBlocking]; // prevent user from resizing the window during an animation
827 [fAnimation startAnimation];
828 fCurrentJobPaneShown = showPane;
831 //------------------------------------------------------------------------------------
832 // Rebuilds the contents of fJobGroups which is a hierarchy of HBJobGroup and HBJobs.
833 //------------------------------------------------------------------------------------
834 - (void)rebuildJobGroups
836 [fJobGroups autorelease];
837 fJobGroups = [[NSMutableArray arrayWithCapacity:0] retain];
839 HBJobGroup * aJobGroup = [HBJobGroup jobGroup];
841 hb_job_t * nextJob = hb_group( fHandle, 0 );
844 if (nextJob->sequence_id == 0)
846 // Encountered a new group. Add the current one to fJobGroups and then start a new one.
847 if ([aJobGroup count] > 0)
849 [fJobGroups addObject: aJobGroup];
850 aJobGroup = [HBJobGroup jobGroup];
853 [aJobGroup addJob: [HBJob jobWithJob:nextJob]];
854 nextJob = hb_next_job (fHandle, nextJob);
856 if ([aJobGroup count] > 0)
858 [fJobGroups addObject:aJobGroup];
862 //------------------------------------------------------------------------------------
863 // Saves the state of the items that are currently expanded. Calling restoreOutlineViewState
864 // will restore the state of all items to match what was saved by saveOutlineViewState.
865 //------------------------------------------------------------------------------------
866 - (void) saveOutlineViewState
868 if (!fSavedExpandedItems)
869 fSavedExpandedItems = [[NSMutableIndexSet alloc] init];
871 [fSavedExpandedItems removeAllIndexes];
873 // NB: This code is stuffing the address of each job into an index set. While it
874 // works 99.9% of the time, it's not the ideal solution. We need unique ids in
875 // each job, possibly using the existing sequence_id field. Could use the high
876 // word as a unique encode id and the low word the sequence number.
878 HBJobGroup * aJobGroup;
879 NSEnumerator * e = [fJobGroups objectEnumerator];
880 while ( (aJobGroup = [e nextObject]) )
882 if ([fOutlineView isItemExpanded: aJobGroup])
883 [fSavedExpandedItems addIndex: (unsigned int)[[aJobGroup jobAtIndex:0] job]];
886 // Save the selection also. This is really UGLY code. Since I have to rebuild the
887 // entire outline hierachy every time hblib changes its job list, there's no easy
888 // way for me to remember the selection state other than saving off the first
889 // hb_job_t item in each selected group. This is done by saving the object's
890 // address. This could go away if I'd save a unique id in each job object.
892 int selection = [fOutlineView selectedRow];
894 fSavedSelectedItem = 0;
897 HBJobGroup * jobGroup = [fOutlineView itemAtRow: selection];
898 fSavedSelectedItem = (unsigned int)[[jobGroup jobAtIndex:0] job];
903 //------------------------------------------------------------------------------------
904 // Restores the expanded state of items in the outline view to match those saved by a
905 // previous call to saveOutlineViewState.
906 //------------------------------------------------------------------------------------
907 - (void) restoreOutlineViewState
909 if (fSavedExpandedItems)
911 HBJobGroup * aJobGroup;
912 NSEnumerator * e = [fJobGroups objectEnumerator];
913 while ( (aJobGroup = [e nextObject]) )
915 hb_job_t * j = [[aJobGroup jobAtIndex:0] job];
916 if ([fSavedExpandedItems containsIndex: (unsigned int)j])
917 [fOutlineView expandItem: aJobGroup];
921 if (fSavedSelectedItem)
923 // Ugh. Have to cycle through each row looking for the previously selected job.
924 // See the explanation in saveExpandedItems about the logic here.
926 // Find out what hb_job_t was selected
927 hb_job_t * j = (hb_job_t *)fSavedSelectedItem;
929 int rowToSelect = -1;
930 for (int i = 0; i < [fOutlineView numberOfRows]; i++)
932 HBJobGroup * jobGroup = [fOutlineView itemAtRow: i];
933 // Test to see if the group's first job is a match
934 if ([[jobGroup jobAtIndex:0] job] == j)
940 if (rowToSelect == -1)
941 [fOutlineView deselectAll: nil];
943 [fOutlineView selectRow:rowToSelect byExtendingSelection:NO];
947 //------------------------------------------------------------------------------------
948 // Generate string to display in UI.
949 //------------------------------------------------------------------------------------
950 - (NSString *) progressStatusStringForJob: (hb_job_t *)job state: (hb_state_t *)s
952 if (s->state == HB_STATE_WORKING)
956 msg = NSLocalizedString( @"Deep Scan", nil );
957 else if (job->pass == 1)
958 msg = NSLocalizedString( @"Analyzing video", nil );
959 else if ((job->pass == 0) || (job->pass == 2))
960 msg = NSLocalizedString( @"Encoding movie", nil );
962 return @""; // unknown condition!
964 if( s->param.working.seconds > -1 )
966 return [NSString stringWithFormat:
967 NSLocalizedString( @"%@ (%.2f fps, avg %.2f fps)", nil ),
968 msg, s->param.working.rate_cur, s->param.working.rate_avg];
975 else if (s->state == HB_STATE_MUXING)
976 return NSLocalizedString( @"Muxing", nil );
978 else if (s->state == HB_STATE_PAUSED)
979 return NSLocalizedString( @"Paused", nil );
981 else if (s->state == HB_STATE_WORKDONE)
982 return NSLocalizedString( @"Done", nil );
987 //------------------------------------------------------------------------------------
988 // Generate string to display in UI.
989 //------------------------------------------------------------------------------------
990 - (NSString *) progressTimeRemainingStringForJob: (hb_job_t *)job state: (hb_state_t *)s
992 if (s->state == HB_STATE_WORKING)
994 #define p s->param.working
998 // Minutes always needed
1001 minutes = [NSString stringWithFormat:NSLocalizedString( @"%d minutes ", nil ), p.minutes];
1002 else if (p.minutes == 1)
1003 minutes = NSLocalizedString( @"1 minute ", nil );
1011 hours = [NSString stringWithFormat:NSLocalizedString( @"%d hours ", nil ), p.hours];
1013 hours = NSLocalizedString( @"1 hour ", nil );
1015 return [NSString stringWithFormat:NSLocalizedString( @"%@%@remaining", nil ), hours, minutes];
1022 seconds = [NSString stringWithFormat:NSLocalizedString( @"%d seconds ", nil ), p.seconds];
1024 seconds = NSLocalizedString( @"1 second ", nil );
1026 return [NSString stringWithFormat:NSLocalizedString( @"%@%@remaining", nil ), minutes, seconds];
1029 /* here is code that does it more like the Finder
1030 if( p.seconds > -1 )
1032 float estHours = (p.hours + (p.minutes / 60.0));
1033 float estMinutes = (p.minutes + (p.seconds / 60.0));
1036 return [NSString stringWithFormat:NSLocalizedString( @"Time remaining: About %d hours", nil ), lrintf(estHours)];
1037 else if (estHours > 0.983) // 59 minutes
1038 return NSLocalizedString( @"Time remaining: About 1 hour", nil );
1039 else if (estMinutes > 1.5)
1040 return [NSString stringWithFormat:NSLocalizedString( @"Time remaining: About %d minutes", nil ), lrintf(estMinutes)];
1041 else if (estMinutes > 0.983) // 59 seconds
1042 return NSLocalizedString( @"Time remaining: About 1 minute", nil );
1043 else if (p.seconds <= 5)
1044 return NSLocalizedString( @"Time remaining: Less than 5 seconds", nil );
1045 else if (p.seconds <= 10)
1046 return NSLocalizedString( @"Time remaining: Less than 10 seconds", nil );
1048 return NSLocalizedString( @"Time remaining: Less than 1 minute", nil );
1051 return NSLocalizedString( @"Time remaining: Calculating...", nil );
1059 //------------------------------------------------------------------------------------
1060 // Refresh progress bar (fProgressBar) from current state.
1061 //------------------------------------------------------------------------------------
1062 - (void) updateProgressBarWithState: (hb_state_t *)s
1064 if (s->state == HB_STATE_WORKING)
1066 #define p s->param.working
1067 [fProgressBar setIndeterminate:NO];
1068 float progress_total = 100.0 * ( p.progress + p.job_cur - 1 ) / p.job_count;
1069 [fProgressBar setDoubleValue:progress_total];
1073 else if (s->state == HB_STATE_MUXING)
1075 #define p s->param.muxing
1076 [fProgressBar setIndeterminate:YES];
1077 [fProgressBar startAnimation:nil];
1081 else if (s->state == HB_STATE_WORKDONE)
1083 [fProgressBar setIndeterminate:NO];
1084 [fProgressBar setDoubleValue:0.0];
1088 //------------------------------------------------------------------------------------
1089 // Refresh queue count text field (fQueueCountField).
1090 //------------------------------------------------------------------------------------
1091 - (void)updateQueueCountField
1096 jobCount = fHandle ? hb_group_count(fHandle) : 0;
1098 msg = NSLocalizedString(@"1 pending encode", nil);
1100 msg = [NSString stringWithFormat:NSLocalizedString(@"%d pending encodes", nil), jobCount];
1102 [fQueueCountField setStringValue:msg];
1105 //------------------------------------------------------------------------------------
1106 // Refresh the UI in the current job pane. Should be called whenever the current job
1107 // being processed has changed or when progress has changed.
1108 //------------------------------------------------------------------------------------
1109 - (void)updateCurrentJobUI
1112 hb_job_t * job = nil;
1116 hb_get_state2( fHandle, &s );
1117 job = hb_current_job(fHandle);
1122 if (fLastKnownCurrentJob != job)
1124 HBJob * currentJob = [HBJob jobWithJob: job];
1128 case -1: // Subtitle scan
1129 [fJobDescTextField setAttributedStringValue:
1130 [currentJob attributedDescriptionWithHBHandle:fHandle
1140 withSubtitleInfo: YES]];
1143 case 1: // video 1st pass
1144 [fJobDescTextField setAttributedStringValue:
1145 [currentJob attributedDescriptionWithHBHandle:fHandle
1151 withPictureInfo: YES
1155 withSubtitleInfo: NO]];
1158 case 0: // single pass
1159 case 2: // video 2nd pass + audio
1160 [fJobDescTextField setAttributedStringValue:
1161 [currentJob attributedDescriptionWithHBHandle:fHandle
1167 withPictureInfo: YES
1171 withSubtitleInfo: YES]];
1175 [fJobDescTextField setAttributedStringValue:
1176 [currentJob attributedDescriptionWithHBHandle:fHandle
1182 withPictureInfo: YES
1186 withSubtitleInfo: YES]];
1189 [self showCurrentJobPane:YES];
1190 [fJobIconView setImage: [NSImage imageNamed:@"JobLarge"]];
1193 NSString * statusMsg = [self progressStatusStringForJob:job state:&s];
1194 NSString * timeMsg = [self progressTimeRemainingStringForJob:job state:&s];
1195 if ([timeMsg length] > 0)
1196 statusMsg = [NSString stringWithFormat:@"%@ - %@", statusMsg, timeMsg];
1197 [fProgressTextField setStringValue:statusMsg];
1198 [self updateProgressBarWithState:&s];
1202 [fJobDescTextField setStringValue:NSLocalizedString(@"No job processing", nil)];
1204 [self showCurrentJobPane:NO];
1205 [fProgressBar stopAnimation:nil]; // just in case in was animating
1208 fLastKnownCurrentJob = job;
1211 //------------------------------------------------------------------------------------
1212 // Refresh the UI in the queue pane. Should be called whenever the content of HB's job
1213 // list has changed so that HBQueueController can sync up.
1214 //------------------------------------------------------------------------------------
1215 - (void)updateQueueUI
1217 [self saveOutlineViewState];
1218 [self rebuildJobGroups];
1219 [fOutlineView noteNumberOfRowsChanged];
1220 [fOutlineView reloadData];
1221 [self restoreOutlineViewState];
1222 [self updateQueueCountField];
1225 //------------------------------------------------------------------------------------
1226 // Deletes the selected job from HB and the queue UI
1227 //------------------------------------------------------------------------------------
1228 - (IBAction)removeSelectedJob: (id)sender
1230 if (!fHandle) return;
1232 int row = [sender selectedRow];
1238 [self cancelCurrentJob:sender];
1243 hb_rem_group( fHandle, hb_group( fHandle, row ) );
1246 HBJobGroup * jobGroup = [fOutlineView itemAtRow: row];
1247 hb_job_t * job = [[jobGroup jobAtIndex: 0] job];
1248 hb_rem_group( fHandle, job );
1250 [self updateQueueUI];
1254 //------------------------------------------------------------------------------------
1255 // Prompts user if the want to cancel encoding of current job. If so, doCancelCurrentJob
1257 //------------------------------------------------------------------------------------
1258 - (IBAction)cancelCurrentJob: (id)sender
1260 [fHBController Cancel:sender];
1263 //------------------------------------------------------------------------------------
1264 // Starts or cancels the processing of jobs depending on the current state
1265 //------------------------------------------------------------------------------------
1266 - (IBAction)toggleStartCancel: (id)sender
1268 if (!fHandle) return;
1271 hb_get_state2 (fHandle, &s);
1273 if ((s.state == HB_STATE_PAUSED) || (s.state == HB_STATE_WORKING) || (s.state == HB_STATE_MUXING))
1274 [fHBController Cancel: fQueuePane]; // sender == fQueuePane so that warning alert shows up on queue window
1276 else if (hb_group_count(fHandle) > 0)
1277 [fHBController doRip];
1280 //------------------------------------------------------------------------------------
1281 // Toggles the pause/resume state of hblib
1282 //------------------------------------------------------------------------------------
1283 - (IBAction)togglePauseResume: (id)sender
1285 if (!fHandle) return;
1288 hb_get_state2 (fHandle, &s);
1290 if (s.state == HB_STATE_PAUSED)
1291 hb_resume (fHandle);
1292 else if ((s.state == HB_STATE_WORKING) || (s.state == HB_STATE_MUXING))
1296 #if HB_OUTLINE_METRIC_CONTROLS
1297 static float spacingWidth = 3.0;
1298 - (IBAction)imageSpacingChanged: (id)sender;
1300 spacingWidth = [sender floatValue];
1301 [fOutlineView setNeedsDisplay: YES];
1303 - (IBAction)indentChanged: (id)sender
1305 [fOutlineView setIndentationPerLevel: [sender floatValue]];
1306 [fOutlineView setNeedsDisplay: YES];
1312 #pragma mark Toolbar
1314 //------------------------------------------------------------------------------------
1316 //------------------------------------------------------------------------------------
1317 - (void)setupToolbar
1319 // Create a new toolbar instance, and attach it to our window
1320 NSToolbar *toolbar = [[[NSToolbar alloc] initWithIdentifier: HBQueueToolbar] autorelease];
1322 // Set up toolbar properties: Allow customization, give a default display mode, and remember state in user defaults
1323 [toolbar setAllowsUserCustomization: YES];
1324 [toolbar setAutosavesConfiguration: YES];
1325 [toolbar setDisplayMode: NSToolbarDisplayModeIconAndLabel];
1327 // We are the delegate
1328 [toolbar setDelegate: self];
1330 // Attach the toolbar to our window
1331 [fQueueWindow setToolbar: toolbar];
1334 //------------------------------------------------------------------------------------
1335 // toolbar:itemForItemIdentifier:willBeInsertedIntoToolbar:
1336 //------------------------------------------------------------------------------------
1337 - (NSToolbarItem *)toolbar:(NSToolbar *)toolbar
1338 itemForItemIdentifier:(NSString *)itemIdentifier
1339 willBeInsertedIntoToolbar:(BOOL)flag
1341 // Required delegate method: Given an item identifier, this method returns an item.
1342 // The toolbar will use this method to obtain toolbar items that can be displayed
1343 // in the customization sheet, or in the toolbar itself.
1345 NSToolbarItem *toolbarItem = nil;
1347 if ([itemIdentifier isEqual: HBQueueStartCancelToolbarIdentifier])
1349 toolbarItem = [[[NSToolbarItem alloc] initWithItemIdentifier: itemIdentifier] autorelease];
1351 // Set the text label to be displayed in the toolbar and customization palette
1352 [toolbarItem setLabel: @"Start"];
1353 [toolbarItem setPaletteLabel: @"Start/Cancel"];
1355 // Set up a reasonable tooltip, and image
1356 [toolbarItem setToolTip: @"Start Encoding"];
1357 [toolbarItem setImage: [NSImage imageNamed: @"Play"]];
1359 // Tell the item what message to send when it is clicked
1360 [toolbarItem setTarget: self];
1361 [toolbarItem setAction: @selector(toggleStartCancel:)];
1364 if ([itemIdentifier isEqual: HBQueuePauseResumeToolbarIdentifier])
1366 toolbarItem = [[[NSToolbarItem alloc] initWithItemIdentifier: itemIdentifier] autorelease];
1368 // Set the text label to be displayed in the toolbar and customization palette
1369 [toolbarItem setLabel: @"Pause"];
1370 [toolbarItem setPaletteLabel: @"Pause/Resume"];
1372 // Set up a reasonable tooltip, and image
1373 [toolbarItem setToolTip: @"Pause Encoding"];
1374 [toolbarItem setImage: [NSImage imageNamed: @"Pause"]];
1376 // Tell the item what message to send when it is clicked
1377 [toolbarItem setTarget: self];
1378 [toolbarItem setAction: @selector(togglePauseResume:)];
1384 //------------------------------------------------------------------------------------
1385 // toolbarDefaultItemIdentifiers:
1386 //------------------------------------------------------------------------------------
1387 - (NSArray *) toolbarDefaultItemIdentifiers: (NSToolbar *) toolbar
1389 // Required delegate method: Returns the ordered list of items to be shown in the
1390 // toolbar by default.
1392 return [NSArray arrayWithObjects:
1393 HBQueueStartCancelToolbarIdentifier,
1394 HBQueuePauseResumeToolbarIdentifier,
1398 //------------------------------------------------------------------------------------
1399 // toolbarAllowedItemIdentifiers:
1400 //------------------------------------------------------------------------------------
1401 - (NSArray *) toolbarAllowedItemIdentifiers: (NSToolbar *) toolbar
1403 // Required delegate method: Returns the list of all allowed items by identifier.
1404 // By default, the toolbar does not assume any items are allowed, even the
1405 // separator. So, every allowed item must be explicitly listed.
1407 return [NSArray arrayWithObjects:
1408 HBQueueStartCancelToolbarIdentifier,
1409 HBQueuePauseResumeToolbarIdentifier,
1410 NSToolbarCustomizeToolbarItemIdentifier,
1411 NSToolbarFlexibleSpaceItemIdentifier,
1412 NSToolbarSpaceItemIdentifier,
1413 NSToolbarSeparatorItemIdentifier,
1417 //------------------------------------------------------------------------------------
1418 // validateToolbarItem:
1419 //------------------------------------------------------------------------------------
1420 - (BOOL) validateToolbarItem: (NSToolbarItem *) toolbarItem
1422 // Optional method: This message is sent to us since we are the target of some
1423 // toolbar item actions.
1425 if (!fHandle) return NO;
1430 hb_get_state2 (fHandle, &s);
1432 if ([[toolbarItem itemIdentifier] isEqual: HBQueueStartCancelToolbarIdentifier])
1434 if ((s.state == HB_STATE_PAUSED) || (s.state == HB_STATE_WORKING) || (s.state == HB_STATE_MUXING))
1437 [toolbarItem setImage:[NSImage imageNamed: @"Stop"]];
1438 [toolbarItem setLabel: @"Stop"];
1439 [toolbarItem setToolTip: @"Stop Encoding"];
1442 else if (hb_count(fHandle) > 0)
1445 [toolbarItem setImage:[NSImage imageNamed: @"Play"]];
1446 [toolbarItem setLabel: @"Start"];
1447 [toolbarItem setToolTip: @"Start Encoding"];
1453 [toolbarItem setImage:[NSImage imageNamed: @"Play"]];
1454 [toolbarItem setLabel: @"Start"];
1455 [toolbarItem setToolTip: @"Start Encoding"];
1459 if ([[toolbarItem itemIdentifier] isEqual: HBQueuePauseResumeToolbarIdentifier])
1461 if (s.state == HB_STATE_PAUSED)
1464 [toolbarItem setImage:[NSImage imageNamed: @"Play"]];
1465 [toolbarItem setLabel: @"Resume"];
1466 [toolbarItem setToolTip: @"Resume Encoding"];
1469 else if ((s.state == HB_STATE_WORKING) || (s.state == HB_STATE_MUXING))
1472 [toolbarItem setImage:[NSImage imageNamed: @"Pause"]];
1473 [toolbarItem setLabel: @"Pause"];
1474 [toolbarItem setToolTip: @"Pause Encoding"];
1479 [toolbarItem setImage:[NSImage imageNamed: @"Pause"]];
1480 [toolbarItem setLabel: @"Pause"];
1481 [toolbarItem setToolTip: @"Pause Encoding"];
1490 //------------------------------------------------------------------------------------
1492 //------------------------------------------------------------------------------------
1493 - (void)awakeFromNib
1495 [self setupToolbar];
1497 if (![fQueueWindow setFrameUsingName:@"Queue"])
1498 [fQueueWindow center];
1499 [fQueueWindow setFrameAutosaveName: @"Queue"];
1500 [fQueueWindow setExcludedFromWindowsMenu:YES];
1502 #if HB_QUEUE_DRAGGING
1503 [fOutlineView registerForDraggedTypes: [NSArray arrayWithObject:HBQueuePboardType] ];
1504 [fOutlineView setDraggingSourceOperationMask:NSDragOperationEvery forLocal:YES];
1505 [fOutlineView setVerticalMotionCanBeginDrag: YES];
1508 // Don't allow autoresizing of main column, else the "delete" column will get
1509 // pushed out of view.
1510 [fOutlineView setAutoresizesOutlineColumn: NO];
1511 [fOutlineView setIndentationPerLevel:21];
1513 #if HB_OUTLINE_METRIC_CONTROLS
1514 [fIndentation setHidden: NO];
1515 [fSpacing setHidden: NO];
1516 [fIndentation setIntValue:[fOutlineView indentationPerLevel]]; // debug
1517 [fSpacing setIntValue:3]; // debug
1520 // Show/hide UI elements
1521 fCurrentJobPaneShown = YES; // it's shown in the nib
1522 [self showCurrentJobPane:NO];
1526 //------------------------------------------------------------------------------------
1528 //------------------------------------------------------------------------------------
1529 - (void)windowWillClose:(NSNotification *)aNotification
1531 [[NSUserDefaults standardUserDefaults] setBool:NO forKey:@"QueueWindowIsOpen"];
1535 #pragma mark NSOutlineView delegate
1537 - (id)outlineView:(NSOutlineView *)outlineView child:(int)index ofItem:(id)item
1540 return [fJobGroups objectAtIndex:index];
1542 // We are only one level deep, so we can't be asked about children
1543 NSAssert (NO, @"HBQueueController outlineView:child:ofItem: can't handle nested items.");
1547 - (BOOL)outlineView:(NSOutlineView *)outlineView isItemExpandable:(id)item
1549 // Our outline view has no levels, but we can still expand every item. Doing so
1550 // just makes the row taller. See heightOfRowByItem below.
1554 - (int)outlineView:(NSOutlineView *)outlineView numberOfChildrenOfItem:(id)item
1556 // Our outline view has no levels, so number of children will be zero for all
1559 return [fJobGroups count];
1564 - (void)outlineViewItemDidCollapse:(NSNotification *)notification
1566 id item = [[notification userInfo] objectForKey:@"NSObject"];
1567 int row = [fOutlineView rowForItem:item];
1568 [fOutlineView noteHeightOfRowsWithIndexesChanged:[NSIndexSet indexSetWithIndexesInRange:NSMakeRange(row,1)]];
1571 - (void)outlineViewItemDidExpand:(NSNotification *)notification
1573 id item = [[notification userInfo] objectForKey:@"NSObject"];
1574 int row = [fOutlineView rowForItem:item];
1575 [fOutlineView noteHeightOfRowsWithIndexesChanged:[NSIndexSet indexSetWithIndexesInRange:NSMakeRange(row,1)]];
1578 - (float)outlineView:(NSOutlineView *)outlineView heightOfRowByItem:(id)item
1580 if ([outlineView isItemExpanded: item])
1582 // Short-circuit here if in a live resize primarily to fix a bug but also to
1583 // increase resposivness during a resize. There's a bug in NSTableView that
1584 // causes row heights to get messed up if you try to change them during a live
1585 // resize. So if in a live resize, simply return the previously calculated
1586 // height. The row heights will get fixed up after the resize because we have
1587 // implemented viewDidEndLiveResize to force all of them to be recalculated.
1588 if ([outlineView inLiveResize] && [item lastDescriptionHeight] > 0)
1589 return [item lastDescriptionHeight];
1591 float width = [[outlineView tableColumnWithIdentifier: @"desc"] width];
1592 // Column width is NOT what is ultimately used
1593 width -= 47; // 26 pixels for disclosure triangle, 20 for icon, 1 for intercell spacing
1595 float height = [item heightOfDescriptionForWidth: width withHBHandle: fHandle];
1599 return HB_ROW_HEIGHT_TITLE_ONLY;
1602 - (id)outlineView:(NSOutlineView *)outlineView objectValueForTableColumn:(NSTableColumn *)tableColumn byItem:(id)item
1604 if ([[tableColumn identifier] isEqualToString:@"desc"])
1605 return [item attributedDescriptionWithHBHandle: fHandle];
1610 - (void)outlineView:(NSOutlineView *)outlineView willDisplayCell:(id)cell forTableColumn:(NSTableColumn *)tableColumn item:(id)item
1612 if ([[tableColumn identifier] isEqualToString:@"desc"])
1614 #if HB_OUTLINE_METRIC_CONTROLS
1615 NSSize theSize = [cell imageSpacing];
1616 theSize.width = spacingWidth;
1617 [cell setImageSpacing: theSize];
1620 // Set the image here since the value returned from outlineView:objectValueForTableColumn: didn't specify the image part
1621 [cell setImage:[NSImage imageNamed:@"JobSmall"]];
1624 else if ([[tableColumn identifier] isEqualToString:@"delete"])
1626 // The Delete action can only be applied for group items, not indivdual jobs.
1627 [cell setEnabled: YES];
1628 BOOL highlighted = [outlineView isRowSelected:[outlineView rowForItem: item]] && [[outlineView window] isKeyWindow] && ([[outlineView window] firstResponder] == outlineView);
1631 [cell setImage:[NSImage imageNamed:@"DeleteHighlight"]];
1632 [cell setAlternateImage:[NSImage imageNamed:@"DeleteHighlightPressed"]];
1635 [cell setImage:[NSImage imageNamed:@"Delete"]];
1639 - (void)outlineView:(NSOutlineView *)outlineView willDisplayOutlineCell:(id)cell forTableColumn:(NSTableColumn *)tableColumn item:(id)item
1641 // By default, the discolsure image gets centered vertically in the cell. We want
1642 // always at the top.
1643 if ([outlineView isItemExpanded: item])
1644 [cell setImagePosition: NSImageAbove];
1646 [cell setImagePosition: NSImageOnly];
1650 #pragma mark NSOutlineView delegate (dragging related)
1652 //------------------------------------------------------------------------------------
1653 // NSTableView delegate
1654 //------------------------------------------------------------------------------------
1656 #if HB_QUEUE_DRAGGING
1657 - (BOOL)outlineView:(NSOutlineView *)outlineView writeItems:(NSArray *)items toPasteboard:(NSPasteboard *)pboard
1659 fDraggedNodes = items; // Don't retain since this is just holding temporaral drag information, and it is only used during a drag! We could put this in the pboard actually.
1661 // Provide data for our custom type, and simple NSStrings.
1662 [pboard declareTypes:[NSArray arrayWithObjects: HBQueuePboardType, nil] owner:self];
1664 // the actual data doesn't matter since DragDropSimplePboardType drags aren't recognized by anyone but us!.
1665 [pboard setData:[NSData data] forType:HBQueuePboardType];
1671 #if HB_QUEUE_DRAGGING
1672 - (NSDragOperation)outlineView:(NSOutlineView *)outlineView validateDrop:(id <NSDraggingInfo>)info proposedItem:(id)item proposedChildIndex:(int)index
1674 // Add code here to validate the drop
1675 BOOL isOnDropTypeProposal = index == NSOutlineViewDropOnItemIndex;
1676 if (isOnDropTypeProposal)
1677 return NSDragOperationNone;
1679 return NSDragOperationGeneric;
1683 #if HB_QUEUE_DRAGGING
1684 - (BOOL)outlineView:(NSOutlineView *)outlineView acceptDrop:(id <NSDraggingInfo>)info item:(id)item childIndex:(int)index
1686 // NSPasteboard* pboard = [info draggingPasteboard];
1687 // NSData* rowData = [pboard dataForType:HBQueuePboardType];
1688 // NSIndexSet* rowIndexes = [NSKeyedUnarchiver unarchiveObjectWithData:rowData];
1689 // int dragRow = [rowIndexes firstIndex];
1691 // Move the specified row to its new location...
1692 HBJob * draggedJob = [fDraggedNodes objectAtIndex: 0];
1693 NSLog(@"dragged job = %@", draggedJob);
1694 NSLog(@"drag from location = %d", [fJobGroups indexOfObject: draggedJob]);
1695 NSLog(@"drag to location = %d", index);
1696 NSLog(@"acceptDrop");