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 if ([self count] == 0)
623 NSAssert(NO, @" jobgroup with no jobs");
627 HBJob * job = [self jobAtIndex:0];
629 [fDescription appendAttributedString: [job attributedDescriptionWithHBHandle: handle
639 withSubtitleInfo: NO]];
641 static NSAttributedString * carriageReturn = [[NSAttributedString alloc] initWithString:@"\n"];
643 NSEnumerator * e = [self jobEnumerator];
644 while ( (job = [e nextObject]) )
646 int pass = [job job]->pass;
647 [fDescription appendAttributedString:carriageReturn];
648 [fDescription appendAttributedString:
649 [job attributedDescriptionWithHBHandle: handle
655 withPictureInfo: pass != -1
656 withVideoInfo: pass != -1
657 withx264Info: pass != -1
658 withAudioInfo: pass == 0 || pass == 2
659 withSubtitleInfo: YES]];
662 fNeedsDescription = NO;
665 - (NSMutableAttributedString *) attributedDescriptionWithHBHandle: (hb_handle_t *)handle
667 if (fNeedsDescription)
668 [self updateDescriptionWithHBHandle: handle];
672 - (float) heightOfDescriptionForWidth:(float)width withHBHandle: (hb_handle_t *)handle
674 // Try to return the cached value if no changes have happened since the last time
675 if ((width == fLastDescriptionWidth) && (fLastDescriptionHeight != 0) && !fNeedsDescription)
676 return fLastDescriptionHeight;
678 if (fNeedsDescription)
679 [self updateDescriptionWithHBHandle: handle];
681 // Calculate the height
682 NSRect bounds = [fDescription boundingRectWithSize:NSMakeSize(width, 10000) options:NSStringDrawingUsesLineFragmentOrigin];
683 fLastDescriptionHeight = bounds.size.height + 6.0; // add some border to bottom
684 fLastDescriptionWidth = width;
685 return fLastDescriptionHeight;
687 /* supposedly another way to do this, in case boundingRectWithSize isn't working
688 NSTextView* tmpView = [[NSTextView alloc] initWithFrame:NSMakeRect(0, 0, width, 1)];
689 [[tmpView textStorage] setAttributedString:aString];
690 [tmpView setHorizontallyResizable:NO];
691 [tmpView setVerticallyResizable:YES];
692 // [[tmpView textContainer] setHeightTracksTextView: YES];
693 // [[tmpView textContainer] setContainerSize: NSMakeSize(width, 10000)];
695 float height = [tmpView frame].size.height;
701 - (float) lastDescriptionHeight
703 return fLastDescriptionHeight;
711 // Toolbar identifiers
712 static NSString* HBQueueToolbar = @"HBQueueToolbar1";
713 static NSString* HBQueueStartCancelToolbarIdentifier = @"HBQueueStartCancelToolbarIdentifier";
714 static NSString* HBQueuePauseResumeToolbarIdentifier = @"HBQueuePauseResumeToolbarIdentifier";
717 @implementation HBQueueController
719 //------------------------------------------------------------------------------------
721 //------------------------------------------------------------------------------------
724 if (self = [super init])
727 [[NSUserDefaults standardUserDefaults] registerDefaults:[NSDictionary dictionaryWithObjectsAndKeys:
728 @"NO", @"QueueWindowIsOpen",
729 @"NO", @"QueueShowsDetail",
730 @"YES", @"QueueShowsJobsAsGroups",
733 fJobGroups = [[NSMutableArray arrayWithCapacity:0] retain];
735 BOOL loadSucceeded = [NSBundle loadNibNamed:@"Queue" owner:self] && fQueueWindow;
736 NSAssert(loadSucceeded, @"Could not open Queue nib");
737 NSAssert(fQueueWindow, @"fQueueWindow not found in Queue nib");
742 //------------------------------------------------------------------------------------
744 //------------------------------------------------------------------------------------
747 [fAnimation release];
749 // clear the delegate so that windowWillClose is not attempted
750 if ([fQueueWindow delegate] == self)
751 [fQueueWindow setDelegate:nil];
753 [fJobGroups release];
754 [fSavedExpandedItems release];
759 //------------------------------------------------------------------------------------
761 //------------------------------------------------------------------------------------
762 - (void)setHandle: (hb_handle_t *)handle
767 //------------------------------------------------------------------------------------
768 // Receive HBController
769 //------------------------------------------------------------------------------------
770 - (void)setHBController: (HBController *)controller
772 fHBController = controller;
775 //------------------------------------------------------------------------------------
776 // Displays and brings the queue window to the front
777 //------------------------------------------------------------------------------------
778 - (IBAction) showQueueWindow: (id)sender
780 [self updateQueueUI];
781 [self updateCurrentJobUI];
783 [fQueueWindow makeKeyAndOrderFront: self];
785 [[NSUserDefaults standardUserDefaults] setBool:YES forKey:@"QueueWindowIsOpen"];
787 //------------------------------------------------------------------------------------
788 // Show or hide the current job pane (fCurrentJobPane).
789 //------------------------------------------------------------------------------------
790 - (void) showCurrentJobPane: (BOOL)showPane
792 if (showPane == fCurrentJobPaneShown)
795 // Things to keep in mind:
796 // - When the current job pane is shown, it occupies the upper portion of the
797 // window with the queue occupying the bottom portion of the window.
798 // - When the current job pane is hidden, it slides up and out of view.
799 // NSView setHidden is NOT used. The queue pane is resized to occupy the full
802 NSRect windowFrame = [[fCurrentJobPane superview] frame];
803 NSRect queueFrame, jobFrame;
805 NSDivideRect(windowFrame, &jobFrame, &queueFrame, NSHeight([fCurrentJobPane frame]), NSMaxYEdge);
808 queueFrame = windowFrame;
809 jobFrame = [fCurrentJobPane frame];
810 jobFrame.origin.y = NSHeight(windowFrame);
813 // Move fCurrentJobPane
814 NSDictionary * dict1 = [NSDictionary dictionaryWithObjectsAndKeys:
815 fCurrentJobPane, NSViewAnimationTargetKey,
816 [NSValue valueWithRect:jobFrame], NSViewAnimationEndFrameKey,
820 NSDictionary * dict2 = [NSDictionary dictionaryWithObjectsAndKeys:
821 fQueuePane, NSViewAnimationTargetKey,
822 [NSValue valueWithRect:queueFrame], NSViewAnimationEndFrameKey,
826 fAnimation = [[NSViewAnimation alloc] initWithViewAnimations:nil];
828 [fAnimation setViewAnimations:[NSArray arrayWithObjects:dict1, dict2, nil]];
829 [fAnimation setDuration:0.25];
830 [fAnimation setAnimationBlockingMode:NSAnimationBlocking]; // prevent user from resizing the window during an animation
831 [fAnimation startAnimation];
832 fCurrentJobPaneShown = showPane;
835 //------------------------------------------------------------------------------------
836 // Rebuilds the contents of fJobGroups which is a hierarchy of HBJobGroup and HBJobs.
837 //------------------------------------------------------------------------------------
838 - (void)rebuildJobGroups
840 [fJobGroups autorelease];
841 fJobGroups = [[NSMutableArray arrayWithCapacity:0] retain];
843 HBJobGroup * aJobGroup = [HBJobGroup jobGroup];
845 hb_job_t * nextJob = hb_group( fHandle, 0 );
848 if (nextJob->sequence_id == 0)
850 // Encountered a new group. Add the current one to fJobGroups and then start a new one.
851 if ([aJobGroup count] > 0)
853 [fJobGroups addObject: aJobGroup];
854 aJobGroup = [HBJobGroup jobGroup];
857 [aJobGroup addJob: [HBJob jobWithJob:nextJob]];
858 nextJob = hb_next_job (fHandle, nextJob);
860 if ([aJobGroup count] > 0)
862 [fJobGroups addObject:aJobGroup];
866 //------------------------------------------------------------------------------------
867 // Saves the state of the items that are currently expanded. Calling restoreOutlineViewState
868 // will restore the state of all items to match what was saved by saveOutlineViewState.
869 //------------------------------------------------------------------------------------
870 - (void) saveOutlineViewState
872 if (!fSavedExpandedItems)
873 fSavedExpandedItems = [[NSMutableIndexSet alloc] init];
875 [fSavedExpandedItems removeAllIndexes];
877 // NB: This code is stuffing the address of each job into an index set. While it
878 // works 99.9% of the time, it's not the ideal solution. We need unique ids in
879 // each job, possibly using the existing sequence_id field. Could use the high
880 // word as a unique encode id and the low word the sequence number.
882 HBJobGroup * aJobGroup;
883 NSEnumerator * e = [fJobGroups objectEnumerator];
884 while ( (aJobGroup = [e nextObject]) )
886 if ([fOutlineView isItemExpanded: aJobGroup])
887 [fSavedExpandedItems addIndex: (unsigned int)[[aJobGroup jobAtIndex:0] job]];
890 // Save the selection also. This is really UGLY code. Since I have to rebuild the
891 // entire outline hierachy every time hblib changes its job list, there's no easy
892 // way for me to remember the selection state other than saving off the first
893 // hb_job_t item in each selected group. This is done by saving the object's
894 // address. This could go away if I'd save a unique id in each job object.
896 int selection = [fOutlineView selectedRow];
898 fSavedSelectedItem = 0;
901 HBJobGroup * jobGroup = [fOutlineView itemAtRow: selection];
902 fSavedSelectedItem = (unsigned int)[[jobGroup jobAtIndex:0] job];
907 //------------------------------------------------------------------------------------
908 // Restores the expanded state of items in the outline view to match those saved by a
909 // previous call to saveOutlineViewState.
910 //------------------------------------------------------------------------------------
911 - (void) restoreOutlineViewState
913 if (fSavedExpandedItems)
915 HBJobGroup * aJobGroup;
916 NSEnumerator * e = [fJobGroups objectEnumerator];
917 while ( (aJobGroup = [e nextObject]) )
919 hb_job_t * j = [[aJobGroup jobAtIndex:0] job];
920 if ([fSavedExpandedItems containsIndex: (unsigned int)j])
921 [fOutlineView expandItem: aJobGroup];
925 if (fSavedSelectedItem)
927 // Ugh. Have to cycle through each row looking for the previously selected job.
928 // See the explanation in saveExpandedItems about the logic here.
930 // Find out what hb_job_t was selected
931 hb_job_t * j = (hb_job_t *)fSavedSelectedItem;
933 int rowToSelect = -1;
934 for (int i = 0; i < [fOutlineView numberOfRows]; i++)
936 HBJobGroup * jobGroup = [fOutlineView itemAtRow: i];
937 // Test to see if the group's first job is a match
938 if ([[jobGroup jobAtIndex:0] job] == j)
944 if (rowToSelect == -1)
945 [fOutlineView deselectAll: nil];
947 [fOutlineView selectRow:rowToSelect byExtendingSelection:NO];
951 //------------------------------------------------------------------------------------
952 // Generate string to display in UI.
953 //------------------------------------------------------------------------------------
954 - (NSString *) progressStatusStringForJob: (hb_job_t *)job state: (hb_state_t *)s
956 if (s->state == HB_STATE_WORKING)
960 msg = NSLocalizedString( @"Deep Scan", nil );
961 else if (job->pass == 1)
962 msg = NSLocalizedString( @"Analyzing video", nil );
963 else if ((job->pass == 0) || (job->pass == 2))
964 msg = NSLocalizedString( @"Encoding movie", nil );
966 return @""; // unknown condition!
968 if( s->param.working.seconds > -1 )
970 return [NSString stringWithFormat:
971 NSLocalizedString( @"%@ (%.2f fps, avg %.2f fps)", nil ),
972 msg, s->param.working.rate_cur, s->param.working.rate_avg];
979 else if (s->state == HB_STATE_MUXING)
980 return NSLocalizedString( @"Muxing", nil );
982 else if (s->state == HB_STATE_PAUSED)
983 return NSLocalizedString( @"Paused", nil );
985 else if (s->state == HB_STATE_WORKDONE)
986 return NSLocalizedString( @"Done", nil );
991 //------------------------------------------------------------------------------------
992 // Generate string to display in UI.
993 //------------------------------------------------------------------------------------
994 - (NSString *) progressTimeRemainingStringForJob: (hb_job_t *)job state: (hb_state_t *)s
996 if (s->state == HB_STATE_WORKING)
998 #define p s->param.working
1002 // Minutes always needed
1005 minutes = [NSString stringWithFormat:NSLocalizedString( @"%d minutes ", nil ), p.minutes];
1006 else if (p.minutes == 1)
1007 minutes = NSLocalizedString( @"1 minute ", nil );
1015 hours = [NSString stringWithFormat:NSLocalizedString( @"%d hours ", nil ), p.hours];
1017 hours = NSLocalizedString( @"1 hour ", nil );
1019 return [NSString stringWithFormat:NSLocalizedString( @"%@%@remaining", nil ), hours, minutes];
1026 seconds = [NSString stringWithFormat:NSLocalizedString( @"%d seconds ", nil ), p.seconds];
1028 seconds = NSLocalizedString( @"1 second ", nil );
1030 return [NSString stringWithFormat:NSLocalizedString( @"%@%@remaining", nil ), minutes, seconds];
1033 /* here is code that does it more like the Finder
1034 if( p.seconds > -1 )
1036 float estHours = (p.hours + (p.minutes / 60.0));
1037 float estMinutes = (p.minutes + (p.seconds / 60.0));
1040 return [NSString stringWithFormat:NSLocalizedString( @"Time remaining: About %d hours", nil ), lrintf(estHours)];
1041 else if (estHours > 0.983) // 59 minutes
1042 return NSLocalizedString( @"Time remaining: About 1 hour", nil );
1043 else if (estMinutes > 1.5)
1044 return [NSString stringWithFormat:NSLocalizedString( @"Time remaining: About %d minutes", nil ), lrintf(estMinutes)];
1045 else if (estMinutes > 0.983) // 59 seconds
1046 return NSLocalizedString( @"Time remaining: About 1 minute", nil );
1047 else if (p.seconds <= 5)
1048 return NSLocalizedString( @"Time remaining: Less than 5 seconds", nil );
1049 else if (p.seconds <= 10)
1050 return NSLocalizedString( @"Time remaining: Less than 10 seconds", nil );
1052 return NSLocalizedString( @"Time remaining: Less than 1 minute", nil );
1055 return NSLocalizedString( @"Time remaining: Calculating...", nil );
1063 //------------------------------------------------------------------------------------
1064 // Refresh progress bar (fProgressBar) from current state.
1065 //------------------------------------------------------------------------------------
1066 - (void) updateProgressBarWithState: (hb_state_t *)s
1068 if (s->state == HB_STATE_WORKING)
1070 #define p s->param.working
1071 [fProgressBar setIndeterminate:NO];
1072 float progress_total = 100.0 * ( p.progress + p.job_cur - 1 ) / p.job_count;
1073 [fProgressBar setDoubleValue:progress_total];
1077 else if (s->state == HB_STATE_MUXING)
1079 #define p s->param.muxing
1080 [fProgressBar setIndeterminate:YES];
1081 [fProgressBar startAnimation:nil];
1085 else if (s->state == HB_STATE_WORKDONE)
1087 [fProgressBar setIndeterminate:NO];
1088 [fProgressBar setDoubleValue:0.0];
1092 //------------------------------------------------------------------------------------
1093 // Refresh queue count text field (fQueueCountField).
1094 //------------------------------------------------------------------------------------
1095 - (void)updateQueueCountField
1100 jobCount = fHandle ? hb_group_count(fHandle) : 0;
1102 msg = NSLocalizedString(@"1 pending encode", nil);
1104 msg = [NSString stringWithFormat:NSLocalizedString(@"%d pending encodes", nil), jobCount];
1106 [fQueueCountField setStringValue:msg];
1109 //------------------------------------------------------------------------------------
1110 // Refresh the UI in the current job pane. Should be called whenever the current job
1111 // being processed has changed or when progress has changed.
1112 //------------------------------------------------------------------------------------
1113 - (void)updateCurrentJobUI
1116 hb_job_t * job = nil;
1120 hb_get_state2( fHandle, &s );
1121 job = hb_current_job(fHandle);
1126 if (fLastKnownCurrentJob != job)
1128 HBJob * currentJob = [HBJob jobWithJob: job];
1132 case -1: // Subtitle scan
1133 [fJobDescTextField setAttributedStringValue:
1134 [currentJob attributedDescriptionWithHBHandle:fHandle
1144 withSubtitleInfo: YES]];
1147 case 1: // video 1st pass
1148 [fJobDescTextField setAttributedStringValue:
1149 [currentJob attributedDescriptionWithHBHandle:fHandle
1155 withPictureInfo: YES
1159 withSubtitleInfo: NO]];
1162 case 0: // single pass
1163 case 2: // video 2nd pass + audio
1164 [fJobDescTextField setAttributedStringValue:
1165 [currentJob attributedDescriptionWithHBHandle:fHandle
1171 withPictureInfo: YES
1175 withSubtitleInfo: YES]];
1179 [fJobDescTextField setAttributedStringValue:
1180 [currentJob attributedDescriptionWithHBHandle:fHandle
1186 withPictureInfo: YES
1190 withSubtitleInfo: YES]];
1193 [self showCurrentJobPane:YES];
1194 [fJobIconView setImage: [NSImage imageNamed:@"JobLarge"]];
1197 NSString * statusMsg = [self progressStatusStringForJob:job state:&s];
1198 NSString * timeMsg = [self progressTimeRemainingStringForJob:job state:&s];
1199 if ([timeMsg length] > 0)
1200 statusMsg = [NSString stringWithFormat:@"%@ - %@", statusMsg, timeMsg];
1201 [fProgressTextField setStringValue:statusMsg];
1202 [self updateProgressBarWithState:&s];
1206 [fJobDescTextField setStringValue:NSLocalizedString(@"No job processing", nil)];
1208 [self showCurrentJobPane:NO];
1209 [fProgressBar stopAnimation:nil]; // just in case in was animating
1212 fLastKnownCurrentJob = job;
1215 //------------------------------------------------------------------------------------
1216 // Refresh the UI in the queue pane. Should be called whenever the content of HB's job
1217 // list has changed so that HBQueueController can sync up.
1218 //------------------------------------------------------------------------------------
1219 - (void)updateQueueUI
1221 [self saveOutlineViewState];
1222 [self rebuildJobGroups];
1223 [fOutlineView noteNumberOfRowsChanged];
1224 [fOutlineView reloadData];
1225 [self restoreOutlineViewState];
1226 [self updateQueueCountField];
1229 //------------------------------------------------------------------------------------
1230 // Deletes the selected job from HB and the queue UI
1231 //------------------------------------------------------------------------------------
1232 - (IBAction)removeSelectedJob: (id)sender
1234 if (!fHandle) return;
1236 int row = [sender selectedRow];
1242 [self cancelCurrentJob:sender];
1247 hb_rem_group( fHandle, hb_group( fHandle, row ) );
1250 HBJobGroup * jobGroup = [fOutlineView itemAtRow: row];
1251 hb_job_t * job = [[jobGroup jobAtIndex: 0] job];
1252 hb_rem_group( fHandle, job );
1254 [self updateQueueUI];
1258 //------------------------------------------------------------------------------------
1259 // Prompts user if the want to cancel encoding of current job. If so, doCancelCurrentJob
1261 //------------------------------------------------------------------------------------
1262 - (IBAction)cancelCurrentJob: (id)sender
1264 [fHBController Cancel:sender];
1267 //------------------------------------------------------------------------------------
1268 // Starts or cancels the processing of jobs depending on the current state
1269 //------------------------------------------------------------------------------------
1270 - (IBAction)toggleStartCancel: (id)sender
1272 if (!fHandle) return;
1275 hb_get_state2 (fHandle, &s);
1277 if ((s.state == HB_STATE_PAUSED) || (s.state == HB_STATE_WORKING) || (s.state == HB_STATE_MUXING))
1278 [fHBController Cancel: fQueuePane]; // sender == fQueuePane so that warning alert shows up on queue window
1280 else if (hb_group_count(fHandle) > 0)
1281 [fHBController doRip];
1284 //------------------------------------------------------------------------------------
1285 // Toggles the pause/resume state of hblib
1286 //------------------------------------------------------------------------------------
1287 - (IBAction)togglePauseResume: (id)sender
1289 if (!fHandle) return;
1292 hb_get_state2 (fHandle, &s);
1294 if (s.state == HB_STATE_PAUSED)
1295 hb_resume (fHandle);
1296 else if ((s.state == HB_STATE_WORKING) || (s.state == HB_STATE_MUXING))
1300 #if HB_OUTLINE_METRIC_CONTROLS
1301 static float spacingWidth = 3.0;
1302 - (IBAction)imageSpacingChanged: (id)sender;
1304 spacingWidth = [sender floatValue];
1305 [fOutlineView setNeedsDisplay: YES];
1307 - (IBAction)indentChanged: (id)sender
1309 [fOutlineView setIndentationPerLevel: [sender floatValue]];
1310 [fOutlineView setNeedsDisplay: YES];
1316 #pragma mark Toolbar
1318 //------------------------------------------------------------------------------------
1320 //------------------------------------------------------------------------------------
1321 - (void)setupToolbar
1323 // Create a new toolbar instance, and attach it to our window
1324 NSToolbar *toolbar = [[[NSToolbar alloc] initWithIdentifier: HBQueueToolbar] autorelease];
1326 // Set up toolbar properties: Allow customization, give a default display mode, and remember state in user defaults
1327 [toolbar setAllowsUserCustomization: YES];
1328 [toolbar setAutosavesConfiguration: YES];
1329 [toolbar setDisplayMode: NSToolbarDisplayModeIconAndLabel];
1331 // We are the delegate
1332 [toolbar setDelegate: self];
1334 // Attach the toolbar to our window
1335 [fQueueWindow setToolbar: toolbar];
1338 //------------------------------------------------------------------------------------
1339 // toolbar:itemForItemIdentifier:willBeInsertedIntoToolbar:
1340 //------------------------------------------------------------------------------------
1341 - (NSToolbarItem *)toolbar:(NSToolbar *)toolbar
1342 itemForItemIdentifier:(NSString *)itemIdentifier
1343 willBeInsertedIntoToolbar:(BOOL)flag
1345 // Required delegate method: Given an item identifier, this method returns an item.
1346 // The toolbar will use this method to obtain toolbar items that can be displayed
1347 // in the customization sheet, or in the toolbar itself.
1349 NSToolbarItem *toolbarItem = nil;
1351 if ([itemIdentifier isEqual: HBQueueStartCancelToolbarIdentifier])
1353 toolbarItem = [[[NSToolbarItem alloc] initWithItemIdentifier: itemIdentifier] autorelease];
1355 // Set the text label to be displayed in the toolbar and customization palette
1356 [toolbarItem setLabel: @"Start"];
1357 [toolbarItem setPaletteLabel: @"Start/Cancel"];
1359 // Set up a reasonable tooltip, and image
1360 [toolbarItem setToolTip: @"Start Encoding"];
1361 [toolbarItem setImage: [NSImage imageNamed: @"Play"]];
1363 // Tell the item what message to send when it is clicked
1364 [toolbarItem setTarget: self];
1365 [toolbarItem setAction: @selector(toggleStartCancel:)];
1368 if ([itemIdentifier isEqual: HBQueuePauseResumeToolbarIdentifier])
1370 toolbarItem = [[[NSToolbarItem alloc] initWithItemIdentifier: itemIdentifier] autorelease];
1372 // Set the text label to be displayed in the toolbar and customization palette
1373 [toolbarItem setLabel: @"Pause"];
1374 [toolbarItem setPaletteLabel: @"Pause/Resume"];
1376 // Set up a reasonable tooltip, and image
1377 [toolbarItem setToolTip: @"Pause Encoding"];
1378 [toolbarItem setImage: [NSImage imageNamed: @"Pause"]];
1380 // Tell the item what message to send when it is clicked
1381 [toolbarItem setTarget: self];
1382 [toolbarItem setAction: @selector(togglePauseResume:)];
1388 //------------------------------------------------------------------------------------
1389 // toolbarDefaultItemIdentifiers:
1390 //------------------------------------------------------------------------------------
1391 - (NSArray *) toolbarDefaultItemIdentifiers: (NSToolbar *) toolbar
1393 // Required delegate method: Returns the ordered list of items to be shown in the
1394 // toolbar by default.
1396 return [NSArray arrayWithObjects:
1397 HBQueueStartCancelToolbarIdentifier,
1398 HBQueuePauseResumeToolbarIdentifier,
1402 //------------------------------------------------------------------------------------
1403 // toolbarAllowedItemIdentifiers:
1404 //------------------------------------------------------------------------------------
1405 - (NSArray *) toolbarAllowedItemIdentifiers: (NSToolbar *) toolbar
1407 // Required delegate method: Returns the list of all allowed items by identifier.
1408 // By default, the toolbar does not assume any items are allowed, even the
1409 // separator. So, every allowed item must be explicitly listed.
1411 return [NSArray arrayWithObjects:
1412 HBQueueStartCancelToolbarIdentifier,
1413 HBQueuePauseResumeToolbarIdentifier,
1414 NSToolbarCustomizeToolbarItemIdentifier,
1415 NSToolbarFlexibleSpaceItemIdentifier,
1416 NSToolbarSpaceItemIdentifier,
1417 NSToolbarSeparatorItemIdentifier,
1421 //------------------------------------------------------------------------------------
1422 // validateToolbarItem:
1423 //------------------------------------------------------------------------------------
1424 - (BOOL) validateToolbarItem: (NSToolbarItem *) toolbarItem
1426 // Optional method: This message is sent to us since we are the target of some
1427 // toolbar item actions.
1429 if (!fHandle) return NO;
1434 hb_get_state2 (fHandle, &s);
1436 if ([[toolbarItem itemIdentifier] isEqual: HBQueueStartCancelToolbarIdentifier])
1438 if ((s.state == HB_STATE_PAUSED) || (s.state == HB_STATE_WORKING) || (s.state == HB_STATE_MUXING))
1441 [toolbarItem setImage:[NSImage imageNamed: @"Stop"]];
1442 [toolbarItem setLabel: @"Stop"];
1443 [toolbarItem setToolTip: @"Stop Encoding"];
1446 else if (hb_count(fHandle) > 0)
1449 [toolbarItem setImage:[NSImage imageNamed: @"Play"]];
1450 [toolbarItem setLabel: @"Start"];
1451 [toolbarItem setToolTip: @"Start Encoding"];
1457 [toolbarItem setImage:[NSImage imageNamed: @"Play"]];
1458 [toolbarItem setLabel: @"Start"];
1459 [toolbarItem setToolTip: @"Start Encoding"];
1463 if ([[toolbarItem itemIdentifier] isEqual: HBQueuePauseResumeToolbarIdentifier])
1465 if (s.state == HB_STATE_PAUSED)
1468 [toolbarItem setImage:[NSImage imageNamed: @"Play"]];
1469 [toolbarItem setLabel: @"Resume"];
1470 [toolbarItem setToolTip: @"Resume Encoding"];
1473 else if ((s.state == HB_STATE_WORKING) || (s.state == HB_STATE_MUXING))
1476 [toolbarItem setImage:[NSImage imageNamed: @"Pause"]];
1477 [toolbarItem setLabel: @"Pause"];
1478 [toolbarItem setToolTip: @"Pause Encoding"];
1483 [toolbarItem setImage:[NSImage imageNamed: @"Pause"]];
1484 [toolbarItem setLabel: @"Pause"];
1485 [toolbarItem setToolTip: @"Pause Encoding"];
1494 //------------------------------------------------------------------------------------
1496 //------------------------------------------------------------------------------------
1497 - (void)awakeFromNib
1499 [self setupToolbar];
1501 if (![fQueueWindow setFrameUsingName:@"Queue"])
1502 [fQueueWindow center];
1503 [fQueueWindow setFrameAutosaveName: @"Queue"];
1504 [fQueueWindow setExcludedFromWindowsMenu:YES];
1506 #if HB_QUEUE_DRAGGING
1507 [fOutlineView registerForDraggedTypes: [NSArray arrayWithObject:HBQueuePboardType] ];
1508 [fOutlineView setDraggingSourceOperationMask:NSDragOperationEvery forLocal:YES];
1509 [fOutlineView setVerticalMotionCanBeginDrag: YES];
1512 // Don't allow autoresizing of main column, else the "delete" column will get
1513 // pushed out of view.
1514 [fOutlineView setAutoresizesOutlineColumn: NO];
1515 [fOutlineView setIndentationPerLevel:21];
1517 #if HB_OUTLINE_METRIC_CONTROLS
1518 [fIndentation setHidden: NO];
1519 [fSpacing setHidden: NO];
1520 [fIndentation setIntValue:[fOutlineView indentationPerLevel]]; // debug
1521 [fSpacing setIntValue:3]; // debug
1524 // Show/hide UI elements
1525 fCurrentJobPaneShown = YES; // it's shown in the nib
1526 [self showCurrentJobPane:NO];
1530 //------------------------------------------------------------------------------------
1532 //------------------------------------------------------------------------------------
1533 - (void)windowWillClose:(NSNotification *)aNotification
1535 [[NSUserDefaults standardUserDefaults] setBool:NO forKey:@"QueueWindowIsOpen"];
1540 - (void)moveObjectsInArray:(NSMutableArray *)array fromIndexes:(NSIndexSet *)indexSet toIndex:(unsigned)insertIndex
1542 unsigned index = [indexSet lastIndex];
1543 unsigned aboveInsertIndexCount = 0;
1545 while (index != NSNotFound)
1547 unsigned removeIndex;
1549 if (index >= insertIndex)
1551 removeIndex = index + aboveInsertIndexCount;
1552 aboveInsertIndexCount++;
1556 removeIndex = index;
1560 id object = [[array objectAtIndex:removeIndex] retain];
1561 [array removeObjectAtIndex:removeIndex];
1562 [array insertObject:object atIndex:insertIndex];
1565 index = [indexSet indexLessThanIndex:index];
1570 #pragma mark NSOutlineView delegate
1572 - (id)outlineView:(NSOutlineView *)outlineView child:(int)index ofItem:(id)item
1575 return [fJobGroups objectAtIndex:index];
1577 // We are only one level deep, so we can't be asked about children
1578 NSAssert (NO, @"HBQueueController outlineView:child:ofItem: can't handle nested items.");
1582 - (BOOL)outlineView:(NSOutlineView *)outlineView isItemExpandable:(id)item
1584 // Our outline view has no levels, but we can still expand every item. Doing so
1585 // just makes the row taller. See heightOfRowByItem below.
1589 - (int)outlineView:(NSOutlineView *)outlineView numberOfChildrenOfItem:(id)item
1591 // Our outline view has no levels, so number of children will be zero for all
1594 return [fJobGroups count];
1599 - (void)outlineViewItemDidCollapse:(NSNotification *)notification
1601 id item = [[notification userInfo] objectForKey:@"NSObject"];
1602 int row = [fOutlineView rowForItem:item];
1603 [fOutlineView noteHeightOfRowsWithIndexesChanged:[NSIndexSet indexSetWithIndexesInRange:NSMakeRange(row,1)]];
1606 - (void)outlineViewItemDidExpand:(NSNotification *)notification
1608 id item = [[notification userInfo] objectForKey:@"NSObject"];
1609 int row = [fOutlineView rowForItem:item];
1610 [fOutlineView noteHeightOfRowsWithIndexesChanged:[NSIndexSet indexSetWithIndexesInRange:NSMakeRange(row,1)]];
1613 - (float)outlineView:(NSOutlineView *)outlineView heightOfRowByItem:(id)item
1615 if ([outlineView isItemExpanded: item])
1617 // Short-circuit here if in a live resize primarily to fix a bug but also to
1618 // increase resposivness during a resize. There's a bug in NSTableView that
1619 // causes row heights to get messed up if you try to change them during a live
1620 // resize. So if in a live resize, simply return the previously calculated
1621 // height. The row heights will get fixed up after the resize because we have
1622 // implemented viewDidEndLiveResize to force all of them to be recalculated.
1623 if ([outlineView inLiveResize] && [item lastDescriptionHeight] > 0)
1624 return [item lastDescriptionHeight];
1626 float width = [[outlineView tableColumnWithIdentifier: @"desc"] width];
1627 // Column width is NOT what is ultimately used
1628 width -= 47; // 26 pixels for disclosure triangle, 20 for icon, 1 for intercell spacing
1630 float height = [item heightOfDescriptionForWidth: width withHBHandle: fHandle];
1634 return HB_ROW_HEIGHT_TITLE_ONLY;
1637 - (id)outlineView:(NSOutlineView *)outlineView objectValueForTableColumn:(NSTableColumn *)tableColumn byItem:(id)item
1639 if ([[tableColumn identifier] isEqualToString:@"desc"])
1640 return [item attributedDescriptionWithHBHandle: fHandle];
1645 - (void)outlineView:(NSOutlineView *)outlineView willDisplayCell:(id)cell forTableColumn:(NSTableColumn *)tableColumn item:(id)item
1647 if ([[tableColumn identifier] isEqualToString:@"desc"])
1649 #if HB_OUTLINE_METRIC_CONTROLS
1650 NSSize theSize = [cell imageSpacing];
1651 theSize.width = spacingWidth;
1652 [cell setImageSpacing: theSize];
1655 // Set the image here since the value returned from outlineView:objectValueForTableColumn: didn't specify the image part
1656 [cell setImage:[NSImage imageNamed:@"JobSmall"]];
1659 else if ([[tableColumn identifier] isEqualToString:@"delete"])
1661 // The Delete action can only be applied for group items, not indivdual jobs.
1662 [cell setEnabled: YES];
1663 BOOL highlighted = [outlineView isRowSelected:[outlineView rowForItem: item]] && [[outlineView window] isKeyWindow] && ([[outlineView window] firstResponder] == outlineView);
1666 [cell setImage:[NSImage imageNamed:@"DeleteHighlight"]];
1667 [cell setAlternateImage:[NSImage imageNamed:@"DeleteHighlightPressed"]];
1670 [cell setImage:[NSImage imageNamed:@"Delete"]];
1674 - (void)outlineView:(NSOutlineView *)outlineView willDisplayOutlineCell:(id)cell forTableColumn:(NSTableColumn *)tableColumn item:(id)item
1676 // By default, the discolsure image gets centered vertically in the cell. We want
1677 // always at the top.
1678 if ([outlineView isItemExpanded: item])
1679 [cell setImagePosition: NSImageAbove];
1681 [cell setImagePosition: NSImageOnly];
1685 #pragma mark NSOutlineView delegate (dragging related)
1687 //------------------------------------------------------------------------------------
1688 // NSTableView delegate
1689 //------------------------------------------------------------------------------------
1691 #if HB_QUEUE_DRAGGING
1692 - (BOOL)outlineView:(NSOutlineView *)outlineView writeItems:(NSArray *)items toPasteboard:(NSPasteboard *)pboard
1694 // Don't retain since this is just holding temporaral drag information, and it is
1695 //only used during a drag! We could put this in the pboard actually.
1696 fDraggedNodes = items;
1698 // Provide data for our custom type, and simple NSStrings.
1699 [pboard declareTypes:[NSArray arrayWithObjects: HBQueuePboardType, nil] owner:self];
1701 // the actual data doesn't matter since DragDropSimplePboardType drags aren't recognized by anyone but us!.
1702 [pboard setData:[NSData data] forType:HBQueuePboardType];
1708 #if HB_QUEUE_DRAGGING
1709 - (NSDragOperation)outlineView:(NSOutlineView *)outlineView validateDrop:(id <NSDraggingInfo>)info proposedItem:(id)item proposedChildIndex:(int)index
1711 // Add code here to validate the drop
1712 BOOL isOnDropTypeProposal = index == NSOutlineViewDropOnItemIndex;
1713 if (isOnDropTypeProposal)
1714 return NSDragOperationNone;
1716 return NSDragOperationGeneric;
1720 #if HB_QUEUE_DRAGGING
1721 - (BOOL)outlineView:(NSOutlineView *)outlineView acceptDrop:(id <NSDraggingInfo>)info item:(id)item childIndex:(int)index
1723 NSMutableIndexSet *moveItems = [NSMutableIndexSet indexSet];
1726 NSEnumerator *enumerator = [fDraggedNodes objectEnumerator];
1727 while (obj = [enumerator nextObject])
1729 [moveItems addIndex:[fJobGroups indexOfObject:obj]];
1732 // Rearrange the data and view
1733 [self saveOutlineViewState];
1734 [self moveObjectsInArray:fJobGroups fromIndexes:moveItems toIndex: index];
1735 [fOutlineView reloadData];
1736 [self restoreOutlineViewState];