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 #define HB_ROW_HEIGHT_TITLE_ONLY 17.0
13 // Pasteboard type for or drag operations
14 #define HBQueuePboardType @"HBQueuePboardType"
17 //------------------------------------------------------------------------------------
18 // NSMutableAttributedString (HBAdditions)
19 //------------------------------------------------------------------------------------
21 @interface NSMutableAttributedString (HBAdditions)
22 - (void) appendString: (NSString*)aString withAttributes: (NSDictionary *)aDictionary;
25 @implementation NSMutableAttributedString (HBAdditions)
26 - (void) appendString: (NSString*)aString withAttributes: (NSDictionary *)aDictionary
28 NSAttributedString * s = [[[NSAttributedString alloc]
29 initWithString: aString
30 attributes: aDictionary] autorelease];
31 [self appendAttributedString: s];
35 //------------------------------------------------------------------------------------
37 //------------------------------------------------------------------------------------
39 @implementation HBQueueOutlineView
41 - (void)viewDidEndLiveResize
43 // Since we disabled calculating row heights during a live resize, force them to
45 [self noteHeightOfRowsWithIndexesChanged:
46 [NSIndexSet indexSetWithIndexesInRange: NSMakeRange(0, [self numberOfRows])]];
47 [super viewDidEndLiveResize];
51 - (NSImage *)dragImageForRowsWithIndexes:(NSIndexSet *)dragRows tableColumns:(NSArray *)tableColumns event:(NSEvent*)dragEvent offset:(NSPointPointer)dragImageOffset
53 // Set the fIsDragging flag so that other's know that a drag operation is being
57 // By default, NSTableView only drags an image of the first column. Change this to
58 // drag an image of the queue's icon and desc columns.
59 NSArray * cols = [NSArray arrayWithObjects: [self tableColumnWithIdentifier:@"icon"], [self tableColumnWithIdentifier:@"desc"], nil];
60 return [super dragImageForRowsWithIndexes:dragRows tableColumns:cols event:dragEvent offset:dragImageOffset];
65 - (void) mouseDown:(NSEvent *)theEvent
67 // After a drag operation, reset fIsDragging back to NO. This is really the only way
68 // for us to detect when a drag has finished. You can't do it in acceptDrop because
69 // that won't be called if the dragged item is released outside the view.
70 [super mouseDown:theEvent];
84 //------------------------------------------------------------------------------------
86 #pragma mark Job group functions
87 //------------------------------------------------------------------------------------
88 // These could be part of hblib if we think hblib should have knowledge of groups.
89 // For now, I see groups as a metaphor that HBQueueController provides.
92 * Returns the number of jobs groups in the queue.
93 * @param h Handle to hb_handle_t.
94 * @return Number of job groups.
96 static int hb_group_count(hb_handle_t * h)
101 while( ( job = hb_job( h, index++ ) ) )
103 if (job->sequence_id == 0)
110 * Returns handle to the first job in the i-th group within the job list.
111 * @param h Handle to hb_handle_t.
112 * @param i Index of group.
113 * @returns Handle to hb_job_t of desired job.
115 static hb_job_t * hb_group(hb_handle_t * h, int i)
120 while( ( job = hb_job( h, index++ ) ) )
122 if (job->sequence_id == 0)
133 * Removes a groups of jobs from the job list.
134 * @param h Handle to hb_handle_t.
135 * @param job Handle to the first job in the group.
137 static void hb_rem_group( hb_handle_t * h, hb_job_t * job )
142 while( ( j = hb_job( h, index ) ) )
146 // Delete this job plus the following ones in the sequence
148 while( ( j = hb_job( h, index ) ) && (j->sequence_id != 0) )
158 * Returns handle to the next job after the given job.
159 * @param h Handle to hb_handle_t.
160 * @param job Handle to the a job in the group.
161 * @returns Handle to hb_job_t of desired job or NULL if no such job.
163 static hb_job_t * hb_next_job( hb_handle_t * h, hb_job_t * job )
167 while( ( j = hb_job( h, index++ ) ) )
170 return hb_job( h, index );
177 //------------------------------------------------------------------------------------
179 //------------------------------------------------------------------------------------
181 @implementation HBJob
183 + (HBJob*) jobWithJob: (hb_job_t *) job
185 return [[[HBJob alloc] initWithJob:job] autorelease];
188 - (id) initWithJob: (hb_job_t *) job
190 if (self = [super init])
192 // job is not owned by HBJob. It does not get dealloacted when HBJob is released.
203 //------------------------------------------------------------------------------------
204 // Generate string to display in UI.
205 //------------------------------------------------------------------------------------
207 - (NSMutableAttributedString *) attributedDescriptionWithHBHandle: (hb_handle_t *)handle
208 withIcon: (BOOL)withIcon
209 withTitle: (BOOL)withTitle
210 withPassName: (BOOL)withPassName
211 withFormatInfo: (BOOL)withFormatInfo
212 withDestination: (BOOL)withDestination
213 withPictureInfo: (BOOL)withPictureInfo
214 withVideoInfo: (BOOL)withVideoInfo
215 withx264Info: (BOOL)withx264Info
216 withAudioInfo: (BOOL)withAudioInfo
217 withSubtitleInfo: (BOOL)withSubtitleInfo
220 NSMutableAttributedString * finalString = [[[NSMutableAttributedString alloc] initWithString: @""] autorelease];
222 hb_title_t * title = hbJob->title;
225 static NSMutableParagraphStyle * ps = NULL;
228 ps = [[[NSParagraphStyle defaultParagraphStyle] mutableCopy] retain];
229 [ps setHeadIndent: 40.0];
230 [ps setParagraphSpacing: 1.0];
231 [ps setTabStops:[NSArray array]]; // clear all tabs
232 [ps addTabStop: [[[NSTextTab alloc] initWithType: NSLeftTabStopType location: 20.0] autorelease]];
235 static NSDictionary* detailAttribute = [[NSDictionary dictionaryWithObjectsAndKeys:
236 [NSFont systemFontOfSize:10.0], NSFontAttributeName,
237 ps, NSParagraphStyleAttributeName,
239 static NSDictionary* detailBoldAttribute = [[NSDictionary dictionaryWithObjectsAndKeys:
240 [NSFont boldSystemFontOfSize:10.0], NSFontAttributeName,
241 ps, NSParagraphStyleAttributeName,
243 static NSDictionary* titleAttribute = [[NSDictionary dictionaryWithObjectsAndKeys:
244 [NSFont systemFontOfSize:[NSFont systemFontSize]], NSFontAttributeName,
245 ps, NSParagraphStyleAttributeName,
247 static NSDictionary* shortHeightAttribute = [[NSDictionary dictionaryWithObjectsAndKeys:
248 [NSFont systemFontOfSize:2.0], NSFontAttributeName,
251 // Title with summary
256 NSFileWrapper * wrapper = [[[NSFileWrapper alloc] initWithPath:[[NSBundle mainBundle] pathForImageResource: @"JobSmall"]] autorelease];
257 NSTextAttachment * imageAttachment = [[[NSTextAttachment alloc] initWithFileWrapper:wrapper] autorelease];
259 NSDictionary* imageAttributes = [NSDictionary dictionaryWithObjectsAndKeys:
260 [NSNumber numberWithFloat: -2.0], NSBaselineOffsetAttributeName,
261 imageAttachment, NSAttachmentAttributeName,
262 ps, NSParagraphStyleAttributeName,
265 NSAttributedString * imageAsString = [[[NSAttributedString alloc]
266 initWithString: [NSString stringWithFormat:@"%C%C", NSAttachmentCharacter, NSTabCharacter]
267 attributes: imageAttributes] autorelease];
269 [finalString appendAttributedString:imageAsString];
272 // Note: use title->name instead of title->dvd since name is just the chosen
273 // folder, instead of dvd which is the full path
274 [finalString appendString:[NSString stringWithUTF8String:title->name] withAttributes:titleAttribute];
276 NSString * summaryInfo;
278 NSString * chapterString = (hbJob->chapter_start == hbJob->chapter_end) ?
279 [NSString stringWithFormat:@"Chapter %d", hbJob->chapter_start] :
280 [NSString stringWithFormat:@"Chapters %d through %d", hbJob->chapter_start, hbJob->chapter_end];
282 BOOL hasIndepthScan = (hbJob->pass == -1);
283 int numVideoPasses = 0;
285 // To determine number of video passes, we need to skip past the subtitle scan.
288 // When job is the one currently being processed, then the next in its group
289 // is the the first job in the queue.
291 if (hbJob == hb_current_job(handle))
292 nextjob = hb_job(handle, 0);
294 nextjob = hb_next_job(handle, hbJob);
295 if (nextjob) // Overly cautious in case there is no next job!
296 numVideoPasses = MIN( 2, nextjob->pass + 1 );
299 numVideoPasses = MIN( 2, hbJob->pass + 1 );
301 if (hasIndepthScan && numVideoPasses == 1)
302 summaryInfo = [NSString stringWithFormat: @" (Title %d, %@, Deep Scan, Single Video Pass)", title->index, chapterString];
303 else if (hasIndepthScan && numVideoPasses > 1)
304 summaryInfo = [NSString stringWithFormat: @" (Title %d, %@, Deep Scan, %d Video Passes)", title->index, chapterString, numVideoPasses];
305 else if (numVideoPasses == 1)
306 summaryInfo = [NSString stringWithFormat: @" (Title %d, %@, Single Video Pass)", title->index, chapterString];
308 summaryInfo = [NSString stringWithFormat: @" (Title %d, %@, %d Video Passes)", title->index, chapterString, numVideoPasses];
310 [finalString appendString:[NSString stringWithFormat:@"%@\n", summaryInfo] withAttributes:detailAttribute];
312 // Insert a short-in-height line to put some white space after the title
313 [finalString appendString:@"\n" withAttributes:shortHeightAttribute];
316 // End of title stuff
324 NSString * imageName;
327 case -1: imageName = @"JobPassSubtitleSmall"; break;
328 case 0: imageName = @"JobPassFirstSmall"; break;
329 case 1: imageName = @"JobPassFirstSmall"; break;
330 case 2: imageName = @"JobPassSecondSmall"; break;
331 default: imageName = @"JobPassUnknownSmall"; break;
334 NSFileWrapper * wrapper = [[[NSFileWrapper alloc] initWithPath:[[NSBundle mainBundle] pathForImageResource: imageName]] autorelease];
335 NSTextAttachment * imageAttachment = [[[NSTextAttachment alloc] initWithFileWrapper:wrapper] autorelease];
337 NSDictionary* imageAttributes = [NSDictionary dictionaryWithObjectsAndKeys:
338 [NSNumber numberWithFloat: -2.0], NSBaselineOffsetAttributeName,
339 imageAttachment, NSAttachmentAttributeName,
340 ps, NSParagraphStyleAttributeName,
343 NSAttributedString * imageAsString = [[[NSAttributedString alloc]
344 initWithString: [NSString stringWithFormat:@"%C%C", NSAttachmentCharacter, NSTabCharacter]
345 attributes: imageAttributes] autorelease];
347 [finalString appendAttributedString:imageAsString];
350 NSString * jobPassName;
351 if (hbJob->pass == -1)
352 jobPassName = NSLocalizedString (@"Deep Scan", nil);
355 int passNum = MAX( 1, hbJob->pass );
357 jobPassName = NSLocalizedString (@"1st Pass", nil);
358 else if (passNum == 1)
359 jobPassName = NSLocalizedString (@"1st Pass", nil);
360 else if (passNum == 2)
361 jobPassName = NSLocalizedString (@"2nd Pass", nil);
363 jobPassName = [NSString stringWithFormat: NSLocalizedString(@"Pass %d", nil), passNum];
365 [finalString appendString:[NSString stringWithFormat:@"%@\n", jobPassName] withAttributes:detailBoldAttribute];
368 // Video Codec needed by FormatInfo and withVideoInfo
369 NSString * jobVideoCodec = nil;
370 if (withFormatInfo || withVideoInfo)
373 // Video Codec settings (Encoder in the gui)
374 if (hbJob->vcodec == HB_VCODEC_FFMPEG)
375 jobVideoCodec = @"FFmpeg"; // HB_VCODEC_FFMPEG
376 else if (hbJob->vcodec == HB_VCODEC_XVID)
377 jobVideoCodec = @"XviD"; // HB_VCODEC_XVID
378 else if (hbJob->vcodec == HB_VCODEC_X264)
380 // Deterimine for sure how we are now setting iPod uuid atom
381 if (hbJob->h264_level) // We are encoding for iPod
382 jobVideoCodec = @"x264 (H.264 iPod)"; // HB_VCODEC_X264
384 jobVideoCodec = @"x264 (H.264 Main)"; // HB_VCODEC_X264
387 if (jobVideoCodec == nil)
388 jobVideoCodec = @"unknown";
390 // Audio Codec needed by FormatInfo and AudioInfo
391 NSString * jobAudioCodec = nil;
392 if (withFormatInfo || withAudioInfo)
394 if (hbJob->acodec == 256)
395 jobAudioCodec = @"AAC"; // HB_ACODEC_FAAC
396 else if (hbJob->acodec == 512)
397 jobAudioCodec = @"MP3"; // HB_ACODEC_LAME
398 else if (hbJob->acodec == 1024)
399 jobAudioCodec = @"Vorbis"; // HB_ACODEC_VORBIS
400 else if (hbJob->acodec == 2048)
401 jobAudioCodec = @"AC3"; // HB_ACODEC_AC3
403 if (jobAudioCodec == nil)
404 jobAudioCodec = @"unknown";
409 NSString * jobFormatInfo;
410 // Muxer settings (File Format in the gui)
411 if (hbJob->mux == 65536 || hbJob->mux == 131072 || hbJob->mux == 1048576)
412 jobFormatInfo = @"MP4"; // HB_MUX_MP4,HB_MUX_PSP,HB_MUX_IPOD
413 else if (hbJob->mux == 262144)
414 jobFormatInfo = @"AVI"; // HB_MUX_AVI
415 else if (hbJob->mux == 524288)
416 jobFormatInfo = @"OGM"; // HB_MUX_OGM
417 else if (hbJob->mux == 2097152)
418 jobFormatInfo = @"MKV"; // HB_MUX_MKV
420 jobFormatInfo = @"unknown";
422 if (hbJob->chapter_markers == 1)
423 jobFormatInfo = [NSString stringWithFormat:@"%@ Container, %@ Video + %@ Audio, Chapter Markers\n", jobFormatInfo, jobVideoCodec, jobAudioCodec];
425 jobFormatInfo = [NSString stringWithFormat:@"%@ Container, %@ Video + %@ Audio\n", jobFormatInfo, jobVideoCodec, jobAudioCodec];
427 [finalString appendString: @"Format: " withAttributes:detailBoldAttribute];
428 [finalString appendString: jobFormatInfo withAttributes:detailAttribute];
433 [finalString appendString: @"Destination: " withAttributes:detailBoldAttribute];
434 [finalString appendString:[NSString stringWithFormat:@"%@\n", [NSString stringWithUTF8String:hbJob->file]] withAttributes:detailAttribute];
440 NSString * jobPictureInfo;
441 // integers for picture values deinterlace, crop[4], keep_ratio, grayscale, pixel_ratio, pixel_aspect_width, pixel_aspect_height,
442 // maxWidth, maxHeight
443 if (hbJob->pixel_ratio == 1)
445 int titlewidth = title->width - hbJob->crop[2] - hbJob->crop[3];
446 int displayparwidth = titlewidth * hbJob->pixel_aspect_width / hbJob->pixel_aspect_height;
447 int displayparheight = title->height - hbJob->crop[0] - hbJob->crop[1];
448 jobPictureInfo = [NSString stringWithFormat:@"%dx%d (%dx%d Anamorphic)", displayparwidth, displayparheight, hbJob->width, displayparheight];
451 jobPictureInfo = [NSString stringWithFormat:@"%dx%d", hbJob->width, hbJob->height];
452 if (hbJob->keep_ratio == 1)
453 jobPictureInfo = [jobPictureInfo stringByAppendingString:@" Keep Aspect Ratio"];
455 if (hbJob->grayscale == 1)
456 jobPictureInfo = [jobPictureInfo stringByAppendingString:@", Grayscale"];
458 if (hbJob->deinterlace == 1)
459 jobPictureInfo = [jobPictureInfo stringByAppendingString:@", Deinterlace"];
460 if (withIcon) // implies indent the info
461 [finalString appendString: @"\t" withAttributes:detailBoldAttribute];
462 [finalString appendString: @"Picture: " withAttributes:detailBoldAttribute];
463 [finalString appendString:[NSString stringWithFormat:@"%@\n", jobPictureInfo] withAttributes:detailAttribute];
468 NSString * jobVideoQuality;
469 NSString * jobVideoDetail;
471 if (hbJob->vquality <= 0 || hbJob->vquality >= 1)
472 jobVideoQuality = [NSString stringWithFormat:@"%d kbps", hbJob->vbitrate];
475 NSNumber * vidQuality;
476 vidQuality = [NSNumber numberWithInt:hbJob->vquality * 100];
477 // this is screwed up kind of. Needs to be formatted properly.
479 jobVideoQuality = [NSString stringWithFormat:@"%@%% CRF", vidQuality];
481 jobVideoQuality = [NSString stringWithFormat:@"%@%% CQP", vidQuality];
484 if (hbJob->vrate_base == 1126125)
487 jobVideoDetail = [NSString stringWithFormat:@"%@, %@, 23.976 fps", jobVideoCodec, jobVideoQuality];
489 else if (hbJob->vrate_base == 900900)
492 jobVideoDetail = [NSString stringWithFormat:@"%@, %@, 29.97 fps", jobVideoCodec, jobVideoQuality];
497 jobVideoDetail = [NSString stringWithFormat:@"%@, %@, %d fps", jobVideoCodec, jobVideoQuality, hbJob->vrate / hbJob->vrate_base];
499 if (withIcon) // implies indent the info
500 [finalString appendString: @"\t" withAttributes:detailBoldAttribute];
501 [finalString appendString: @"Video: " withAttributes:detailBoldAttribute];
502 [finalString appendString:[NSString stringWithFormat:@"%@\n", jobVideoDetail] withAttributes:detailAttribute];
507 if (hbJob->vcodec == HB_VCODEC_X264 && hbJob->x264opts)
509 if (withIcon) // implies indent the info
510 [finalString appendString: @"\t" withAttributes:detailBoldAttribute];
511 [finalString appendString: @"x264 Options: " withAttributes:detailBoldAttribute];
512 [finalString appendString:[NSString stringWithFormat:@"%@\n", [NSString stringWithUTF8String:hbJob->x264opts]] withAttributes:detailAttribute];
518 NSString * jobAudioInfo;
519 if ([jobAudioCodec isEqualToString: @"AC3"])
520 jobAudioInfo = [NSString stringWithFormat:@"%@, Pass-Through", jobAudioCodec];
522 jobAudioInfo = [NSString stringWithFormat:@"%@, %d kbps, %d Hz", jobAudioCodec, hbJob->abitrate, hbJob->arate];
524 // we now get the audio mixdown info for each of the two gui audio tracks
525 // lets do it the long way here to get a handle on things.
526 // Hardcoded for two tracks for gui: audio_mixdowns[i] audio_mixdowns[i]
527 int ai; // counter for each audios [] , macgui only allows for two audio tracks currently
528 for( ai = 0; ai < 2; ai++ )
530 if (hbJob->audio_mixdowns[ai] == HB_AMIXDOWN_MONO)
531 jobAudioInfo = [jobAudioInfo stringByAppendingString:[NSString stringWithFormat:@", Track %d: Mono", ai + 1]];
532 if (hbJob->audio_mixdowns[ai] == HB_AMIXDOWN_STEREO)
533 jobAudioInfo = [jobAudioInfo stringByAppendingString:[NSString stringWithFormat:@", Track %d: Stereo", ai + 1]];
534 if (hbJob->audio_mixdowns[ai] == HB_AMIXDOWN_DOLBY)
535 jobAudioInfo = [jobAudioInfo stringByAppendingString:[NSString stringWithFormat:@", Track %d: Dolby Surround", ai + 1]];
536 if (hbJob->audio_mixdowns[ai] == HB_AMIXDOWN_DOLBYPLII)
537 jobAudioInfo = [jobAudioInfo stringByAppendingString:[NSString stringWithFormat:@", Track %d: Dolby Pro Logic II", ai + 1]];
538 if (hbJob->audio_mixdowns[ai] == HB_AMIXDOWN_6CH)
539 jobAudioInfo = [jobAudioInfo stringByAppendingString:[NSString stringWithFormat:@", Track %d: 6-channel discreet", ai + 1]];
541 if (withIcon) // implies indent the info
542 [finalString appendString: @"\t" withAttributes:detailBoldAttribute];
543 [finalString appendString: @"Audio: " withAttributes:detailBoldAttribute];
544 [finalString appendString:[NSString stringWithFormat:@"%@\n", jobAudioInfo] withAttributes:detailAttribute];
547 if (withSubtitleInfo)
549 // hbJob->subtitle can == -1 in two cases:
550 // autoselect: when pass == -1
551 // none: when pass != -1
552 if ((hbJob->subtitle == -1) && (hbJob->pass == -1))
554 if (withIcon) // implies indent the info
555 [finalString appendString: @"\t" withAttributes:detailBoldAttribute];
556 [finalString appendString: @"Subtitles: " withAttributes:detailBoldAttribute];
557 [finalString appendString: @"Autoselect " withAttributes:detailAttribute];
559 else if (hbJob->subtitle >= 0)
561 hb_subtitle_t * subtitle = (hb_subtitle_t *) hb_list_item( title->list_subtitle, 0 );
564 if (withIcon) // implies indent the info
565 [finalString appendString: @"\t" withAttributes:detailBoldAttribute];
566 [finalString appendString: @"Subtitles: " withAttributes:detailBoldAttribute];
567 [finalString appendString: [NSString stringWithCString: subtitle->lang] withAttributes:detailAttribute];
573 if ([[finalString string] hasSuffix: @"\n"])
574 [finalString deleteCharactersInRange: NSMakeRange([[finalString string] length]-1, 1)];
583 //------------------------------------------------------------------------------------
585 //------------------------------------------------------------------------------------
587 @implementation HBJobGroup
589 + (HBJobGroup *) jobGroup;
591 return [[[HBJobGroup alloc] init] autorelease];
596 if (self = [super init])
598 fJobs = [[NSMutableArray arrayWithCapacity:0] retain];
599 fDescription = [[NSMutableAttributedString alloc] initWithString: @""];
600 [self setNeedsDescription: NO];
601 fStatus = HBStatusNone;
613 - (unsigned int) count
615 return [fJobs count];
618 - (void) addJob: (HBJob *)aJob
620 [fJobs addObject: aJob];
621 [self setNeedsDescription: YES];
622 fLastDescriptionHeight = 0;
623 fLastDescriptionWidth = 0;
626 - (void) removeAllJobs
628 [fJobs removeAllObjects];
631 - (HBJob *) jobAtIndex: (unsigned)index
633 return [fJobs objectAtIndex: index];
636 - (unsigned) indexOfJob: (HBJob *)aJob;
638 return [fJobs indexOfObject: aJob];
641 - (NSEnumerator *) jobEnumerator
643 return [fJobs objectEnumerator];
646 - (void) setNeedsDescription: (BOOL)flag
648 fNeedsDescription = flag;
651 - (void) updateDescriptionWithHBHandle: (hb_handle_t *)handle
653 fNeedsDescription = NO;
655 [fDescription deleteCharactersInRange: NSMakeRange(0, [fDescription length])];
657 if ([self count] == 0)
659 NSAssert(NO, @" jobgroup with no jobs");
663 HBJob * job = [self jobAtIndex:0];
665 [fDescription appendAttributedString: [job attributedDescriptionWithHBHandle: handle
675 withSubtitleInfo: NO]];
677 static NSAttributedString * carriageReturn = [[NSAttributedString alloc] initWithString:@"\n"];
679 NSEnumerator * e = [self jobEnumerator];
680 while ( (job = [e nextObject]) )
682 int pass = [job job]->pass;
683 [fDescription appendAttributedString:carriageReturn];
684 [fDescription appendAttributedString:
685 [job attributedDescriptionWithHBHandle: handle
691 withPictureInfo: pass != -1
692 withVideoInfo: pass != -1
693 withx264Info: pass != -1
694 withAudioInfo: pass == 0 || pass == 2
695 withSubtitleInfo: YES]];
700 - (NSMutableAttributedString *) attributedDescriptionWithHBHandle: (hb_handle_t *)handle
702 if (fNeedsDescription)
703 [self updateDescriptionWithHBHandle: handle];
707 - (float) heightOfDescriptionForWidth:(float)width withHBHandle: (hb_handle_t *)handle
709 // Try to return the cached value if no changes have happened since the last time
710 if ((width == fLastDescriptionWidth) && (fLastDescriptionHeight != 0) && !fNeedsDescription)
711 return fLastDescriptionHeight;
713 if (fNeedsDescription)
714 [self updateDescriptionWithHBHandle: handle];
716 // Calculate the height
717 NSRect bounds = [fDescription boundingRectWithSize:NSMakeSize(width, 10000) options:NSStringDrawingUsesLineFragmentOrigin];
718 fLastDescriptionHeight = bounds.size.height + 6.0; // add some border to bottom
719 fLastDescriptionWidth = width;
720 return fLastDescriptionHeight;
722 /* supposedly another way to do this, in case boundingRectWithSize isn't working
723 NSTextView* tmpView = [[NSTextView alloc] initWithFrame:NSMakeRect(0, 0, width, 1)];
724 [[tmpView textStorage] setAttributedString:aString];
725 [tmpView setHorizontallyResizable:NO];
726 [tmpView setVerticallyResizable:YES];
727 // [[tmpView textContainer] setHeightTracksTextView: YES];
728 // [[tmpView textContainer] setContainerSize: NSMakeSize(width, 10000)];
730 float height = [tmpView frame].size.height;
736 - (float) lastDescriptionHeight
738 return fLastDescriptionHeight;
741 - (void) setStatus: (HBQueueJobGroupStatus)status
743 self->fStatus = status;
746 - (HBQueueJobGroupStatus) status
748 return self->fStatus;
751 - (void) setPath: (NSString *)path
768 @interface HBQueueController (Private)
769 - (void)updateQueueUI;
772 // Toolbar identifiers
773 static NSString* HBQueueToolbar = @"HBQueueToolbar1";
774 static NSString* HBQueueStartCancelToolbarIdentifier = @"HBQueueStartCancelToolbarIdentifier";
775 static NSString* HBQueuePauseResumeToolbarIdentifier = @"HBQueuePauseResumeToolbarIdentifier";
779 @implementation HBQueueController
781 //------------------------------------------------------------------------------------
783 //------------------------------------------------------------------------------------
786 if (self = [super init])
789 [[NSUserDefaults standardUserDefaults] registerDefaults:[NSDictionary dictionaryWithObjectsAndKeys:
790 @"NO", @"QueueWindowIsOpen",
791 @"NO", @"QueueShowsDetail",
792 @"YES", @"QueueShowsJobsAsGroups",
795 fJobGroups = [[NSMutableArray arrayWithCapacity:0] retain];
796 fCompleted = [[NSMutableArray arrayWithCapacity:0] retain];
798 BOOL loadSucceeded = [NSBundle loadNibNamed:@"Queue" owner:self] && fQueueWindow;
799 NSAssert(loadSucceeded, @"Could not open Queue nib");
800 NSAssert(fQueueWindow, @"fQueueWindow not found in Queue nib");
805 //------------------------------------------------------------------------------------
807 //------------------------------------------------------------------------------------
810 // clear the delegate so that windowWillClose is not attempted
811 if ([fQueueWindow delegate] == self)
812 [fQueueWindow setDelegate:nil];
814 [fJobGroups release];
815 [fCompleted release];
816 [fCurrentJobGroup release];
817 [fSavedExpandedItems release];
818 [fSavedSelectedItems release];
823 //------------------------------------------------------------------------------------
825 //------------------------------------------------------------------------------------
826 - (void)setHandle: (hb_handle_t *)handle
831 //------------------------------------------------------------------------------------
832 // Receive HBController
833 //------------------------------------------------------------------------------------
834 - (void)setHBController: (HBController *)controller
836 fHBController = controller;
839 //------------------------------------------------------------------------------------
840 // Displays and brings the queue window to the front
841 //------------------------------------------------------------------------------------
842 - (IBAction) showQueueWindow: (id)sender
844 [self updateQueueUI];
845 [fQueueWindow makeKeyAndOrderFront: self];
846 [[NSUserDefaults standardUserDefaults] setBool:YES forKey:@"QueueWindowIsOpen"];
848 //------------------------------------------------------------------------------------
849 // Show or hide the current job pane (fCurrentJobPane).
850 //------------------------------------------------------------------------------------
851 - (void) showCurrentJobPane: (BOOL)showPane
853 if (showPane == fCurrentJobPaneShown)
856 // Things to keep in mind:
857 // - When the current job pane is shown, it occupies the upper portion of the
858 // window with the queue occupying the bottom portion of the window.
859 // - When the current job pane is hidden, it slides up and out of view.
860 // NSView setHidden is NOT used. The queue pane is resized to occupy the full
863 NSRect windowFrame = [[fCurrentJobPane superview] frame];
864 NSRect queueFrame, jobFrame;
866 NSDivideRect(windowFrame, &jobFrame, &queueFrame, NSHeight([fCurrentJobPane frame]), NSMaxYEdge);
869 queueFrame = windowFrame;
870 jobFrame = [fCurrentJobPane frame];
871 jobFrame.origin.y = NSHeight(windowFrame);
874 // Move fCurrentJobPane
875 NSDictionary * dict1 = [NSDictionary dictionaryWithObjectsAndKeys:
876 fCurrentJobPane, NSViewAnimationTargetKey,
877 [NSValue valueWithRect:jobFrame], NSViewAnimationEndFrameKey,
881 NSDictionary * dict2 = [NSDictionary dictionaryWithObjectsAndKeys:
882 fQueuePane, NSViewAnimationTargetKey,
883 [NSValue valueWithRect:queueFrame], NSViewAnimationEndFrameKey,
886 NSViewAnimation * anAnimation = [[[NSViewAnimation alloc] initWithViewAnimations:nil] autorelease];
887 [anAnimation setViewAnimations:[NSArray arrayWithObjects:dict1, dict2, nil]];
888 [anAnimation setDuration:0.25];
889 [anAnimation setAnimationBlockingMode:NSAnimationBlocking]; // prevent user from resizing the window during an animation
890 [anAnimation startAnimation];
892 fCurrentJobPaneShown = showPane;
895 //------------------------------------------------------------------------------------
896 // Rebuilds the contents of fJobGroups which is a hierarchy of HBJobGroup and HBJobs.
897 //------------------------------------------------------------------------------------
898 - (void)rebuildJobGroups
900 // Currently, job groups are rdered like this:
901 // Completed job groups
903 // Pending job groups
905 [fJobGroups autorelease];
906 fJobGroups = [[NSMutableArray arrayWithCapacity:0] retain];
908 // Add all the completed job groups
909 [fJobGroups addObjectsFromArray: fCompleted];
911 // Add all the completed job groups
912 if (fCurrentJobGroup)
913 [fJobGroups addObject: fCurrentJobGroup];
915 // Add all the pending job groups. These come from hblib
916 HBJobGroup * aJobGroup = [HBJobGroup jobGroup];
918 // If hblib is currently processing something, hb_group will skip over that group.
919 // And that's exactly what we want -- fJobGroups contains only pending job groups.
921 hb_job_t * nextJob = hb_group( fHandle, 0 );
924 if (nextJob->sequence_id == 0)
926 // Encountered a new group. Add the current one to fJobGroups and then start a new one.
927 if ([aJobGroup count] > 0)
929 [aJobGroup setStatus: HBStatusPending];
930 [fJobGroups addObject: aJobGroup];
931 aJobGroup = [HBJobGroup jobGroup];
934 [aJobGroup addJob: [HBJob jobWithJob:nextJob]];
935 [aJobGroup setPath: [NSString stringWithUTF8String:nextJob->file]];
936 nextJob = hb_next_job (fHandle, nextJob);
938 if ([aJobGroup count] > 0)
940 [aJobGroup setStatus: HBStatusPending];
941 [fJobGroups addObject:aJobGroup];
945 //------------------------------------------------------------------------------------
946 // Adds aJobGroup to the list of completed job groups, marking its status as
948 //------------------------------------------------------------------------------------
949 - (void) addCompletedJobGroup: (HBJobGroup *)aJobGroup
951 // Once hblib has completed its work, the hb_job_t objects will be freed, so we
952 // can't keep a reference to them.
953 [aJobGroup removeAllJobs];
955 [aJobGroup setStatus: HBStatusComplete];
957 // Put the group in the completed list for permanent storage, and also rebuild
958 // the master job group list which contains completed and pending groups.
959 [fCompleted addObject: aJobGroup];
962 - (void) setCurrentJobGroupFromJob: (hb_job_t *)aJob
964 HBJobGroup * aJobGroup = nil;
966 // Try to find this new group in our existing job groups.
970 NSEnumerator * groupEnum = [fJobGroups objectEnumerator];
971 while ( !found && (aJobGroup = [groupEnum nextObject]) )
974 NSEnumerator * jobEnum = [aJobGroup jobEnumerator];
975 while ( !found && (j = [jobEnum nextObject]) )
982 // Or create the job group.
985 aJobGroup = [HBJobGroup jobGroup];
986 [aJobGroup addJob: [HBJob jobWithJob: aJob]];
987 [aJobGroup setPath: [NSString stringWithUTF8String:aJob->file]];
988 while ( (aJob = hb_next_job(fHandle, aJob)) && (aJob->sequence_id != 0) )
989 [aJobGroup addJob: [HBJob jobWithJob: aJob]];
991 [aJobGroup updateDescriptionWithHBHandle: fHandle];
994 [aJobGroup setStatus: HBStatusWorking];
998 [fCurrentJobGroup release];
999 fCurrentJobGroup = aJobGroup;
1003 #pragma mark UI Updating
1005 //------------------------------------------------------------------------------------
1006 // Saves the state of the items that are currently expanded. Calling restoreOutlineViewState
1007 // will restore the state of all items to match what was saved by saveOutlineViewState.
1008 //------------------------------------------------------------------------------------
1009 - (void) saveOutlineViewState
1011 if (!fSavedExpandedItems)
1012 fSavedExpandedItems = [[NSMutableIndexSet alloc] init];
1014 [fSavedExpandedItems removeAllIndexes];
1016 // NB: This code is stuffing the address of each job into an index set. While it
1017 // works 99.9% of the time, it's not the ideal solution. We need unique ids in
1018 // each job, possibly using the existing sequence_id field. Could use the high
1019 // word as a unique encode id and the low word the sequence number.
1021 HBJobGroup * aJobGroup;
1022 NSEnumerator * e = [fJobGroups objectEnumerator];
1023 while ( (aJobGroup = [e nextObject]) )
1025 if ([fOutlineView isItemExpanded: aJobGroup])
1027 if ([aJobGroup status] == HBStatusComplete)
1028 [fSavedExpandedItems addIndex: (unsigned int)aJobGroup];
1030 [fSavedExpandedItems addIndex: (unsigned int)[[aJobGroup jobAtIndex:0] job]];
1034 // Save the selection also. This is really UGLY code. Since I have to rebuild the
1035 // entire outline hierachy every time hblib changes its job list, there's no easy
1036 // way for me to remember the selection state other than saving off the first
1037 // hb_job_t item in each selected group. This is done by saving the object's
1038 // address. This could go away if I'd save a unique id in each job object.
1040 if (!fSavedSelectedItems)
1041 fSavedSelectedItems = [[NSMutableIndexSet alloc] init];
1043 [fSavedSelectedItems removeAllIndexes];
1045 NSIndexSet * selectedRows = [fOutlineView selectedRowIndexes];
1046 int row = [selectedRows firstIndex];
1047 while (row != NSNotFound)
1049 aJobGroup = [fOutlineView itemAtRow: row];
1050 if ([aJobGroup status] == HBStatusComplete)
1051 [fSavedSelectedItems addIndex: (unsigned int)aJobGroup];
1053 [fSavedSelectedItems addIndex: (unsigned int)[[aJobGroup jobAtIndex:0] job]];
1054 row = [selectedRows indexGreaterThanIndex: row];
1059 //------------------------------------------------------------------------------------
1060 // Restores the expanded state of items in the outline view to match those saved by a
1061 // previous call to saveOutlineViewState.
1062 //------------------------------------------------------------------------------------
1063 - (void) restoreOutlineViewState
1065 if (fSavedExpandedItems)
1067 HBJobGroup * aJobGroup;
1068 NSEnumerator * e = [fJobGroups objectEnumerator];
1069 while ( (aJobGroup = [e nextObject]) )
1071 if ([aJobGroup status] == HBStatusComplete)
1073 if ([fSavedExpandedItems containsIndex: (unsigned int)aJobGroup])
1074 [fOutlineView expandItem: aJobGroup];
1078 hb_job_t * j = [[aJobGroup jobAtIndex:0] job];
1079 if ([fSavedExpandedItems containsIndex: (unsigned int)j])
1080 [fOutlineView expandItem: aJobGroup];
1085 if (fSavedSelectedItems)
1087 // Ugh. Have to cycle through each row looking for the previously selected job.
1088 // See the explanation in saveOutlineViewState about the logic here.
1090 NSMutableIndexSet * rowsToSelect = [[[NSMutableIndexSet alloc] init] autorelease];
1091 for (int i = 0; i < [fOutlineView numberOfRows]; i++)
1093 HBJobGroup * aJobGroup = [fOutlineView itemAtRow: i];
1094 // Test to see if the group or the group's first job is a match
1095 if ([aJobGroup status] == HBStatusComplete)
1097 if ([fSavedSelectedItems containsIndex: (unsigned int)aJobGroup])
1098 [rowsToSelect addIndex: i];
1102 if ([fSavedSelectedItems containsIndex: (unsigned int)[[aJobGroup jobAtIndex:0] job]])
1103 [rowsToSelect addIndex: i];
1106 if ([rowsToSelect count] == 0)
1107 [fOutlineView deselectAll: nil];
1109 [fOutlineView selectRowIndexes:rowsToSelect byExtendingSelection:NO];
1113 //------------------------------------------------------------------------------------
1114 // If a job is currently processing, its job icon in the queue outline view is
1115 // animated to its next state.
1116 //------------------------------------------------------------------------------------
1117 - (void) animateCurrentJobGroupInQueue:(NSTimer*)theTimer
1119 int row = [fOutlineView rowForItem: fCurrentJobGroup];
1120 int col = [fOutlineView columnWithIdentifier: @"icon"];
1121 if (row != -1 && col != -1)
1124 fAnimationIndex %= 6; // there are 6 animation images; see outlineView:objectValueForTableColumn:byItem: below.
1125 NSRect frame = [fOutlineView frameOfCellAtColumn:col row:row];
1126 [fOutlineView setNeedsDisplayInRect: frame];
1130 //------------------------------------------------------------------------------------
1131 // Starts animating the job icon of the currently processing job in the queue outline
1133 //------------------------------------------------------------------------------------
1134 - (void) startAnimatingCurrentJobGroupInQueue
1136 if (!fAnimationTimer)
1137 fAnimationTimer = [[NSTimer scheduledTimerWithTimeInterval:1.0/12.0 // 1/12 because there are 6 images in the animation cycle
1139 selector:@selector(animateCurrentJobGroupInQueue:)
1141 repeats:YES] retain];
1144 //------------------------------------------------------------------------------------
1145 // Stops animating the job icon of the currently processing job in the queue outline
1147 //------------------------------------------------------------------------------------
1148 - (void) stopAnimatingCurrentJobGroupInQueue
1150 if (fAnimationTimer && [fAnimationTimer isValid])
1152 [fAnimationTimer invalidate];
1153 [fAnimationTimer release];
1154 fAnimationTimer = nil;
1158 //------------------------------------------------------------------------------------
1159 // Generate string to display in UI.
1160 //------------------------------------------------------------------------------------
1161 - (NSString *) progressStatusStringForJob: (hb_job_t *)job state: (hb_state_t *)s
1163 if (s->state == HB_STATE_WORKING)
1166 if (job->pass == -1)
1167 msg = NSLocalizedString( @"Deep Scan", nil );
1168 else if (job->pass == 1)
1169 msg = NSLocalizedString( @"Analyzing video", nil );
1170 else if ((job->pass == 0) || (job->pass == 2))
1171 msg = NSLocalizedString( @"Encoding movie", nil );
1173 return @""; // unknown condition!
1175 if( s->param.working.seconds > -1 )
1177 return [NSString stringWithFormat:
1178 NSLocalizedString( @"%@ (%.2f fps, avg %.2f fps)", nil ),
1179 msg, s->param.working.rate_cur, s->param.working.rate_avg];
1186 else if (s->state == HB_STATE_MUXING)
1187 return NSLocalizedString( @"Muxing", nil );
1189 else if (s->state == HB_STATE_PAUSED)
1190 return NSLocalizedString( @"Paused", nil );
1192 else if (s->state == HB_STATE_WORKDONE)
1193 return NSLocalizedString( @"Done", nil );
1198 //------------------------------------------------------------------------------------
1199 // Generate string to display in UI.
1200 //------------------------------------------------------------------------------------
1201 - (NSString *) progressTimeRemainingStringForJob: (hb_job_t *)job state: (hb_state_t *)s
1203 if (s->state == HB_STATE_WORKING)
1205 #define p s->param.working
1209 // Minutes always needed
1212 minutes = [NSString stringWithFormat:NSLocalizedString( @"%d minutes ", nil ), p.minutes];
1213 else if (p.minutes == 1)
1214 minutes = NSLocalizedString( @"1 minute ", nil );
1222 hours = [NSString stringWithFormat:NSLocalizedString( @"%d hours ", nil ), p.hours];
1224 hours = NSLocalizedString( @"1 hour ", nil );
1226 return [NSString stringWithFormat:NSLocalizedString( @"%@%@remaining", nil ), hours, minutes];
1233 seconds = [NSString stringWithFormat:NSLocalizedString( @"%d seconds ", nil ), p.seconds];
1235 seconds = NSLocalizedString( @"1 second ", nil );
1237 return [NSString stringWithFormat:NSLocalizedString( @"%@%@remaining", nil ), minutes, seconds];
1240 /* here is code that does it more like the Finder
1241 if( p.seconds > -1 )
1243 float estHours = (p.hours + (p.minutes / 60.0));
1244 float estMinutes = (p.minutes + (p.seconds / 60.0));
1247 return [NSString stringWithFormat:NSLocalizedString( @"Time remaining: About %d hours", nil ), lrintf(estHours)];
1248 else if (estHours > 0.983) // 59 minutes
1249 return NSLocalizedString( @"Time remaining: About 1 hour", nil );
1250 else if (estMinutes > 1.5)
1251 return [NSString stringWithFormat:NSLocalizedString( @"Time remaining: About %d minutes", nil ), lrintf(estMinutes)];
1252 else if (estMinutes > 0.983) // 59 seconds
1253 return NSLocalizedString( @"Time remaining: About 1 minute", nil );
1254 else if (p.seconds <= 5)
1255 return NSLocalizedString( @"Time remaining: Less than 5 seconds", nil );
1256 else if (p.seconds <= 10)
1257 return NSLocalizedString( @"Time remaining: Less than 10 seconds", nil );
1259 return NSLocalizedString( @"Time remaining: Less than 1 minute", nil );
1262 return NSLocalizedString( @"Time remaining: Calculating...", nil );
1270 //------------------------------------------------------------------------------------
1271 // Refresh progress bar (fProgressTextField) from current state.
1272 //------------------------------------------------------------------------------------
1273 - (void) updateProgressTextForJob: (hb_job_t *)job state: (hb_state_t *)s
1275 NSString * statusMsg = [self progressStatusStringForJob:job state:s];
1276 NSString * timeMsg = [self progressTimeRemainingStringForJob:job state:s];
1277 if ([timeMsg length] > 0)
1278 statusMsg = [NSString stringWithFormat:@"%@ - %@", statusMsg, timeMsg];
1279 [fProgressTextField setStringValue:statusMsg];
1282 //------------------------------------------------------------------------------------
1283 // Refresh progress bar (fProgressBar) from current state.
1284 //------------------------------------------------------------------------------------
1285 - (void) updateProgressBarWithState: (hb_state_t *)s
1287 if (s->state == HB_STATE_WORKING)
1289 #define p s->param.working
1290 [fProgressBar setIndeterminate:NO];
1291 float progress_total = 100.0 * ( p.progress + p.job_cur - 1 ) / p.job_count;
1292 [fProgressBar setDoubleValue:progress_total];
1296 else if (s->state == HB_STATE_MUXING)
1298 #define p s->param.muxing
1299 [fProgressBar setIndeterminate:YES];
1300 [fProgressBar startAnimation:nil];
1304 else if (s->state == HB_STATE_WORKDONE)
1306 [fProgressBar setIndeterminate:NO];
1307 [fProgressBar stopAnimation:nil];
1308 [fProgressBar setDoubleValue:0.0];
1312 [fProgressBar stopAnimation:nil]; // just in case in was animating
1315 //------------------------------------------------------------------------------------
1316 // Refresh queue count text field (fQueueCountField).
1317 //------------------------------------------------------------------------------------
1318 - (void)updateQueueCountField
1323 jobCount = fHandle ? hb_group_count(fHandle) : 0;
1325 msg = NSLocalizedString(@"1 pending encode", nil);
1327 msg = [NSString stringWithFormat:NSLocalizedString(@"%d pending encodes", nil), jobCount];
1329 [fQueueCountField setStringValue:msg];
1332 //------------------------------------------------------------------------------------
1333 // Refresh the UI in the current job pane. Should be called whenever the current job
1334 // being processed has changed.
1335 //------------------------------------------------------------------------------------
1336 - (void)updateCurrentJobDescription
1338 hb_job_t * job = fHandle ? hb_current_job(fHandle) : nil;
1342 HBJob * currentJob = [HBJob jobWithJob: job];
1345 case -1: // Subtitle scan
1346 [fJobDescTextField setAttributedStringValue:
1347 [currentJob attributedDescriptionWithHBHandle:fHandle
1357 withSubtitleInfo: YES]];
1360 case 1: // video 1st pass
1361 [fJobDescTextField setAttributedStringValue:
1362 [currentJob attributedDescriptionWithHBHandle:fHandle
1368 withPictureInfo: YES
1372 withSubtitleInfo: NO]];
1375 case 0: // single pass
1376 case 2: // video 2nd pass + audio
1377 [fJobDescTextField setAttributedStringValue:
1378 [currentJob attributedDescriptionWithHBHandle:fHandle
1384 withPictureInfo: YES
1388 withSubtitleInfo: YES]];
1392 [fJobDescTextField setAttributedStringValue:
1393 [currentJob attributedDescriptionWithHBHandle:fHandle
1399 withPictureInfo: YES
1403 withSubtitleInfo: YES]];
1408 [fJobDescTextField setStringValue: @"No encodes pending"];
1411 //------------------------------------------------------------------------------------
1412 // Refresh the UI in the current job pane. Should be called whenever the current job
1413 // being processed has changed or when progress has changed.
1414 //------------------------------------------------------------------------------------
1415 - (void)updateCurrentJobProgress
1417 hb_job_t * job = fHandle ? hb_current_job(fHandle) : nil;
1419 hb_get_state2( fHandle, &s );
1420 [self updateProgressTextForJob: job state: &s];
1421 [self updateProgressBarWithState:&s];
1424 //------------------------------------------------------------------------------------
1425 // Refresh the UI in the queue pane. Should be called whenever the content of HB's job
1426 // list has changed so that HBQueueController can sync up.
1427 //------------------------------------------------------------------------------------
1428 - (void)updateQueueUI
1430 [self saveOutlineViewState];
1431 [self rebuildJobGroups];
1432 [fOutlineView noteNumberOfRowsChanged];
1433 [fOutlineView reloadData];
1434 [self restoreOutlineViewState];
1435 [self updateQueueCountField];
1439 #pragma mark Actions
1441 //------------------------------------------------------------------------------------
1442 // Deletes the selected jobs from HB and the queue UI
1443 //------------------------------------------------------------------------------------
1444 - (IBAction)removeSelectedJobGroups: (id)sender
1446 if (!fHandle) return;
1448 NSIndexSet * selectedRows = [fOutlineView selectedRowIndexes];
1449 int row = [selectedRows firstIndex];
1450 if (row != NSNotFound)
1452 while (row != NSNotFound)
1454 HBJobGroup * jobGroup = [fOutlineView itemAtRow: row];
1455 switch ([jobGroup status])
1457 case HBStatusComplete:
1458 case HBStatusCanceled:
1459 [fCompleted removeObject: jobGroup];
1461 case HBStatusWorking:
1462 [self cancelCurrentJob: sender];
1464 case HBStatusPending:
1465 hb_job_t * job = [[jobGroup jobAtIndex: 0] job];
1466 hb_rem_group( fHandle, job );
1472 row = [selectedRows indexGreaterThanIndex: row];
1475 [self hblibJobListChanged];
1479 //------------------------------------------------------------------------------------
1480 // Reveals the file icons in the Finder of the selected job groups.
1481 //------------------------------------------------------------------------------------
1482 - (IBAction)revealSelectedJobGroups: (id)sender
1484 if (!fHandle) return;
1486 NSIndexSet * selectedRows = [fOutlineView selectedRowIndexes];
1487 int row = [selectedRows firstIndex];
1488 if (row != NSNotFound)
1490 while (row != NSNotFound)
1492 HBJobGroup * jobGroup = [fOutlineView itemAtRow: row];
1493 if ([[jobGroup path] length])
1494 [[NSWorkspace sharedWorkspace] selectFile:[jobGroup path] inFileViewerRootedAtPath:nil];
1496 row = [selectedRows indexGreaterThanIndex: row];
1501 //------------------------------------------------------------------------------------
1502 // Calls HBController Cancel: which displays an alert asking user if they want to
1503 // cancel encoding of current job. cancelCurrentJob: returns immediately after posting
1504 // the alert. Later, when the user acknowledges the alert, HBController will call
1505 // hblib to cancel the job.
1506 //------------------------------------------------------------------------------------
1507 - (IBAction)cancelCurrentJob: (id)sender
1509 [fHBController Cancel:sender];
1512 //------------------------------------------------------------------------------------
1513 // Starts or cancels the processing of jobs depending on the current state
1514 //------------------------------------------------------------------------------------
1515 - (IBAction)toggleStartCancel: (id)sender
1517 if (!fHandle) return;
1520 hb_get_state2 (fHandle, &s);
1522 if ((s.state == HB_STATE_PAUSED) || (s.state == HB_STATE_WORKING) || (s.state == HB_STATE_MUXING))
1523 [fHBController Cancel: fQueuePane]; // sender == fQueuePane so that warning alert shows up on queue window
1525 else if (hb_group_count(fHandle) > 0)
1526 [fHBController doRip];
1529 //------------------------------------------------------------------------------------
1530 // Toggles the pause/resume state of hblib
1531 //------------------------------------------------------------------------------------
1532 - (IBAction)togglePauseResume: (id)sender
1534 if (!fHandle) return;
1537 hb_get_state2 (fHandle, &s);
1539 if (s.state == HB_STATE_PAUSED)
1540 hb_resume (fHandle);
1541 else if ((s.state == HB_STATE_WORKING) || (s.state == HB_STATE_MUXING))
1546 #pragma mark Synchronizing with hblib
1548 //------------------------------------------------------------------------------------
1549 // Notifies HBQueueController that hblib's current job has changed
1550 //------------------------------------------------------------------------------------
1551 - (void)currentJobGroupChanged: (hb_job_t *) currentJob
1553 if (fCurrentJobGroup && [fCurrentJobGroup status] != HBStatusCanceled)
1554 [self addCompletedJobGroup: fCurrentJobGroup];
1555 [self setCurrentJobGroupFromJob: currentJob];
1556 [self updateCurrentJobDescription];
1557 [self updateCurrentJobProgress];
1558 [self showCurrentJobPane: fCurrentJobGroup != nil];
1559 if (fCurrentJobGroup)
1560 [self startAnimatingCurrentJobGroupInQueue];
1562 [self stopAnimatingCurrentJobGroupInQueue];
1565 //------------------------------------------------------------------------------------
1566 // Notifies HBQueueController that hb_stop is about to be called. This signals us that
1567 // the current job is going to be canceled and deleted. This is somewhat of a hack to
1568 // let HBQueueController know when a job group has been cancelled. Otherwise, we'd
1569 // have no way of knowing if a job was canceled or completed sucessfully.
1570 //------------------------------------------------------------------------------------
1571 - (void)hblibWillStop
1573 if (fCurrentJobGroup)
1574 [fCurrentJobGroup setStatus: HBStatusCanceled];
1577 //------------------------------------------------------------------------------------
1578 // Notifies HBQueueController that hblib's job list has been modified
1579 //------------------------------------------------------------------------------------
1580 - (void)hblibJobListChanged
1582 // This message is received from HBController after it has added a job group to
1583 // hblib's job list. It is also received from self when a job group is deleted by
1585 [self updateQueueUI];
1588 //------------------------------------------------------------------------------------
1589 // Notifies HBQueueController that hblib's state has changed
1590 //------------------------------------------------------------------------------------
1591 - (void)hblibStateChanged: (hb_state_t &)state
1593 // First check to see if hblib has moved on to another job. We get no direct
1594 // message when this happens, so we have to detect it ourself. The new job could
1595 // be either just the next job in the current group, or the start of a new group.
1596 if (fLastKnownCurrentJob != hb_current_job(fHandle))
1598 hb_job_t * currentJob = hb_current_job(fHandle);
1599 if (!currentJob || currentJob->sequence_id == 0) // start of a new group
1601 [self currentJobGroupChanged: currentJob];
1602 [self hblibJobListChanged];
1606 [self updateCurrentJobDescription];
1607 [self updateCurrentJobProgress];
1610 fLastKnownCurrentJob = currentJob;
1613 switch( state.state )
1615 case HB_STATE_WORKING:
1617 [self updateCurrentJobProgress];
1618 [self startAnimatingCurrentJobGroupInQueue];
1622 case HB_STATE_MUXING:
1624 [self updateCurrentJobProgress];
1628 case HB_STATE_PAUSED:
1630 [self updateCurrentJobProgress];
1631 [self stopAnimatingCurrentJobGroupInQueue];
1635 case HB_STATE_WORKDONE:
1637 // HB_STATE_WORKDONE means that hblib has finished processing all the jobs
1638 // in *its* queue. This message is NOT sent as each individual job is
1646 #if HB_OUTLINE_METRIC_CONTROLS
1647 static float spacingWidth = 3.0;
1648 - (IBAction)imageSpacingChanged: (id)sender;
1650 spacingWidth = [sender floatValue];
1651 [fOutlineView setNeedsDisplay: YES];
1653 - (IBAction)indentChanged: (id)sender
1655 [fOutlineView setIndentationPerLevel: [sender floatValue]];
1656 [fOutlineView setNeedsDisplay: YES];
1662 #pragma mark Toolbar
1664 //------------------------------------------------------------------------------------
1666 //------------------------------------------------------------------------------------
1667 - (void)setupToolbar
1669 // Create a new toolbar instance, and attach it to our window
1670 NSToolbar *toolbar = [[[NSToolbar alloc] initWithIdentifier: HBQueueToolbar] autorelease];
1672 // Set up toolbar properties: Allow customization, give a default display mode, and remember state in user defaults
1673 [toolbar setAllowsUserCustomization: YES];
1674 [toolbar setAutosavesConfiguration: YES];
1675 [toolbar setDisplayMode: NSToolbarDisplayModeIconAndLabel];
1677 // We are the delegate
1678 [toolbar setDelegate: self];
1680 // Attach the toolbar to our window
1681 [fQueueWindow setToolbar: toolbar];
1684 //------------------------------------------------------------------------------------
1685 // toolbar:itemForItemIdentifier:willBeInsertedIntoToolbar:
1686 //------------------------------------------------------------------------------------
1687 - (NSToolbarItem *)toolbar:(NSToolbar *)toolbar
1688 itemForItemIdentifier:(NSString *)itemIdentifier
1689 willBeInsertedIntoToolbar:(BOOL)flag
1691 // Required delegate method: Given an item identifier, this method returns an item.
1692 // The toolbar will use this method to obtain toolbar items that can be displayed
1693 // in the customization sheet, or in the toolbar itself.
1695 NSToolbarItem *toolbarItem = nil;
1697 if ([itemIdentifier isEqual: HBQueueStartCancelToolbarIdentifier])
1699 toolbarItem = [[[NSToolbarItem alloc] initWithItemIdentifier: itemIdentifier] autorelease];
1701 // Set the text label to be displayed in the toolbar and customization palette
1702 [toolbarItem setLabel: @"Start"];
1703 [toolbarItem setPaletteLabel: @"Start/Cancel"];
1705 // Set up a reasonable tooltip, and image
1706 [toolbarItem setToolTip: @"Start Encoding"];
1707 [toolbarItem setImage: [NSImage imageNamed: @"Play"]];
1709 // Tell the item what message to send when it is clicked
1710 [toolbarItem setTarget: self];
1711 [toolbarItem setAction: @selector(toggleStartCancel:)];
1714 if ([itemIdentifier isEqual: HBQueuePauseResumeToolbarIdentifier])
1716 toolbarItem = [[[NSToolbarItem alloc] initWithItemIdentifier: itemIdentifier] autorelease];
1718 // Set the text label to be displayed in the toolbar and customization palette
1719 [toolbarItem setLabel: @"Pause"];
1720 [toolbarItem setPaletteLabel: @"Pause/Resume"];
1722 // Set up a reasonable tooltip, and image
1723 [toolbarItem setToolTip: @"Pause Encoding"];
1724 [toolbarItem setImage: [NSImage imageNamed: @"Pause"]];
1726 // Tell the item what message to send when it is clicked
1727 [toolbarItem setTarget: self];
1728 [toolbarItem setAction: @selector(togglePauseResume:)];
1734 //------------------------------------------------------------------------------------
1735 // toolbarDefaultItemIdentifiers:
1736 //------------------------------------------------------------------------------------
1737 - (NSArray *) toolbarDefaultItemIdentifiers: (NSToolbar *) toolbar
1739 // Required delegate method: Returns the ordered list of items to be shown in the
1740 // toolbar by default.
1742 return [NSArray arrayWithObjects:
1743 HBQueueStartCancelToolbarIdentifier,
1744 HBQueuePauseResumeToolbarIdentifier,
1748 //------------------------------------------------------------------------------------
1749 // toolbarAllowedItemIdentifiers:
1750 //------------------------------------------------------------------------------------
1751 - (NSArray *) toolbarAllowedItemIdentifiers: (NSToolbar *) toolbar
1753 // Required delegate method: Returns the list of all allowed items by identifier.
1754 // By default, the toolbar does not assume any items are allowed, even the
1755 // separator. So, every allowed item must be explicitly listed.
1757 return [NSArray arrayWithObjects:
1758 HBQueueStartCancelToolbarIdentifier,
1759 HBQueuePauseResumeToolbarIdentifier,
1760 NSToolbarCustomizeToolbarItemIdentifier,
1761 NSToolbarFlexibleSpaceItemIdentifier,
1762 NSToolbarSpaceItemIdentifier,
1763 NSToolbarSeparatorItemIdentifier,
1767 //------------------------------------------------------------------------------------
1768 // validateToolbarItem:
1769 //------------------------------------------------------------------------------------
1770 - (BOOL) validateToolbarItem: (NSToolbarItem *) toolbarItem
1772 // Optional method: This message is sent to us since we are the target of some
1773 // toolbar item actions.
1775 if (!fHandle) return NO;
1780 hb_get_state2 (fHandle, &s);
1782 if ([[toolbarItem itemIdentifier] isEqual: HBQueueStartCancelToolbarIdentifier])
1784 if ((s.state == HB_STATE_PAUSED) || (s.state == HB_STATE_WORKING) || (s.state == HB_STATE_MUXING))
1787 [toolbarItem setImage:[NSImage imageNamed: @"Stop"]];
1788 [toolbarItem setLabel: @"Stop"];
1789 [toolbarItem setToolTip: @"Stop Encoding"];
1792 else if (hb_count(fHandle) > 0)
1795 [toolbarItem setImage:[NSImage imageNamed: @"Play"]];
1796 [toolbarItem setLabel: @"Start"];
1797 [toolbarItem setToolTip: @"Start Encoding"];
1803 [toolbarItem setImage:[NSImage imageNamed: @"Play"]];
1804 [toolbarItem setLabel: @"Start"];
1805 [toolbarItem setToolTip: @"Start Encoding"];
1809 if ([[toolbarItem itemIdentifier] isEqual: HBQueuePauseResumeToolbarIdentifier])
1811 if (s.state == HB_STATE_PAUSED)
1814 [toolbarItem setImage:[NSImage imageNamed: @"Play"]];
1815 [toolbarItem setLabel: @"Resume"];
1816 [toolbarItem setToolTip: @"Resume Encoding"];
1819 else if ((s.state == HB_STATE_WORKING) || (s.state == HB_STATE_MUXING))
1822 [toolbarItem setImage:[NSImage imageNamed: @"Pause"]];
1823 [toolbarItem setLabel: @"Pause"];
1824 [toolbarItem setToolTip: @"Pause Encoding"];
1829 [toolbarItem setImage:[NSImage imageNamed: @"Pause"]];
1830 [toolbarItem setLabel: @"Pause"];
1831 [toolbarItem setToolTip: @"Pause Encoding"];
1840 //------------------------------------------------------------------------------------
1842 //------------------------------------------------------------------------------------
1843 - (void)awakeFromNib
1845 [self setupToolbar];
1847 if (![fQueueWindow setFrameUsingName:@"Queue"])
1848 [fQueueWindow center];
1849 [fQueueWindow setFrameAutosaveName: @"Queue"];
1850 [fQueueWindow setExcludedFromWindowsMenu:YES];
1852 #if HB_QUEUE_DRAGGING
1853 [fOutlineView registerForDraggedTypes: [NSArray arrayWithObject:HBQueuePboardType] ];
1854 [fOutlineView setDraggingSourceOperationMask:NSDragOperationEvery forLocal:YES];
1855 [fOutlineView setVerticalMotionCanBeginDrag: YES];
1858 // Don't allow autoresizing of main column, else the "delete" column will get
1859 // pushed out of view.
1860 [fOutlineView setAutoresizesOutlineColumn: NO];
1862 #if HB_OUTLINE_METRIC_CONTROLS
1863 [fIndentation setHidden: NO];
1864 [fSpacing setHidden: NO];
1865 [fIndentation setIntValue:[fOutlineView indentationPerLevel]]; // debug
1866 [fSpacing setIntValue:3]; // debug
1869 // Show/hide UI elements
1870 fCurrentJobPaneShown = YES; // it's shown in the nib
1871 [self showCurrentJobPane:NO];
1875 //------------------------------------------------------------------------------------
1877 //------------------------------------------------------------------------------------
1878 - (void)windowWillClose:(NSNotification *)aNotification
1880 [[NSUserDefaults standardUserDefaults] setBool:NO forKey:@"QueueWindowIsOpen"];
1885 - (void)moveObjectsInArray:(NSMutableArray *)array fromIndexes:(NSIndexSet *)indexSet toIndex:(unsigned)insertIndex
1887 unsigned index = [indexSet lastIndex];
1888 unsigned aboveInsertIndexCount = 0;
1890 while (index != NSNotFound)
1892 unsigned removeIndex;
1894 if (index >= insertIndex)
1896 removeIndex = index + aboveInsertIndexCount;
1897 aboveInsertIndexCount++;
1901 removeIndex = index;
1905 id object = [[array objectAtIndex:removeIndex] retain];
1906 [array removeObjectAtIndex:removeIndex];
1907 [array insertObject:object atIndex:insertIndex];
1910 index = [indexSet indexLessThanIndex:index];
1915 #pragma mark NSOutlineView delegate
1917 - (id)outlineView:(NSOutlineView *)outlineView child:(int)index ofItem:(id)item
1920 return [fJobGroups objectAtIndex:index];
1922 // We are only one level deep, so we can't be asked about children
1923 NSAssert (NO, @"HBQueueController outlineView:child:ofItem: can't handle nested items.");
1927 - (BOOL)outlineView:(NSOutlineView *)outlineView isItemExpandable:(id)item
1929 // Our outline view has no levels, but we can still expand every item. Doing so
1930 // just makes the row taller. See heightOfRowByItem below.
1934 - (BOOL)outlineView:(NSOutlineView *)outlineView shouldExpandItem:(id)item
1936 // Our outline view has no levels, but we can still expand every item. Doing so
1937 // just makes the row taller. See heightOfRowByItem below.
1938 #if HB_QUEUE_DRAGGING
1939 // Don't autoexpand while dragging, since we can't drop into the items
1940 return ![(HBQueueOutlineView*)outlineView isDragging];
1946 - (int)outlineView:(NSOutlineView *)outlineView numberOfChildrenOfItem:(id)item
1948 // Our outline view has no levels, so number of children will be zero for all
1951 return [fJobGroups count];
1956 - (void)outlineViewItemDidCollapse:(NSNotification *)notification
1958 id item = [[notification userInfo] objectForKey:@"NSObject"];
1959 int row = [fOutlineView rowForItem:item];
1960 [fOutlineView noteHeightOfRowsWithIndexesChanged:[NSIndexSet indexSetWithIndexesInRange:NSMakeRange(row,1)]];
1963 - (void)outlineViewItemDidExpand:(NSNotification *)notification
1965 id item = [[notification userInfo] objectForKey:@"NSObject"];
1966 int row = [fOutlineView rowForItem:item];
1967 [fOutlineView noteHeightOfRowsWithIndexesChanged:[NSIndexSet indexSetWithIndexesInRange:NSMakeRange(row,1)]];
1970 - (float)outlineView:(NSOutlineView *)outlineView heightOfRowByItem:(id)item
1972 if ([outlineView isItemExpanded: item])
1974 // Short-circuit here if in a live resize primarily to fix a bug but also to
1975 // increase resposivness during a resize. There's a bug in NSTableView that
1976 // causes row heights to get messed up if you try to change them during a live
1977 // resize. So if in a live resize, simply return the previously calculated
1978 // height. The row heights will get fixed up after the resize because we have
1979 // implemented viewDidEndLiveResize to force all of them to be recalculated.
1980 if ([outlineView inLiveResize] && [item lastDescriptionHeight] > 0)
1981 return [item lastDescriptionHeight];
1983 float width = [[outlineView tableColumnWithIdentifier: @"desc"] width];
1984 // Column width is NOT what is ultimately used
1985 width -= 47; // 26 pixels for disclosure triangle, 20 for icon, 1 for intercell spacing
1987 float height = [item heightOfDescriptionForWidth: width withHBHandle: fHandle];
1991 return HB_ROW_HEIGHT_TITLE_ONLY;
1994 - (id)outlineView:(NSOutlineView *)outlineView objectValueForTableColumn:(NSTableColumn *)tableColumn byItem:(id)item
1996 // nb: The "desc" column is currently an HBImageAndTextCell. However, we are longer
1997 // using the image portion of the cell so we could switch back to a regular NSTextFieldCell.
1999 if ([[tableColumn identifier] isEqualToString:@"desc"])
2000 return [item attributedDescriptionWithHBHandle: fHandle];
2001 else if ([[tableColumn identifier] isEqualToString:@"icon"])
2003 switch ([(HBJobGroup*)item status])
2005 case HBStatusComplete:
2006 return [NSImage imageNamed:@"EncodeComplete"];
2008 case HBStatusWorking:
2009 return [NSImage imageNamed: [NSString stringWithFormat: @"EncodeWorking%d", fAnimationIndex]];
2012 return [NSImage imageNamed:@"JobSmall"];
2020 - (void)outlineView:(NSOutlineView *)outlineView willDisplayCell:(id)cell forTableColumn:(NSTableColumn *)tableColumn item:(id)item
2022 if ([[tableColumn identifier] isEqualToString:@"desc"])
2024 #if HB_OUTLINE_METRIC_CONTROLS
2025 NSSize theSize = [cell imageSpacing];
2026 theSize.width = spacingWidth;
2027 [cell setImageSpacing: theSize];
2030 // nb: The "desc" column is currently an HBImageAndTextCell. However, we are longer
2031 // using the image portion of the cell so we could switch back to a regular NSTextFieldCell.
2033 // Set the image here since the value returned from outlineView:objectValueForTableColumn: didn't specify the image part
2034 [cell setImage:nil];
2037 else if ([[tableColumn identifier] isEqualToString:@"action"])
2039 [cell setEnabled: YES];
2040 BOOL highlighted = [outlineView isRowSelected:[outlineView rowForItem: item]] && [[outlineView window] isKeyWindow] && ([[outlineView window] firstResponder] == outlineView);
2041 if ([(HBJobGroup*)item status] == HBStatusComplete)
2043 [cell setAction: @selector(revealSelectedJobGroups:)];
2046 [cell setImage:[NSImage imageNamed:@"RevealHighlight"]];
2047 [cell setAlternateImage:[NSImage imageNamed:@"RevealHighlightPressed"]];
2050 [cell setImage:[NSImage imageNamed:@"Reveal"]];
2054 [cell setAction: @selector(removeSelectedJobGroups:)];
2057 [cell setImage:[NSImage imageNamed:@"DeleteHighlight"]];
2058 [cell setAlternateImage:[NSImage imageNamed:@"DeleteHighlightPressed"]];
2061 [cell setImage:[NSImage imageNamed:@"Delete"]];
2066 - (void)outlineView:(NSOutlineView *)outlineView willDisplayOutlineCell:(id)cell forTableColumn:(NSTableColumn *)tableColumn item:(id)item
2068 // By default, the discolsure image gets centered vertically in the cell. We want
2069 // always at the top.
2070 if ([outlineView isItemExpanded: item])
2071 [cell setImagePosition: NSImageAbove];
2073 [cell setImagePosition: NSImageOnly];
2077 #pragma mark NSOutlineView delegate (dragging related)
2079 //------------------------------------------------------------------------------------
2080 // NSTableView delegate
2081 //------------------------------------------------------------------------------------
2083 #if HB_QUEUE_DRAGGING
2084 - (BOOL)outlineView:(NSOutlineView *)outlineView writeItems:(NSArray *)items toPasteboard:(NSPasteboard *)pboard
2086 // Dragging is only allowed of the pending items.
2087 NSEnumerator * e = [items objectEnumerator];
2089 while ( (group = [e nextObject]) )
2091 if ([group status] != HBStatusPending)
2095 // Don't retain since this is just holding temporaral drag information, and it is
2096 //only used during a drag! We could put this in the pboard actually.
2097 fDraggedNodes = items;
2099 // Provide data for our custom type, and simple NSStrings.
2100 [pboard declareTypes:[NSArray arrayWithObjects: HBQueuePboardType, nil] owner:self];
2102 // the actual data doesn't matter since DragDropSimplePboardType drags aren't recognized by anyone but us!.
2103 [pboard setData:[NSData data] forType:HBQueuePboardType];
2109 #if HB_QUEUE_DRAGGING
2110 - (NSDragOperation)outlineView:(NSOutlineView *)outlineView validateDrop:(id <NSDraggingInfo>)info proposedItem:(id)item proposedChildIndex:(int)index
2112 // Don't allow dropping ONTO an item since they can't really contain any children.
2113 BOOL isOnDropTypeProposal = index == NSOutlineViewDropOnItemIndex;
2114 if (isOnDropTypeProposal)
2115 return NSDragOperationNone;
2117 // Don't allow dropping INTO an item since they can't really contain any children.
2120 index = [fOutlineView rowForItem: item] + 1;
2124 // Prevent dragging into the completed or current job.
2125 int firstPendingIndex = [fCompleted count];
2126 if (fCurrentJobGroup)
2127 firstPendingIndex++;
2128 index = MAX (index, firstPendingIndex);
2130 [outlineView setDropItem:item dropChildIndex:index];
2131 return NSDragOperationGeneric;
2135 #if HB_QUEUE_DRAGGING
2136 - (BOOL)outlineView:(NSOutlineView *)outlineView acceptDrop:(id <NSDraggingInfo>)info item:(id)item childIndex:(int)index
2138 NSMutableIndexSet *moveItems = [NSMutableIndexSet indexSet];
2141 NSEnumerator *enumerator = [fDraggedNodes objectEnumerator];
2142 while (obj = [enumerator nextObject])
2144 [moveItems addIndex:[fJobGroups indexOfObject:obj]];
2147 // Rearrange the data and view
2148 [self saveOutlineViewState];
2149 [self moveObjectsInArray:fJobGroups fromIndexes:moveItems toIndex: index];
2150 [fOutlineView reloadData];
2151 [self restoreOutlineViewState];