3 This file is part of the HandBrake source code.
4 Homepage: <http://handbrake.fr/>.
5 It may be used under the terms of the GNU General Public License. */
7 #import "HBQueueController.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"
16 //------------------------------------------------------------------------------------
18 //------------------------------------------------------------------------------------
20 int MakeJobID(int jobGroupID, int sequenceNum)
22 return jobGroupID<<16 | sequenceNum;
25 bool IsFirstPass(int jobID)
27 return LoWord(jobID) == 0;
30 //------------------------------------------------------------------------------------
31 // NSMutableAttributedString (HBAdditions)
32 //------------------------------------------------------------------------------------
34 @interface NSMutableAttributedString (HBAdditions)
35 - (void) appendString: (NSString*)aString withAttributes: (NSDictionary *)aDictionary;
38 @implementation NSMutableAttributedString (HBAdditions)
39 - (void) appendString: (NSString*)aString withAttributes: (NSDictionary *)aDictionary
41 NSAttributedString * s = [[[NSAttributedString alloc]
42 initWithString: aString
43 attributes: aDictionary] autorelease];
44 [self appendAttributedString: s];
48 //------------------------------------------------------------------------------------
50 //------------------------------------------------------------------------------------
52 @implementation HBQueueOutlineView
54 - (void)viewDidEndLiveResize
56 // Since we disabled calculating row heights during a live resize, force them to
58 [self noteHeightOfRowsWithIndexesChanged:
59 [NSIndexSet indexSetWithIndexesInRange: NSMakeRange(0, [self numberOfRows])]];
60 [super viewDidEndLiveResize];
64 - (NSImage *)dragImageForRowsWithIndexes:(NSIndexSet *)dragRows tableColumns:(NSArray *)tableColumns event:(NSEvent*)dragEvent offset:(NSPointPointer)dragImageOffset
66 // Set the fIsDragging flag so that other's know that a drag operation is being
70 // By default, NSTableView only drags an image of the first column. Change this to
71 // drag an image of the queue's icon and desc columns.
72 NSArray * cols = [NSArray arrayWithObjects: [self tableColumnWithIdentifier:@"icon"], [self tableColumnWithIdentifier:@"desc"], nil];
73 return [super dragImageForRowsWithIndexes:dragRows tableColumns:cols event:dragEvent offset:dragImageOffset];
78 - (void) mouseDown:(NSEvent *)theEvent
80 // After a drag operation, reset fIsDragging back to NO. This is really the only way
81 // for us to detect when a drag has finished. You can't do it in acceptDrop because
82 // that won't be called if the dragged item is released outside the view.
83 [super mouseDown:theEvent];
99 //------------------------------------------------------------------------------------
101 //------------------------------------------------------------------------------------
103 static NSMutableParagraphStyle * _descriptionParagraphStyle = nil;
104 static NSDictionary* _detailAttribute = nil;
105 static NSDictionary* _detailBoldAttribute = nil;
106 static NSDictionary* _titleAttribute = nil;
107 static NSDictionary* _shortHeightAttribute = nil;
109 @implementation HBJob
111 + (HBJob*) jobWithLibhbJob: (hb_job_t *) job
113 return [[[HBJob alloc] initWithLibhbJob:job] autorelease];
116 - (id) initWithLibhbJob: (hb_job_t *) job
118 if (self = [super init])
120 sequence_id = job->sequence_id;
122 chapter_start = job->chapter_start;
123 chapter_end = job->chapter_end;
124 chapter_markers = job->chapter_markers;
125 memcpy(crop, job->crop, sizeof(crop));
126 deinterlace = job->deinterlace;
128 height = job->height;
129 keep_ratio = job->keep_ratio;
130 grayscale = job->grayscale;
131 pixel_ratio = job->pixel_ratio;
132 pixel_aspect_width = job->pixel_aspect_width;
133 pixel_aspect_height = job->pixel_aspect_height;
134 vcodec = job->vcodec;
135 vquality = job->vquality;
136 vbitrate = job->vbitrate;
138 vrate_base = job->vrate_base;
140 h264_level = job->h264_level;
143 x264opts = [[NSString stringWithUTF8String:job->x264opts] retain];
144 /* So, with the advent of job->list_audio's I decided why not just use an NSString and concatanate
145 all of the info we need for all of the audio values to display right into an NSString here ? So I
146 did. I have no idea why we are reading libhb stuff just to display it in the queue gui. So here we
147 are with a huge string. But its easy to change and saves alot of messing about. Maybe we move a bunch
148 of other display stuff into strings for display for each job. It's not like they have to actually do
150 hb_audio_config_t * audio;
151 NSString * thisJobAudioCodecs = [NSString stringWithFormat:@""];
152 NSString * thisJobAudioInfo = [NSString stringWithFormat:@""];
154 for( i = 0; i < hb_list_count(job->list_audio); i++ )
156 audio = (hb_audio_config_t *) hb_list_audio_config_item( job->list_audio, i );
158 NSString *outputCodec;
159 if (audio->out.codec == HB_ACODEC_AC3)
160 outputCodec = @"AC3";
161 else if (audio->out.codec == HB_ACODEC_FAAC)
162 outputCodec = @"AAC";
163 else if (audio->out.codec == HB_ACODEC_LAME)
164 outputCodec = @"MP3";
165 else if (audio->out.codec == HB_ACODEC_VORBIS)
166 outputCodec = @"Vorbis";
168 outputCodec = @"Unknown Codec";
169 /* Add the codec to the audio codecs list ( We should check against dupes)*/
170 thisJobAudioCodecs = [thisJobAudioCodecs stringByAppendingString:[NSString stringWithFormat:@" %@,",outputCodec]];
173 /* Insert a line break so that we get each track on a separate line */
174 /* Wicked HACK alert!!, use 18 whitespaces to align offset in display for list > 2 to offset "Audio" in the queue display
175 Please Fix Me because this is embarrassing (but it works) */
176 thisJobAudioInfo = [thisJobAudioInfo stringByAppendingString:@"\n "];
178 /* Detailed Job audio track info*/
179 /* Track Number and Mixdown Info */
180 if (audio->out.mixdown == HB_ACODEC_AC3)// Remember for ac3 passthru the mixdown uses the source codec
181 thisJobAudioInfo = [thisJobAudioInfo stringByAppendingString:[NSString stringWithFormat:@"Track %d: Source: %@ Output: %@, Pass-Thru", i + 1, [NSString stringWithUTF8String:audio->lang.description], outputCodec]];
182 else if (audio->out.mixdown == HB_AMIXDOWN_MONO)
183 thisJobAudioInfo = [thisJobAudioInfo stringByAppendingString:[NSString stringWithFormat:@"Track %d: Source: %@ Output: %@, Mono", i + 1, [NSString stringWithUTF8String:audio->lang.description], outputCodec]];
184 else if (audio->out.mixdown == HB_AMIXDOWN_STEREO)
185 thisJobAudioInfo = [thisJobAudioInfo stringByAppendingString:[NSString stringWithFormat:@"Track %d: Source: %@ Output: %@, Stereo", i + 1, [NSString stringWithUTF8String:audio->lang.description], outputCodec]];
186 else if (audio->out.mixdown == HB_AMIXDOWN_DOLBY)
187 thisJobAudioInfo = [thisJobAudioInfo stringByAppendingString:[NSString stringWithFormat:@"Track %d: Source: %@ Output: %@, Dolby Surround", i + 1, [NSString stringWithUTF8String:audio->lang.description], outputCodec]];
188 else if (audio->out.mixdown == HB_AMIXDOWN_DOLBYPLII)
189 thisJobAudioInfo = [thisJobAudioInfo stringByAppendingString:[NSString stringWithFormat:@"Track %d: Source: %@ Output: %@, Dolby Pro Logic II", i + 1, [NSString stringWithUTF8String:audio->lang.description], outputCodec]];
190 else if (audio->out.mixdown == HB_AMIXDOWN_6CH)
191 thisJobAudioInfo = [thisJobAudioInfo stringByAppendingString:[NSString stringWithFormat:@"Track %d: Source: %@ Output: %@, 6 Channel Discreet", i + 1, [NSString stringWithUTF8String:audio->lang.description], outputCodec]];
193 thisJobAudioInfo = [thisJobAudioInfo stringByAppendingString:[NSString stringWithFormat:@"Track %d: Source: %@ Output: Unknown Codec Info", i + 1, [NSString stringWithUTF8String:audio->lang.description]]];
195 thisJobAudioInfo = [thisJobAudioInfo stringByAppendingString:[NSString stringWithFormat:@", %d kbps, %d Hz", audio->out.bitrate, audio->out.samplerate]];
198 audioinfo_summary = [[NSString stringWithFormat:@"%@",thisJobAudioInfo]retain];
199 audioinfo_codecs = [[NSString stringWithFormat:@"%@",thisJobAudioCodecs]retain];
201 subtitle = job->subtitle;
204 file = [[NSString stringWithUTF8String:job->file] retain];
205 if (job->title->name)
206 titleName = [[NSString stringWithUTF8String:job->title->name] retain];
207 titleIndex = job->title->index;
208 titleWidth = job->title->width;
209 titleHeight = job->title->height;
210 if (job->subtitle >= 0)
212 hb_subtitle_t * aSubtitle = (hb_subtitle_t *) hb_list_item(job->title->list_subtitle, job->subtitle);
214 subtitleLang = [[NSString stringWithUTF8String:aSubtitle->lang] retain];
217 // Calculate and store output dimensions and anamorphic dimensions
218 if (pixel_ratio == 1) // Original PAR Implementation, now called Strict Anamorphic
220 output_width = titleWidth - crop[2] - crop[3];
221 output_height = titleHeight - crop[0] - crop[1];
222 anamorphic_width = output_width * pixel_aspect_width / pixel_aspect_height;
223 anamorphic_height = output_height;
225 else if (pixel_ratio == 2) // Loose Anamorphic
227 // call hb_set_anamorphic_size to do a "dry run" to get the values to be
228 // used by libhb for loose anamorphic.
229 int par_width, par_height;
230 hb_set_anamorphic_size(job, &output_width, &output_height, &par_width, &par_height);
231 anamorphic_width = output_width * par_width / par_height;
232 anamorphic_height = output_height;
234 else // No Anamorphic
236 output_width = width;
237 output_height = height;
238 anamorphic_width = 0; // not needed for this case
239 anamorphic_height = 0; // not needed for this case
248 // jobGroup is a weak reference and does not need to be deleted
252 [subtitleLang release];
253 [audioinfo_summary release];
254 [audioinfo_codecs release];
258 - (HBJobGroup *) jobGroup
263 - (void) setJobGroup: (HBJobGroup *)aJobGroup
265 // This is a weak reference. We don't retain or release it.
266 jobGroup = aJobGroup;
269 //------------------------------------------------------------------------------------
270 // Generate string to display in UI.
271 //------------------------------------------------------------------------------------
273 - (NSMutableAttributedString *) attributedDescriptionWithIcon: (BOOL)withIcon
274 withTitle: (BOOL)withTitle
275 withPassName: (BOOL)withPassName
276 withFormatInfo: (BOOL)withFormatInfo
277 withDestination: (BOOL)withDestination
278 withPictureInfo: (BOOL)withPictureInfo
279 withVideoInfo: (BOOL)withVideoInfo
280 withx264Info: (BOOL)withx264Info
281 withAudioInfo: (BOOL)withAudioInfo
282 withSubtitleInfo: (BOOL)withSubtitleInfo
285 NSMutableAttributedString * finalString = [[[NSMutableAttributedString alloc] initWithString: @""] autorelease];
288 NSMutableParagraphStyle * ps = [HBJob descriptionParagraphStyle];
289 NSDictionary* detailAttr = [HBJob descriptionDetailAttribute];
290 NSDictionary* detailBoldAttr = [HBJob descriptionDetailBoldAttribute];
291 NSDictionary* titleAttr = [HBJob descriptionTitleAttribute];
292 NSDictionary* shortHeightAttr = [HBJob descriptionShortHeightAttribute];
294 // Title with summary
299 NSFileWrapper * wrapper = [[[NSFileWrapper alloc] initWithPath:[[NSBundle mainBundle] pathForImageResource: @"JobSmall"]] autorelease];
300 NSTextAttachment * imageAttachment = [[[NSTextAttachment alloc] initWithFileWrapper:wrapper] autorelease];
302 NSDictionary* imageAttributes = [NSDictionary dictionaryWithObjectsAndKeys:
303 [NSNumber numberWithFloat: -2.0], NSBaselineOffsetAttributeName,
304 imageAttachment, NSAttachmentAttributeName,
305 ps, NSParagraphStyleAttributeName,
308 NSAttributedString * imageAsString = [[[NSAttributedString alloc]
309 initWithString: [NSString stringWithFormat:@"%C%C", NSAttachmentCharacter, NSTabCharacter]
310 attributes: imageAttributes] autorelease];
312 [finalString appendAttributedString:imageAsString];
315 // Note: use title->name instead of title->dvd since name is just the chosen
316 // folder, instead of dvd which is the full path
317 [finalString appendString:titleName withAttributes:titleAttr];
319 NSString * summaryInfo;
321 NSString * chapterString = (chapter_start == chapter_end) ?
322 [NSString stringWithFormat:@"Chapter %d", chapter_start] :
323 [NSString stringWithFormat:@"Chapters %d through %d", chapter_start, chapter_end];
325 BOOL hasIndepthScan = (pass == -1);
326 int numVideoPasses = 0;
328 // To determine number of video passes, we need to skip past the subtitle scan.
331 // When job is the one currently being processed, then the next in its group
332 // is the the first job in the queue.
333 HBJob * nextjob = nil;
334 NSUInteger index = [jobGroup indexOfJob:self];
335 if (index != NSNotFound)
336 nextjob = [jobGroup jobAtIndex:index+1];
337 if (nextjob) // Overly cautious in case there is no next job!
338 numVideoPasses = MIN( 2, nextjob->pass + 1 );
341 numVideoPasses = MIN( 2, pass + 1 );
343 if (hasIndepthScan && numVideoPasses == 1)
344 summaryInfo = [NSString stringWithFormat: @" (Title %d, %@, Deep Scan, Single Video Pass)", titleIndex, chapterString];
345 else if (hasIndepthScan && numVideoPasses > 1)
346 summaryInfo = [NSString stringWithFormat: @" (Title %d, %@, Deep Scan, %d Video Passes)", titleIndex, chapterString, numVideoPasses];
347 else if (numVideoPasses == 1)
348 summaryInfo = [NSString stringWithFormat: @" (Title %d, %@, Single Video Pass)", titleIndex, chapterString];
350 summaryInfo = [NSString stringWithFormat: @" (Title %d, %@, %d Video Passes)", titleIndex, chapterString, numVideoPasses];
352 [finalString appendString:[NSString stringWithFormat:@"%@\n", summaryInfo] withAttributes:detailAttr];
354 // Insert a short-in-height line to put some white space after the title
355 [finalString appendString:@"\n" withAttributes:shortHeightAttr];
358 // End of title stuff
366 NSString * imageName;
369 case -1: imageName = @"JobPassSubtitleSmall"; break;
370 case 0: imageName = @"JobPassFirstSmall"; break;
371 case 1: imageName = @"JobPassFirstSmall"; break;
372 case 2: imageName = @"JobPassSecondSmall"; break;
373 default: imageName = @"JobPassUnknownSmall"; break;
376 NSFileWrapper * wrapper = [[[NSFileWrapper alloc] initWithPath:[[NSBundle mainBundle] pathForImageResource: imageName]] autorelease];
377 NSTextAttachment * imageAttachment = [[[NSTextAttachment alloc] initWithFileWrapper:wrapper] autorelease];
379 NSDictionary* imageAttributes = [NSDictionary dictionaryWithObjectsAndKeys:
380 [NSNumber numberWithFloat: -2.0], NSBaselineOffsetAttributeName,
381 imageAttachment, NSAttachmentAttributeName,
382 ps, NSParagraphStyleAttributeName,
385 NSAttributedString * imageAsString = [[[NSAttributedString alloc]
386 initWithString: [NSString stringWithFormat:@"%C%C", NSAttachmentCharacter, NSTabCharacter]
387 attributes: imageAttributes] autorelease];
389 [finalString appendAttributedString:imageAsString];
392 NSString * jobPassName;
394 jobPassName = NSLocalizedString (@"Deep Scan", nil);
397 int passNum = MAX( 1, pass );
399 jobPassName = NSLocalizedString (@"1st Pass", nil);
400 else if (passNum == 1)
401 jobPassName = NSLocalizedString (@"1st Pass", nil);
402 else if (passNum == 2)
403 jobPassName = NSLocalizedString (@"2nd Pass", nil);
405 jobPassName = [NSString stringWithFormat: NSLocalizedString(@"Pass %d", nil), passNum];
407 [finalString appendString:[NSString stringWithFormat:@"%@\n", jobPassName] withAttributes:detailBoldAttr];
410 // Video Codec needed by FormatInfo and withVideoInfo
411 NSString * jobVideoCodec = nil;
412 if (withFormatInfo || withVideoInfo)
415 // Video Codec settings (Encoder in the gui)
416 if (vcodec == HB_VCODEC_FFMPEG)
417 jobVideoCodec = @"FFmpeg"; // HB_VCODEC_FFMPEG
418 else if (vcodec == HB_VCODEC_XVID)
419 jobVideoCodec = @"XviD"; // HB_VCODEC_XVID
420 else if (vcodec == HB_VCODEC_X264)
422 // Deterimine for sure how we are now setting iPod uuid atom
423 if (h264_level) // We are encoding for iPod
424 jobVideoCodec = @"x264 (H.264 iPod)"; // HB_VCODEC_X264
426 jobVideoCodec = @"x264 (H.264 Main)"; // HB_VCODEC_X264
429 if (jobVideoCodec == nil)
430 jobVideoCodec = @"unknown";
434 NSString * jobFormatInfo;
435 // Muxer settings (File Format in the gui)
436 if (mux == 65536 || mux == 131072 || mux == 1048576)
437 jobFormatInfo = @"MP4"; // HB_MUX_MP4,HB_MUX_PSP,HB_MUX_IPOD
438 else if (mux == 262144)
439 jobFormatInfo = @"AVI"; // HB_MUX_AVI
440 else if (mux == 524288)
441 jobFormatInfo = @"OGM"; // HB_MUX_OGM
442 else if (mux == 2097152)
443 jobFormatInfo = @"MKV"; // HB_MUX_MKV
445 jobFormatInfo = @"unknown";
447 if (chapter_markers == 1)
448 jobFormatInfo = [NSString stringWithFormat:@"%@ Container, %@ Video + %@ Audio, Chapter Markers\n", jobFormatInfo, jobVideoCodec, audioinfo_codecs];
450 jobFormatInfo = [NSString stringWithFormat:@"%@ Container, %@ Video + %@ Audio\n", jobFormatInfo, jobVideoCodec, audioinfo_codecs];
452 [finalString appendString: @"Format: " withAttributes:detailBoldAttr];
453 [finalString appendString: jobFormatInfo withAttributes:detailAttr];
458 [finalString appendString: @"Destination: " withAttributes:detailBoldAttr];
459 [finalString appendString:[NSString stringWithFormat:@"%@\n", file] withAttributes:detailAttr];
465 NSString * jobPictureInfo;
466 if (pixel_ratio == 1) // Original PAR Implementation, now called Strict Anamorphic
467 jobPictureInfo = [NSString stringWithFormat:@"%d x %d (%d x %d Strict Anamorphic)", output_width, output_height, anamorphic_width, anamorphic_height];
468 else if (pixel_ratio == 2) // Loose Anamorphic
469 jobPictureInfo = [NSString stringWithFormat:@"%d x %d (%d x %d Loose Anamorphic)", output_width, output_height, anamorphic_width, anamorphic_height];
471 jobPictureInfo = [NSString stringWithFormat:@"%d x %d", output_width, output_height];
473 jobPictureInfo = [jobPictureInfo stringByAppendingString:@" Keep Aspect Ratio"];
476 jobPictureInfo = [jobPictureInfo stringByAppendingString:@", Grayscale"];
478 if (deinterlace == 1)
479 jobPictureInfo = [jobPictureInfo stringByAppendingString:@", Deinterlace"];
480 if (withIcon) // implies indent the info
481 [finalString appendString: @"\t" withAttributes:detailBoldAttr];
482 [finalString appendString: @"Picture: " withAttributes:detailBoldAttr];
483 [finalString appendString:[NSString stringWithFormat:@"%@\n", jobPictureInfo] withAttributes:detailAttr];
488 NSString * jobVideoQuality;
489 NSString * jobVideoDetail;
491 if (vquality <= 0 || vquality >= 1)
492 jobVideoQuality = [NSString stringWithFormat:@"%d kbps", vbitrate];
495 NSNumber * vidQuality;
496 vidQuality = [NSNumber numberWithInt:vquality * 100];
497 // this is screwed up kind of. Needs to be formatted properly.
499 jobVideoQuality = [NSString stringWithFormat:@"%@%% CRF", vidQuality];
501 jobVideoQuality = [NSString stringWithFormat:@"%@%% CQP", vidQuality];
504 if (vrate_base == 1126125)
507 jobVideoDetail = [NSString stringWithFormat:@"%@, %@, 23.976 fps", jobVideoCodec, jobVideoQuality];
509 else if (vrate_base == 900900)
512 jobVideoDetail = [NSString stringWithFormat:@"%@, %@, 29.97 fps", jobVideoCodec, jobVideoQuality];
517 jobVideoDetail = [NSString stringWithFormat:@"%@, %@, %d fps", jobVideoCodec, jobVideoQuality, vrate / vrate_base];
519 if (withIcon) // implies indent the info
520 [finalString appendString: @"\t" withAttributes:detailBoldAttr];
521 [finalString appendString: @"Video: " withAttributes:detailBoldAttr];
522 [finalString appendString:[NSString stringWithFormat:@"%@\n", jobVideoDetail] withAttributes:detailAttr];
527 if (vcodec == HB_VCODEC_X264 && x264opts)
529 if (withIcon) // implies indent the info
530 [finalString appendString: @"\t" withAttributes:detailBoldAttr];
531 [finalString appendString: @"x264 Options: " withAttributes:detailBoldAttr];
532 [finalString appendString:[NSString stringWithFormat:@"%@\n", x264opts] withAttributes:detailAttr];
538 if (withIcon) // implies indent the info
539 [finalString appendString: @"\t" withAttributes:detailBoldAttr];
540 [finalString appendString: @"Audio: " withAttributes:detailBoldAttr];
541 [finalString appendString:[NSString stringWithFormat:@"%@\n", audioinfo_summary] withAttributes:detailAttr];
544 if (withSubtitleInfo)
546 // subtitle scan == -1 in two cases:
547 // autoselect: when pass == -1
548 // none: when pass != -1
549 if ((subtitle == -1) && (pass == -1))
551 if (withIcon) // implies indent the info
552 [finalString appendString: @"\t" withAttributes:detailBoldAttr];
553 [finalString appendString: @"Subtitles: " withAttributes:detailBoldAttr];
554 [finalString appendString: @"Autoselect " withAttributes:detailAttr];
556 else if (subtitle >= 0)
560 if (withIcon) // implies indent the info
561 [finalString appendString: @"\t" withAttributes:detailBoldAttr];
562 [finalString appendString: @"Subtitles: " withAttributes:detailBoldAttr];
563 [finalString appendString: subtitleLang withAttributes:detailAttr];
569 if ([[finalString string] hasSuffix: @"\n"])
570 [finalString deleteCharactersInRange: NSMakeRange([[finalString string] length]-1, 1)];
575 + (NSMutableParagraphStyle *) descriptionParagraphStyle
577 if (!_descriptionParagraphStyle)
579 _descriptionParagraphStyle = [[[NSParagraphStyle defaultParagraphStyle] mutableCopy] retain];
580 [_descriptionParagraphStyle setHeadIndent: 40.0];
581 [_descriptionParagraphStyle setParagraphSpacing: 1.0];
582 [_descriptionParagraphStyle setTabStops:[NSArray array]]; // clear all tabs
583 [_descriptionParagraphStyle addTabStop: [[[NSTextTab alloc] initWithType: NSLeftTabStopType location: 20.0] autorelease]];
585 return _descriptionParagraphStyle;
588 + (NSDictionary *) descriptionDetailAttribute
590 if (!_detailAttribute)
591 _detailAttribute = [[NSDictionary dictionaryWithObjectsAndKeys:
592 [NSFont systemFontOfSize:10.0], NSFontAttributeName,
593 _descriptionParagraphStyle, NSParagraphStyleAttributeName,
595 return _detailAttribute;
598 + (NSDictionary *) descriptionDetailBoldAttribute
600 if (!_detailBoldAttribute)
601 _detailBoldAttribute = [[NSDictionary dictionaryWithObjectsAndKeys:
602 [NSFont boldSystemFontOfSize:10.0], NSFontAttributeName,
603 _descriptionParagraphStyle, NSParagraphStyleAttributeName,
605 return _detailBoldAttribute;
608 + (NSDictionary *) descriptionTitleAttribute
610 if (!_titleAttribute)
611 _titleAttribute = [[NSDictionary dictionaryWithObjectsAndKeys:
612 [NSFont systemFontOfSize:[NSFont systemFontSize]], NSFontAttributeName,
613 _descriptionParagraphStyle, NSParagraphStyleAttributeName,
615 return _titleAttribute;
618 + (NSDictionary *) descriptionShortHeightAttribute
620 if (!_shortHeightAttribute)
621 _shortHeightAttribute = [[NSDictionary dictionaryWithObjectsAndKeys:
622 [NSFont systemFontOfSize:2.0], NSFontAttributeName,
624 return _shortHeightAttribute;
632 //------------------------------------------------------------------------------------
634 //------------------------------------------------------------------------------------
636 // Notification sent from HBJobGroup setStatus whenever the status changes.
637 NSString *HBJobGroupStatusNotification = @"HBJobGroupStatusNotification";
639 @implementation HBJobGroup
641 + (HBJobGroup *) jobGroup;
643 return [[[HBJobGroup alloc] init] autorelease];
648 if (self = [super init])
650 fJobs = [[NSMutableArray arrayWithCapacity:0] retain];
651 fDescription = [[NSMutableAttributedString alloc] initWithString: @""];
652 [self setNeedsDescription: NO];
653 fStatus = HBStatusNone;
660 [fPresetName release];
665 - (unsigned int) count
667 return [fJobs count];
670 - (void) addJob: (HBJob *)aJob
672 [aJob setJobGroup:self];
673 [fJobs addObject: aJob];
674 [self setNeedsDescription: YES];
675 fLastDescriptionHeight = 0;
676 fLastDescriptionWidth = 0;
679 - (HBJob *) jobAtIndex: (unsigned)index
681 return [fJobs objectAtIndex: index];
684 - (unsigned) indexOfJob: (HBJob *)aJob;
686 return [fJobs indexOfObject: aJob];
689 - (NSMutableArray *) fJobs
694 - (void) setNeedsDescription: (BOOL)flag
696 fNeedsDescription = flag;
699 - (void) updateDescription
701 fNeedsDescription = NO;
703 [fDescription deleteCharactersInRange: NSMakeRange(0, [fDescription length])];
705 if ([self count] == 0)
707 NSAssert(NO, @" jobgroup with no jobs");
711 HBJob * job = [self jobAtIndex:0];
714 [fDescription appendAttributedString: [job attributedDescriptionWithIcon: NO
723 withSubtitleInfo: NO]];
725 // append the preset name
726 if ([fPresetName length])
728 [fDescription appendString:@"Preset: " withAttributes:[HBJob descriptionDetailBoldAttribute]];
729 [fDescription appendString:fPresetName withAttributes:[HBJob descriptionDetailAttribute]];
730 [fDescription appendString:@"\n" withAttributes:[HBJob descriptionDetailAttribute]];
733 // append the format and destinaton
734 [fDescription appendAttributedString: [job attributedDescriptionWithIcon: NO
743 withSubtitleInfo: NO]];
746 NSAttributedString * carriageReturn = [[NSAttributedString alloc] initWithString:@"\n"];
750 int pass = job->pass;
751 [fDescription appendAttributedString:carriageReturn];
752 [fDescription appendAttributedString:
753 [job attributedDescriptionWithIcon: YES
758 withPictureInfo: pass != -1
759 withVideoInfo: pass != -1
760 withx264Info: pass != -1
761 withAudioInfo: pass == 0 || pass == 2
762 withSubtitleInfo: YES]];
767 - (NSMutableAttributedString *) attributedDescription
769 if (fNeedsDescription)
770 [self updateDescription];
774 - (CGFloat) heightOfDescriptionForWidth:(CGFloat)width
776 // Try to return the cached value if no changes have happened since the last time
777 if ((width == fLastDescriptionWidth) && (fLastDescriptionHeight != 0) && !fNeedsDescription)
778 return fLastDescriptionHeight;
780 if (fNeedsDescription)
781 [self updateDescription];
783 // Calculate the height
784 NSRect bounds = [fDescription boundingRectWithSize:NSMakeSize(width, 10000) options:NSStringDrawingUsesLineFragmentOrigin];
785 fLastDescriptionHeight = bounds.size.height + 6.0; // add some border to bottom
786 fLastDescriptionWidth = width;
787 return fLastDescriptionHeight;
789 /* supposedly another way to do this, in case boundingRectWithSize isn't working
790 NSTextView* tmpView = [[NSTextView alloc] initWithFrame:NSMakeRect(0, 0, width, 1)];
791 [[tmpView textStorage] setAttributedString:aString];
792 [tmpView setHorizontallyResizable:NO];
793 [tmpView setVerticallyResizable:YES];
794 // [[tmpView textContainer] setHeightTracksTextView: YES];
795 // [[tmpView textContainer] setContainerSize: NSMakeSize(width, 10000)];
797 float height = [tmpView frame].size.height;
803 - (CGFloat) lastDescriptionHeight
805 return fLastDescriptionHeight;
808 - (void) setStatus: (HBQueueJobGroupStatus)status
810 // Create a dictionary with the old status
811 NSDictionary * userInfo = [NSDictionary dictionaryWithObject:[NSNumber numberWithInt:self->fStatus] forKey:@"HBOldJobGroupStatus"];
813 self->fStatus = status;
815 // Send notification with old status
816 [[NSNotificationCenter defaultCenter] postNotificationName:HBJobGroupStatusNotification object:self userInfo:userInfo];
819 - (HBQueueJobGroupStatus) status
821 return self->fStatus;
824 - (void) setPresetName: (NSString *)name
827 [fPresetName release];
831 - (NSString *) presetName
838 HBJob * firstJob = [self jobAtIndex:0];
839 return firstJob ? firstJob->titleName : nil;
842 - (NSString *) destinationPath
844 HBJob * firstJob = [self jobAtIndex:0];
845 return firstJob ? firstJob->file : nil;
853 // Toolbar identifiers
854 static NSString* HBQueueToolbar = @"HBQueueToolbar1";
855 static NSString* HBQueueStartCancelToolbarIdentifier = @"HBQueueStartCancelToolbarIdentifier";
856 static NSString* HBQueuePauseResumeToolbarIdentifier = @"HBQueuePauseResumeToolbarIdentifier";
860 @implementation HBQueueController
862 //------------------------------------------------------------------------------------
864 //------------------------------------------------------------------------------------
867 if (self = [super initWithWindowNibName:@"Queue"])
869 // NSWindowController likes to lazily load its window nib. Since this
870 // controller tries to touch the outlets before accessing the window, we
871 // need to force it to load immadiately by invoking its accessor.
873 // If/when we switch to using bindings, this can probably go away.
877 [[NSUserDefaults standardUserDefaults] registerDefaults:[NSDictionary dictionaryWithObjectsAndKeys:
878 @"NO", @"QueueWindowIsOpen",
879 @"NO", @"QueueShowsDetail",
880 @"YES", @"QueueShowsJobsAsGroups",
883 fJobGroups = [[NSMutableArray arrayWithCapacity:0] retain];
885 // Register for HBJobGroup status changes
886 [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(jobGroupStatusNotification:) name:HBJobGroupStatusNotification object:nil];
891 //------------------------------------------------------------------------------------
893 //------------------------------------------------------------------------------------
896 // clear the delegate so that windowWillClose is not attempted
897 if( [[self window] delegate] == self )
898 [[self window] setDelegate:nil];
900 [fJobGroups release];
901 [fCurrentJobGroup release];
902 [fSavedExpandedItems release];
903 [fSavedSelectedItems release];
905 [[NSNotificationCenter defaultCenter] removeObserver:self];
910 //------------------------------------------------------------------------------------
912 //------------------------------------------------------------------------------------
913 - (void)setHandle: (hb_handle_t *)handle
918 //------------------------------------------------------------------------------------
919 // Receive HBController
920 //------------------------------------------------------------------------------------
921 - (void)setHBController: (HBController *)controller
923 fHBController = controller;
927 #pragma mark - Getting the currently processing job group
929 //------------------------------------------------------------------------------------
930 // Returns the HBJobGroup that is currently being encoded; nil if no encoding is
932 //------------------------------------------------------------------------------------
933 - (HBJobGroup *) currentJobGroup;
935 return fCurrentJobGroup;
938 //------------------------------------------------------------------------------------
939 // Returns the HBJob (pass) that is currently being encoded; nil if no encoding is
941 //------------------------------------------------------------------------------------
942 - (HBJob *) currentJob
949 //------------------------------------------------------------------------------------
950 // Displays and brings the queue window to the front
951 //------------------------------------------------------------------------------------
952 - (IBAction) showQueueWindow: (id)sender
954 [self showWindow:sender];
955 [[NSUserDefaults standardUserDefaults] setBool:YES forKey:@"QueueWindowIsOpen"];
958 //------------------------------------------------------------------------------------
959 // Show or hide the current job pane (fCurrentJobPane).
960 //------------------------------------------------------------------------------------
961 - (void) showCurrentJobPane: (BOOL)showPane
963 if (showPane == fCurrentJobPaneShown)
966 // Things to keep in mind:
967 // - When the current job pane is shown, it occupies the upper portion of the
968 // window with the queue occupying the bottom portion of the window.
969 // - When the current job pane is hidden, it slides up and out of view.
970 // NSView setHidden is NOT used. The queue pane is resized to occupy the full
973 NSRect windowFrame = [[fCurrentJobPane superview] frame];
974 NSRect queueFrame, jobFrame;
976 NSDivideRect(windowFrame, &jobFrame, &queueFrame, NSHeight([fCurrentJobPane frame]), NSMaxYEdge);
979 queueFrame = windowFrame;
980 jobFrame = [fCurrentJobPane frame];
981 jobFrame.origin.y = NSHeight(windowFrame);
984 // Move fCurrentJobPane
985 NSDictionary * dict1 = [NSDictionary dictionaryWithObjectsAndKeys:
986 fCurrentJobPane, NSViewAnimationTargetKey,
987 [NSValue valueWithRect:jobFrame], NSViewAnimationEndFrameKey,
991 NSDictionary * dict2 = [NSDictionary dictionaryWithObjectsAndKeys:
992 fQueuePane, NSViewAnimationTargetKey,
993 [NSValue valueWithRect:queueFrame], NSViewAnimationEndFrameKey,
996 NSViewAnimation * anAnimation = [[[NSViewAnimation alloc] initWithViewAnimations:nil] autorelease];
997 [anAnimation setViewAnimations:[NSArray arrayWithObjects:dict1, dict2, nil]];
998 [anAnimation setDuration:0.25];
999 [anAnimation setAnimationBlockingMode:NSAnimationBlocking]; // prevent user from resizing the window during an animation
1000 [anAnimation startAnimation];
1002 fCurrentJobPaneShown = showPane;
1005 //------------------------------------------------------------------------------------
1006 // Sets fCurrentJobGroup to a new job group.
1007 //------------------------------------------------------------------------------------
1008 - (void) setCurrentJobGroup: (HBJobGroup *)aJobGroup
1011 [aJobGroup setStatus: HBStatusWorking];
1014 [fCurrentJobGroup release];
1015 fCurrentJobGroup = aJobGroup;
1018 #pragma mark - Finding job groups
1020 //------------------------------------------------------------------------------------
1021 // Returns the first pending job with a specified destination path or nil if no such
1023 //------------------------------------------------------------------------------------
1024 - (HBJobGroup *) pendingJobGroupWithDestinationPath: (NSString *)path
1026 for( HBJobGroup * aJobGroup in fJobGroups )
1028 if ([[aJobGroup destinationPath] isEqualToString: path])
1034 //------------------------------------------------------------------------------------
1035 // Locates and returns a HBJob whose sequence_id matches a specified value.
1036 //------------------------------------------------------------------------------------
1037 - (HBJob *) findJobWithID: (int)aJobID
1039 for( HBJobGroup * aJobGroup in fJobGroups )
1041 for( HBJob * job in [aJobGroup fJobs] )
1043 if (job->sequence_id == aJobID)
1050 //------------------------------------------------------------------------------------
1051 // Locates and returns a libhb job whose sequence_id matches a specified value.
1052 //------------------------------------------------------------------------------------
1053 - (hb_job_t *) findLibhbJobWithID: (int)aJobID
1057 while( ( job = hb_job( fHandle, index++ ) ) )
1059 if (job->sequence_id == aJobID)
1066 #pragma mark Queue Counts
1068 //------------------------------------------------------------------------------------
1069 // Sets a flag indicating that the values for fPendingCount, fCompletedCount,
1070 // fCanceledCount, and fWorkingCount need to be recalculated.
1071 //------------------------------------------------------------------------------------
1072 - (void) setJobGroupCountsNeedUpdating: (BOOL)flag
1074 fJobGroupCountsNeedUpdating = flag;
1077 //------------------------------------------------------------------------------------
1078 // Recalculates and stores new values in fPendingCount, fCompletedCount,
1079 // fCanceledCount, and fWorkingCount.
1080 //------------------------------------------------------------------------------------
1081 - (void) recalculateJobGroupCounts
1084 fCompletedCount = 0;
1088 for( HBJobGroup * aJobGroup in fJobGroups )
1090 switch ([aJobGroup status])
1093 // We don't track these.
1095 case HBStatusPending:
1098 case HBStatusCompleted:
1101 case HBStatusCanceled:
1104 case HBStatusWorking:
1109 fJobGroupCountsNeedUpdating = NO;
1112 //------------------------------------------------------------------------------------
1113 // Returns the number of job groups whose status is HBStatusPending.
1114 //------------------------------------------------------------------------------------
1115 - (unsigned int) pendingCount
1117 if (fJobGroupCountsNeedUpdating)
1118 [self recalculateJobGroupCounts];
1119 return fPendingCount;
1122 //------------------------------------------------------------------------------------
1123 // Returns the number of job groups whose status is HBStatusCompleted.
1124 //------------------------------------------------------------------------------------
1125 - (unsigned int) completedCount
1127 if (fJobGroupCountsNeedUpdating)
1128 [self recalculateJobGroupCounts];
1129 return fCompletedCount;
1132 //------------------------------------------------------------------------------------
1133 // Returns the number of job groups whose status is HBStatusCanceled.
1134 //------------------------------------------------------------------------------------
1135 - (unsigned int) canceledCount
1137 if (fJobGroupCountsNeedUpdating)
1138 [self recalculateJobGroupCounts];
1139 return fCanceledCount;
1142 //------------------------------------------------------------------------------------
1143 // Returns the number of job groups whose status is HBStatusWorking.
1144 //------------------------------------------------------------------------------------
1145 - (unsigned int) workingCount
1147 if (fJobGroupCountsNeedUpdating)
1148 [self recalculateJobGroupCounts];
1149 return fWorkingCount;
1153 #pragma mark UI Updating
1155 //------------------------------------------------------------------------------------
1156 // Saves the state of the items that are currently expanded and selected. Calling
1157 // restoreOutlineViewState will restore the state of all items to match what was saved
1158 // by saveOutlineViewState. Nested calls to saveOutlineViewState are not supported.
1159 //------------------------------------------------------------------------------------
1160 - (void) saveOutlineViewState
1162 if (!fSavedExpandedItems)
1163 fSavedExpandedItems = [[NSMutableIndexSet alloc] init];
1165 [fSavedExpandedItems removeAllIndexes];
1167 // This code stores the sequence_id of the first job of each job group into an
1168 // index set. This is sufficient to identify each group uniquely.
1170 HBJobGroup * aJobGroup;
1171 for( aJobGroup in fJobGroups )
1173 if ([fOutlineView isItemExpanded: aJobGroup])
1174 [fSavedExpandedItems addIndex: [aJobGroup jobAtIndex:0]->sequence_id];
1177 // Save the selection also.
1179 if (!fSavedSelectedItems)
1180 fSavedSelectedItems = [[NSMutableIndexSet alloc] init];
1182 [fSavedSelectedItems removeAllIndexes];
1184 NSIndexSet * selectedRows = [fOutlineView selectedRowIndexes];
1185 NSInteger row = [selectedRows firstIndex];
1186 while (row != NSNotFound)
1188 aJobGroup = [fOutlineView itemAtRow: row];
1189 [fSavedSelectedItems addIndex: [aJobGroup jobAtIndex:0]->sequence_id];
1190 row = [selectedRows indexGreaterThanIndex: row];
1195 //------------------------------------------------------------------------------------
1196 // Restores the expanded state of items in the outline view to match those saved by a
1197 // previous call to saveOutlineViewState.
1198 //------------------------------------------------------------------------------------
1199 - (void) restoreOutlineViewState
1201 if (fSavedExpandedItems)
1203 for( HBJobGroup * aJobGroup in fJobGroups )
1205 HBJob * job = [aJobGroup jobAtIndex:0];
1206 if (job && [fSavedExpandedItems containsIndex: job->sequence_id])
1207 [fOutlineView expandItem: aJobGroup];
1211 if (fSavedSelectedItems)
1213 NSMutableIndexSet * rowsToSelect = [[[NSMutableIndexSet alloc] init] autorelease];
1215 for( HBJobGroup * aJobGroup in fJobGroups )
1217 HBJob * job = [aJobGroup jobAtIndex:0];
1218 if (job && [fSavedSelectedItems containsIndex: job->sequence_id])
1219 [rowsToSelect addIndex: i];
1222 if ([rowsToSelect count] == 0)
1223 [fOutlineView deselectAll: nil];
1225 [fOutlineView selectRowIndexes:rowsToSelect byExtendingSelection:NO];
1229 //------------------------------------------------------------------------------------
1230 // Marks the icon region of a job group in the queue view as needing display.
1231 //------------------------------------------------------------------------------------
1232 - (void) updateJobGroupIconInQueue:(HBJobGroup*)aJobGroup
1234 NSInteger row = [fOutlineView rowForItem: aJobGroup];
1235 NSInteger col = [fOutlineView columnWithIdentifier: @"icon"];
1236 if (row != -1 && col != -1)
1238 NSRect frame = [fOutlineView frameOfCellAtColumn:col row:row];
1239 [fOutlineView setNeedsDisplayInRect: frame];
1243 //------------------------------------------------------------------------------------
1244 // Marks the entire region of a job group in the queue view as needing display.
1245 //------------------------------------------------------------------------------------
1246 - (void) updateJobGroupInQueue:(HBJobGroup*)aJobGroup
1248 NSInteger row = [fOutlineView rowForItem: aJobGroup];
1251 NSRect frame = [fOutlineView rectOfRow:row];
1252 [fOutlineView setNeedsDisplayInRect: frame];
1256 //------------------------------------------------------------------------------------
1257 // If a job is currently processing, its job icon in the queue outline view is
1258 // animated to its next state.
1259 //------------------------------------------------------------------------------------
1260 - (void) animateCurrentJobGroupInQueue:(NSTimer*)theTimer
1262 if (fCurrentJobGroup)
1265 fAnimationIndex %= 6; // there are 6 animation images; see outlineView:objectValueForTableColumn:byItem: below.
1266 [self updateJobGroupIconInQueue: fCurrentJobGroup];
1270 //------------------------------------------------------------------------------------
1271 // Starts animating the job icon of the currently processing job in the queue outline
1273 //------------------------------------------------------------------------------------
1274 - (void) startAnimatingCurrentJobGroupInQueue
1276 if (!fAnimationTimer)
1277 fAnimationTimer = [[NSTimer scheduledTimerWithTimeInterval:1.0/12.0 // 1/12 because there are 6 images in the animation cycle
1279 selector:@selector(animateCurrentJobGroupInQueue:)
1281 repeats:YES] retain];
1284 //------------------------------------------------------------------------------------
1285 // Stops animating the job icon of the currently processing job in the queue outline
1287 //------------------------------------------------------------------------------------
1288 - (void) stopAnimatingCurrentJobGroupInQueue
1290 if (fAnimationTimer && [fAnimationTimer isValid])
1292 [fAnimationTimer invalidate];
1293 [fAnimationTimer release];
1294 fAnimationTimer = nil;
1298 //------------------------------------------------------------------------------------
1299 // Generate string to display in UI.
1300 //------------------------------------------------------------------------------------
1301 - (NSString *) progressStatusStringForJob: (HBJob *)job state: (hb_state_t *)s
1303 if (s->state == HB_STATE_WORKING)
1306 if (job->pass == -1)
1307 msg = NSLocalizedString( @"Deep Scan", nil );
1308 else if (job->pass == 1)
1309 msg = NSLocalizedString( @"Analyzing video", nil );
1310 else if ((job->pass == 0) || (job->pass == 2))
1311 msg = NSLocalizedString( @"Encoding movie", nil );
1313 return @""; // unknown condition!
1315 if( s->param.working.seconds > -1 )
1317 return [NSString stringWithFormat:
1318 NSLocalizedString( @"%@ (%.2f fps, avg %.2f fps)", nil ),
1319 msg, s->param.working.rate_cur, s->param.working.rate_avg];
1326 else if (s->state == HB_STATE_MUXING)
1327 return NSLocalizedString( @"Muxing", nil );
1329 else if (s->state == HB_STATE_PAUSED)
1330 return NSLocalizedString( @"Paused", nil );
1332 else if (s->state == HB_STATE_WORKDONE)
1333 return NSLocalizedString( @"Done", nil );
1338 //------------------------------------------------------------------------------------
1339 // Generate string to display in UI.
1340 //------------------------------------------------------------------------------------
1341 - (NSString *) progressTimeRemainingStringForJob: (HBJob *)job state: (hb_state_t *)s
1343 if (s->state == HB_STATE_WORKING)
1345 #define p s->param.working
1349 // Minutes always needed
1352 minutes = [NSString stringWithFormat:NSLocalizedString( @"%d minutes ", nil ), p.minutes];
1353 else if (p.minutes == 1)
1354 minutes = NSLocalizedString( @"1 minute ", nil );
1362 hours = [NSString stringWithFormat:NSLocalizedString( @"%d hours ", nil ), p.hours];
1364 hours = NSLocalizedString( @"1 hour ", nil );
1366 return [NSString stringWithFormat:NSLocalizedString( @"%@%@remaining", nil ), hours, minutes];
1373 seconds = [NSString stringWithFormat:NSLocalizedString( @"%d seconds ", nil ), p.seconds];
1375 seconds = NSLocalizedString( @"1 second ", nil );
1377 return [NSString stringWithFormat:NSLocalizedString( @"%@%@remaining", nil ), minutes, seconds];
1380 /* here is code that does it more like the Finder
1381 if( p.seconds > -1 )
1383 float estHours = (p.hours + (p.minutes / 60.0));
1384 float estMinutes = (p.minutes + (p.seconds / 60.0));
1387 return [NSString stringWithFormat:NSLocalizedString( @"Time remaining: About %d hours", nil ), lrintf(estHours)];
1388 else if (estHours > 0.983) // 59 minutes
1389 return NSLocalizedString( @"Time remaining: About 1 hour", nil );
1390 else if (estMinutes > 1.5)
1391 return [NSString stringWithFormat:NSLocalizedString( @"Time remaining: About %d minutes", nil ), lrintf(estMinutes)];
1392 else if (estMinutes > 0.983) // 59 seconds
1393 return NSLocalizedString( @"Time remaining: About 1 minute", nil );
1394 else if (p.seconds <= 5)
1395 return NSLocalizedString( @"Time remaining: Less than 5 seconds", nil );
1396 else if (p.seconds <= 10)
1397 return NSLocalizedString( @"Time remaining: Less than 10 seconds", nil );
1399 return NSLocalizedString( @"Time remaining: Less than 1 minute", nil );
1402 return NSLocalizedString( @"Time remaining: Calculating...", nil );
1410 //------------------------------------------------------------------------------------
1411 // Refresh progress bar (fProgressTextField) from current state.
1412 //------------------------------------------------------------------------------------
1413 - (void) updateProgressTextForJob: (HBJob *)job state: (hb_state_t *)s
1415 NSString * statusMsg = [self progressStatusStringForJob:job state:s];
1416 NSString * timeMsg = [self progressTimeRemainingStringForJob:job state:s];
1417 if ([timeMsg length] > 0)
1418 statusMsg = [NSString stringWithFormat:@"%@ - %@", statusMsg, timeMsg];
1419 [fProgressTextField setStringValue:statusMsg];
1422 //------------------------------------------------------------------------------------
1423 // Refresh progress bar (fProgressBar) from current state.
1424 //------------------------------------------------------------------------------------
1425 - (void) updateProgressBarWithState: (hb_state_t *)s
1427 if (s->state == HB_STATE_WORKING)
1429 #define p s->param.working
1430 [fProgressBar setIndeterminate:NO];
1431 float progress_total = 100.0 * ( p.progress + p.job_cur - 1 ) / p.job_count;
1432 [fProgressBar setDoubleValue:progress_total];
1436 else if (s->state == HB_STATE_MUXING)
1438 #define p s->param.muxing
1439 [fProgressBar setIndeterminate:YES];
1440 [fProgressBar startAnimation:nil];
1444 else if (s->state == HB_STATE_WORKDONE)
1446 [fProgressBar setIndeterminate:NO];
1447 [fProgressBar stopAnimation:nil];
1448 [fProgressBar setDoubleValue:0.0];
1452 [fProgressBar stopAnimation:nil]; // just in case in was animating
1455 //------------------------------------------------------------------------------------
1456 // Refresh queue count text field (fQueueCountField).
1457 //------------------------------------------------------------------------------------
1458 - (void)updateQueueCountField
1461 int jobCount = [fJobGroups count];
1462 int pendingCount = [self pendingCount];
1464 msg = NSLocalizedString(@"No encodes", nil);
1465 else if ((jobCount == 1) && (pendingCount == 0))
1466 msg = NSLocalizedString(@"1 encode", nil);
1467 else if (jobCount == pendingCount) // ie, all jobs listed are pending
1470 msg = NSLocalizedString(@"1 pending encode", nil);
1472 msg = [NSString stringWithFormat:NSLocalizedString(@"%d pending encodes", nil), pendingCount];
1474 else // some completed, some pending
1475 msg = [NSString stringWithFormat:NSLocalizedString(@"%d encodes (%d pending)", nil), jobCount, pendingCount];
1477 [fQueueCountField setStringValue:msg];
1480 //------------------------------------------------------------------------------------
1481 // Refresh the UI in the current job pane. Should be called whenever the current job
1482 // being processed has changed.
1483 //------------------------------------------------------------------------------------
1484 - (void)updateCurrentJobDescription
1488 switch (fCurrentJob->pass)
1490 case -1: // Subtitle scan
1491 [fJobDescTextField setAttributedStringValue:
1492 [fCurrentJob attributedDescriptionWithIcon: NO
1501 withSubtitleInfo: YES]];
1504 case 1: // video 1st pass
1505 [fJobDescTextField setAttributedStringValue:
1506 [fCurrentJob attributedDescriptionWithIcon: NO
1511 withPictureInfo: YES
1515 withSubtitleInfo: NO]];
1518 case 0: // single pass
1519 case 2: // video 2nd pass + audio
1520 [fJobDescTextField setAttributedStringValue:
1521 [fCurrentJob attributedDescriptionWithIcon: NO
1526 withPictureInfo: YES
1530 withSubtitleInfo: YES]];
1534 [fJobDescTextField setAttributedStringValue:
1535 [fCurrentJob attributedDescriptionWithIcon: NO
1540 withPictureInfo: YES
1544 withSubtitleInfo: YES]];
1549 [fJobDescTextField setStringValue: @"No encodes pending"];
1555 //------------------------------------------------------------------------------------
1556 // Refresh the UI in the current job pane. Should be called whenever the current job
1557 // being processed has changed or when progress has changed.
1558 //------------------------------------------------------------------------------------
1559 - (void)updateCurrentJobProgress
1562 hb_get_state2( fHandle, &s );
1563 [self updateProgressTextForJob: fCurrentJob state: &s];
1564 [self updateProgressBarWithState:&s];
1567 //------------------------------------------------------------------------------------
1568 // Notifies HBQueuecontroller that the contents of fJobGroups is about to be modified.
1569 // HBQueuecontroller remembers the state of the UI (selection and expanded items).
1570 //------------------------------------------------------------------------------------
1571 - (void) beginEditingJobGroupsArray
1573 [self saveOutlineViewState];
1576 //------------------------------------------------------------------------------------
1577 // Notifies HBQueuecontroller that modifications to fJobGroups as indicated by a prior
1578 // call to beginEditingJobGroupsArray have been completed. HBQueuecontroller reloads
1579 // the queue view and restores the state of the UI (selection and expanded items).
1580 //------------------------------------------------------------------------------------
1581 - (void) endEditingJobGroupsArray
1583 [self setJobGroupCountsNeedUpdating:YES];
1584 [fOutlineView noteNumberOfRowsChanged];
1585 [fOutlineView reloadData];
1586 [self restoreOutlineViewState];
1587 [self updateQueueCountField];
1591 #pragma mark Actions
1593 //------------------------------------------------------------------------------------
1594 // Deletes the selected jobs from HB and the queue UI
1595 //------------------------------------------------------------------------------------
1596 - (IBAction)removeSelectedJobGroups: (id)sender
1598 if (!fHandle) return;
1600 NSIndexSet * selectedRows = [fOutlineView selectedRowIndexes];
1601 NSInteger row = [selectedRows firstIndex];
1602 if (row != NSNotFound)
1604 [self beginEditingJobGroupsArray];
1605 while (row != NSNotFound)
1607 HBJobGroup * jobGroup = [fOutlineView itemAtRow: row];
1608 switch ([jobGroup status])
1610 case HBStatusCompleted:
1611 case HBStatusCanceled:
1612 [fJobGroups removeObject: jobGroup];
1614 case HBStatusWorking:
1615 [self cancelCurrentJob: sender];
1617 case HBStatusPending:
1618 // Remove from libhb
1619 for( HBJob * job in [jobGroup fJobs] )
1621 hb_job_t * libhbJob = [self findLibhbJobWithID:job->sequence_id];
1623 hb_rem( fHandle, libhbJob );
1625 // Remove from our list
1626 [fJobGroups removeObject: jobGroup];
1632 row = [selectedRows indexGreaterThanIndex: row];
1634 [self endEditingJobGroupsArray];
1638 //------------------------------------------------------------------------------------
1639 // Reveals the file icons in the Finder of the selected job groups.
1640 //------------------------------------------------------------------------------------
1641 - (IBAction)revealSelectedJobGroups: (id)sender
1643 if (!fHandle) return;
1645 NSIndexSet * selectedRows = [fOutlineView selectedRowIndexes];
1646 NSInteger row = [selectedRows firstIndex];
1647 if (row != NSNotFound)
1649 while (row != NSNotFound)
1651 HBJobGroup * jobGroup = [fOutlineView itemAtRow: row];
1652 if ([[jobGroup destinationPath] length])
1653 [[NSWorkspace sharedWorkspace] selectFile:[jobGroup destinationPath] inFileViewerRootedAtPath:nil];
1655 row = [selectedRows indexGreaterThanIndex: row];
1660 //------------------------------------------------------------------------------------
1661 // Calls HBController Cancel: which displays an alert asking user if they want to
1662 // cancel encoding of current job. cancelCurrentJob: returns immediately after posting
1663 // the alert. Later, when the user acknowledges the alert, HBController will call
1664 // libhb to cancel the job.
1665 //------------------------------------------------------------------------------------
1666 - (IBAction)cancelCurrentJob: (id)sender
1668 [fHBController Cancel:sender];
1671 //------------------------------------------------------------------------------------
1672 // Starts or cancels the processing of jobs depending on the current state
1673 //------------------------------------------------------------------------------------
1674 - (IBAction)toggleStartCancel: (id)sender
1676 if (!fHandle) return;
1679 hb_get_state2 (fHandle, &s);
1681 if ((s.state == HB_STATE_PAUSED) || (s.state == HB_STATE_WORKING) || (s.state == HB_STATE_MUXING))
1682 [fHBController Cancel: fQueuePane]; // sender == fQueuePane so that warning alert shows up on queue window
1684 else if ([self pendingCount] > 0)
1685 [fHBController doRip];
1688 //------------------------------------------------------------------------------------
1689 // Toggles the pause/resume state of libhb
1690 //------------------------------------------------------------------------------------
1691 - (IBAction)togglePauseResume: (id)sender
1693 if (!fHandle) return;
1696 hb_get_state2 (fHandle, &s);
1698 if (s.state == HB_STATE_PAUSED)
1699 hb_resume (fHandle);
1700 else if ((s.state == HB_STATE_WORKING) || (s.state == HB_STATE_MUXING))
1705 #pragma mark Synchronizing with libhb
1707 //------------------------------------------------------------------------------------
1708 // Queues a job group. The job group's status is set to HBStatusPending.
1709 //------------------------------------------------------------------------------------
1710 - (void) addJobGroup: (HBJobGroup *) aJobGroup
1712 NSAssert(![fJobGroups containsObject:aJobGroup], @"Duplicate job group");
1713 [aJobGroup setStatus:HBStatusPending];
1715 [self beginEditingJobGroupsArray];
1716 [fJobGroups addObject:aJobGroup];
1717 [self endEditingJobGroupsArray];
1720 //------------------------------------------------------------------------------------
1721 // Notifies HBQueueController that libhb's current job has changed
1722 //------------------------------------------------------------------------------------
1723 - (void)currentJobChanged: (HBJob *) currentJob
1725 /* if the job has a destination path, lets perform finished job notifications in fHBController
1726 * We call this here so that we pickup the last job in the queue and single encodes before fCurrentJob
1727 * is released. So for the first job and the beginning of single encodes we check for the existence
1728 * of a valid fCurrentJob jobGroup
1730 [currentJob retain];
1731 /* We need to compare the job group to determine if this is the end of a job group
1732 * or just the end of a job within a group to keep from sending encode done notification
1733 * after the first pass in a two pass encode
1735 HBJobGroup * theJobGroupCheck = [currentJob jobGroup];
1736 if ((theJobGroupCheck == nil) || (theJobGroupCheck != fCurrentJobGroup))
1738 /* we need to make sure that we are not at the beginning of a queue and also that the job hasn't
1741 if ([[fCurrentJob jobGroup] destinationPath] && [fCurrentJobGroup status] != HBStatusCanceled)
1743 /* send encode messages to fHBController. User prefs are grokked there. */
1744 [fHBController showGrowlDoneNotification: [[fCurrentJob jobGroup] destinationPath]];
1745 [fHBController sendToMetaX: [[fCurrentJob jobGroup] destinationPath]];
1748 [fCurrentJob release];
1749 fCurrentJob = currentJob;
1751 // Log info about the preset name. We do this for each job, since libhb logs each
1752 // job separately. The preset name is found in the job's job group object.
1753 if (fCurrentJob && [fCurrentJob jobGroup] && ([[[fCurrentJob jobGroup] presetName] length] > 0))
1754 [fHBController writeToActivityLog: "Using preset: %s", [[[fCurrentJob jobGroup] presetName] UTF8String]];
1756 // Check to see if this is also a change in Job Group
1758 HBJobGroup * theJobGroup = [currentJob jobGroup];
1759 if ((theJobGroup == nil) || (theJobGroup != fCurrentJobGroup)) // no more job groups or start of a new group
1761 // Previous job has completed
1762 if (fCurrentJobGroup)
1764 // Update the status of the job that just finished. If the user canceled,
1765 // the status will have already been set to canceled by libhbWillStop. So
1766 // all other cases are assumed to be a successful encode. BTW, libhb
1767 // doesn't currently report errors back to the GUI.
1768 if ([fCurrentJobGroup status] != HBStatusCanceled)
1770 [fCurrentJobGroup setStatus:HBStatusCompleted];
1775 // Set the new group
1776 [self setCurrentJobGroup: theJobGroup];
1779 [self updateCurrentJobDescription];
1780 [self updateCurrentJobProgress];
1781 [self showCurrentJobPane: fCurrentJobGroup != nil];
1782 if (fCurrentJobGroup)
1783 [self startAnimatingCurrentJobGroupInQueue];
1785 [self stopAnimatingCurrentJobGroupInQueue];
1788 else // start a new job/pass in the same group
1791 [self updateCurrentJobDescription];
1792 [self updateCurrentJobProgress];
1797 //------------------------------------------------------------------------------------
1798 // Notifies HBQueueController that hb_stop is about to be called. This signals us that
1799 // the current job is going to be canceled and deleted. This is somewhat of a hack to
1800 // let HBQueueController know when a job group has been cancelled. Otherwise, we'd
1801 // have no way of knowing if a job was canceled or completed sucessfully.
1802 //------------------------------------------------------------------------------------
1803 - (void)libhbWillStop
1805 if (fCurrentJobGroup)
1806 [fCurrentJobGroup setStatus: HBStatusCanceled];
1809 //------------------------------------------------------------------------------------
1810 // Notifies HBQueueController that libhb's state has changed
1811 //------------------------------------------------------------------------------------
1812 - (void)libhbStateChanged: (hb_state_t)state
1814 switch( state.state )
1816 case HB_STATE_WORKING:
1818 //NSLog(@"job = %x; job_cur = %d; job_count = %d", state.param.working.sequence_id, state.param.working.job_cur, state.param.working.job_count);
1819 // First check to see if libhb has moved on to another job. We get no direct
1820 // message when this happens, so we have to detect it ourself. The new job could
1821 // be either just the next job in the current group, or the start of a new group.
1822 if (fCurrentJobID != state.param.working.sequence_id)
1824 fCurrentJobID = state.param.working.sequence_id;
1825 HBJob * currentJob = [self findJobWithID:fCurrentJobID];
1826 [self currentJobChanged: currentJob];
1831 [self updateCurrentJobProgress];
1832 [self startAnimatingCurrentJobGroupInQueue];
1837 case HB_STATE_MUXING:
1839 [self updateCurrentJobProgress];
1843 case HB_STATE_PAUSED:
1845 [self updateCurrentJobProgress];
1846 [self stopAnimatingCurrentJobGroupInQueue];
1850 case HB_STATE_WORKDONE:
1852 // HB_STATE_WORKDONE means that libhb has finished processing all the jobs
1853 // in *its* queue. This message is NOT sent as each individual job is
1856 [self currentJobChanged: nil];
1865 #if HB_OUTLINE_METRIC_CONTROLS
1866 static CGFloat spacingWidth = 3.0;
1867 - (IBAction)imageSpacingChanged: (id)sender;
1869 spacingWidth = [sender floatValue];
1870 [fOutlineView setNeedsDisplay: YES];
1872 - (IBAction)indentChanged: (id)sender
1874 [fOutlineView setIndentationPerLevel: [sender floatValue]];
1875 [fOutlineView setNeedsDisplay: YES];
1881 //------------------------------------------------------------------------------------
1882 // Receives notification whenever an HBJobGroup's status is changed.
1883 //------------------------------------------------------------------------------------
1884 - (void) jobGroupStatusNotification:(NSNotification *)notification
1886 [self setJobGroupCountsNeedUpdating: YES];
1887 // HBQueueJobGroupStatus oldStatus = (HBQueueJobGroupStatus) [[[notification userInfo] objectForKey:@"HBOldJobGroupStatus"] integerValue];
1888 HBJobGroup * jobGroup = [notification object];
1890 [self updateJobGroupInQueue:jobGroup];
1891 [self updateQueueCountField];
1896 #pragma mark Toolbar
1898 //------------------------------------------------------------------------------------
1900 //------------------------------------------------------------------------------------
1901 - (void)setupToolbar
1903 // Create a new toolbar instance, and attach it to our window
1904 NSToolbar *toolbar = [[[NSToolbar alloc] initWithIdentifier: HBQueueToolbar] autorelease];
1906 // Set up toolbar properties: Allow customization, give a default display mode, and remember state in user defaults
1907 [toolbar setAllowsUserCustomization: YES];
1908 [toolbar setAutosavesConfiguration: YES];
1909 [toolbar setDisplayMode: NSToolbarDisplayModeIconAndLabel];
1911 // We are the delegate
1912 [toolbar setDelegate: self];
1914 // Attach the toolbar to our window
1915 [[self window] setToolbar:toolbar];
1918 //------------------------------------------------------------------------------------
1919 // toolbar:itemForItemIdentifier:willBeInsertedIntoToolbar:
1920 //------------------------------------------------------------------------------------
1921 - (NSToolbarItem *)toolbar:(NSToolbar *)toolbar
1922 itemForItemIdentifier:(NSString *)itemIdentifier
1923 willBeInsertedIntoToolbar:(BOOL)flag
1925 // Required delegate method: Given an item identifier, this method returns an item.
1926 // The toolbar will use this method to obtain toolbar items that can be displayed
1927 // in the customization sheet, or in the toolbar itself.
1929 NSToolbarItem *toolbarItem = nil;
1931 if ([itemIdentifier isEqual: HBQueueStartCancelToolbarIdentifier])
1933 toolbarItem = [[[NSToolbarItem alloc] initWithItemIdentifier: itemIdentifier] autorelease];
1935 // Set the text label to be displayed in the toolbar and customization palette
1936 [toolbarItem setLabel: @"Start"];
1937 [toolbarItem setPaletteLabel: @"Start/Cancel"];
1939 // Set up a reasonable tooltip, and image
1940 [toolbarItem setToolTip: @"Start Encoding"];
1941 [toolbarItem setImage: [NSImage imageNamed: @"Play"]];
1943 // Tell the item what message to send when it is clicked
1944 [toolbarItem setTarget: self];
1945 [toolbarItem setAction: @selector(toggleStartCancel:)];
1948 if ([itemIdentifier isEqual: HBQueuePauseResumeToolbarIdentifier])
1950 toolbarItem = [[[NSToolbarItem alloc] initWithItemIdentifier: itemIdentifier] autorelease];
1952 // Set the text label to be displayed in the toolbar and customization palette
1953 [toolbarItem setLabel: @"Pause"];
1954 [toolbarItem setPaletteLabel: @"Pause/Resume"];
1956 // Set up a reasonable tooltip, and image
1957 [toolbarItem setToolTip: @"Pause Encoding"];
1958 [toolbarItem setImage: [NSImage imageNamed: @"Pause"]];
1960 // Tell the item what message to send when it is clicked
1961 [toolbarItem setTarget: self];
1962 [toolbarItem setAction: @selector(togglePauseResume:)];
1968 //------------------------------------------------------------------------------------
1969 // toolbarDefaultItemIdentifiers:
1970 //------------------------------------------------------------------------------------
1971 - (NSArray *) toolbarDefaultItemIdentifiers: (NSToolbar *) toolbar
1973 // Required delegate method: Returns the ordered list of items to be shown in the
1974 // toolbar by default.
1976 return [NSArray arrayWithObjects:
1977 HBQueueStartCancelToolbarIdentifier,
1978 HBQueuePauseResumeToolbarIdentifier,
1982 //------------------------------------------------------------------------------------
1983 // toolbarAllowedItemIdentifiers:
1984 //------------------------------------------------------------------------------------
1985 - (NSArray *) toolbarAllowedItemIdentifiers: (NSToolbar *) toolbar
1987 // Required delegate method: Returns the list of all allowed items by identifier.
1988 // By default, the toolbar does not assume any items are allowed, even the
1989 // separator. So, every allowed item must be explicitly listed.
1991 return [NSArray arrayWithObjects:
1992 HBQueueStartCancelToolbarIdentifier,
1993 HBQueuePauseResumeToolbarIdentifier,
1994 NSToolbarCustomizeToolbarItemIdentifier,
1995 NSToolbarFlexibleSpaceItemIdentifier,
1996 NSToolbarSpaceItemIdentifier,
1997 NSToolbarSeparatorItemIdentifier,
2001 //------------------------------------------------------------------------------------
2002 // validateToolbarItem:
2003 //------------------------------------------------------------------------------------
2004 - (BOOL) validateToolbarItem: (NSToolbarItem *) toolbarItem
2006 // Optional method: This message is sent to us since we are the target of some
2007 // toolbar item actions.
2009 if (!fHandle) return NO;
2014 hb_get_state2 (fHandle, &s);
2016 if ([[toolbarItem itemIdentifier] isEqual: HBQueueStartCancelToolbarIdentifier])
2018 if ((s.state == HB_STATE_PAUSED) || (s.state == HB_STATE_WORKING) || (s.state == HB_STATE_MUXING))
2021 [toolbarItem setImage:[NSImage imageNamed: @"Stop"]];
2022 [toolbarItem setLabel: @"Stop"];
2023 [toolbarItem setToolTip: @"Stop Encoding"];
2026 else if ([self pendingCount] > 0)
2029 [toolbarItem setImage:[NSImage imageNamed: @"Play"]];
2030 [toolbarItem setLabel: @"Start"];
2031 [toolbarItem setToolTip: @"Start Encoding"];
2037 [toolbarItem setImage:[NSImage imageNamed: @"Play"]];
2038 [toolbarItem setLabel: @"Start"];
2039 [toolbarItem setToolTip: @"Start Encoding"];
2043 if ([[toolbarItem itemIdentifier] isEqual: HBQueuePauseResumeToolbarIdentifier])
2045 if (s.state == HB_STATE_PAUSED)
2048 [toolbarItem setImage:[NSImage imageNamed: @"Play"]];
2049 [toolbarItem setLabel: @"Resume"];
2050 [toolbarItem setToolTip: @"Resume Encoding"];
2053 else if ((s.state == HB_STATE_WORKING) || (s.state == HB_STATE_MUXING))
2056 [toolbarItem setImage:[NSImage imageNamed: @"Pause"]];
2057 [toolbarItem setLabel: @"Pause"];
2058 [toolbarItem setToolTip: @"Pause Encoding"];
2063 [toolbarItem setImage:[NSImage imageNamed: @"Pause"]];
2064 [toolbarItem setLabel: @"Pause"];
2065 [toolbarItem setToolTip: @"Pause Encoding"];
2074 //------------------------------------------------------------------------------------
2076 //------------------------------------------------------------------------------------
2077 - (void)awakeFromNib
2079 [self setupToolbar];
2081 if( ![[self window] setFrameUsingName:@"Queue"] )
2082 [[self window] center];
2083 [self setWindowFrameAutosaveName:@"Queue"];
2084 [[self window] setExcludedFromWindowsMenu:YES];
2086 #if HB_QUEUE_DRAGGING
2087 [fOutlineView registerForDraggedTypes: [NSArray arrayWithObject:HBQueuePboardType] ];
2088 [fOutlineView setDraggingSourceOperationMask:NSDragOperationEvery forLocal:YES];
2089 [fOutlineView setVerticalMotionCanBeginDrag: YES];
2092 // Don't allow autoresizing of main column, else the "delete" column will get
2093 // pushed out of view.
2094 [fOutlineView setAutoresizesOutlineColumn: NO];
2096 #if HB_OUTLINE_METRIC_CONTROLS
2097 [fIndentation setHidden: NO];
2098 [fSpacing setHidden: NO];
2099 [fIndentation setIntegerValue:[fOutlineView indentationPerLevel]]; // debug
2100 [fSpacing setIntegerValue:3]; // debug
2103 // Show/hide UI elements
2104 fCurrentJobPaneShown = YES; // it's shown in the nib
2105 [self showCurrentJobPane:NO];
2107 [self updateQueueCountField];
2111 //------------------------------------------------------------------------------------
2113 //------------------------------------------------------------------------------------
2114 - (void)windowWillClose:(NSNotification *)aNotification
2116 [[NSUserDefaults standardUserDefaults] setBool:NO forKey:@"QueueWindowIsOpen"];
2121 - (void)moveObjectsInArray:(NSMutableArray *)array fromIndexes:(NSIndexSet *)indexSet toIndex:(NSUInteger)insertIndex
2123 NSUInteger index = [indexSet lastIndex];
2124 NSUInteger aboveInsertIndexCount = 0;
2126 while (index != NSNotFound)
2128 NSUInteger removeIndex;
2130 if (index >= insertIndex)
2132 removeIndex = index + aboveInsertIndexCount;
2133 aboveInsertIndexCount++;
2137 removeIndex = index;
2141 id object = [[array objectAtIndex:removeIndex] retain];
2142 [array removeObjectAtIndex:removeIndex];
2143 [array insertObject:object atIndex:insertIndex];
2146 index = [indexSet indexLessThanIndex:index];
2151 #pragma mark NSOutlineView delegate
2153 - (id)outlineView:(NSOutlineView *)outlineView child:(NSInteger)index ofItem:(id)item
2156 return [fJobGroups objectAtIndex:index];
2158 // We are only one level deep, so we can't be asked about children
2159 NSAssert (NO, @"HBQueueController outlineView:child:ofItem: can't handle nested items.");
2163 - (BOOL)outlineView:(NSOutlineView *)outlineView isItemExpandable:(id)item
2165 // Our outline view has no levels, but we can still expand every item. Doing so
2166 // just makes the row taller. See heightOfRowByItem below.
2170 - (BOOL)outlineView:(NSOutlineView *)outlineView shouldExpandItem:(id)item
2172 // Our outline view has no levels, but we can still expand every item. Doing so
2173 // just makes the row taller. See heightOfRowByItem below.
2174 #if HB_QUEUE_DRAGGING
2175 // Don't autoexpand while dragging, since we can't drop into the items
2176 return ![(HBQueueOutlineView*)outlineView isDragging];
2182 - (NSInteger)outlineView:(NSOutlineView *)outlineView numberOfChildrenOfItem:(id)item
2184 // Our outline view has no levels, so number of children will be zero for all
2187 return [fJobGroups count];
2192 - (void)outlineViewItemDidCollapse:(NSNotification *)notification
2194 id item = [[notification userInfo] objectForKey:@"NSObject"];
2195 NSInteger row = [fOutlineView rowForItem:item];
2196 [fOutlineView noteHeightOfRowsWithIndexesChanged:[NSIndexSet indexSetWithIndexesInRange:NSMakeRange(row,1)]];
2199 - (void)outlineViewItemDidExpand:(NSNotification *)notification
2201 id item = [[notification userInfo] objectForKey:@"NSObject"];
2202 NSInteger row = [fOutlineView rowForItem:item];
2203 [fOutlineView noteHeightOfRowsWithIndexesChanged:[NSIndexSet indexSetWithIndexesInRange:NSMakeRange(row,1)]];
2206 - (CGFloat)outlineView:(NSOutlineView *)outlineView heightOfRowByItem:(id)item
2208 if ([outlineView isItemExpanded: item])
2210 // Short-circuit here if in a live resize primarily to fix a bug but also to
2211 // increase resposivness during a resize. There's a bug in NSTableView that
2212 // causes row heights to get messed up if you try to change them during a live
2213 // resize. So if in a live resize, simply return the previously calculated
2214 // height. The row heights will get fixed up after the resize because we have
2215 // implemented viewDidEndLiveResize to force all of them to be recalculated.
2216 if ([outlineView inLiveResize] && [item lastDescriptionHeight] > 0)
2217 return [item lastDescriptionHeight];
2219 CGFloat width = [[outlineView tableColumnWithIdentifier: @"desc"] width];
2220 // Column width is NOT what is ultimately used. I can't quite figure out what
2221 // width to use for calculating text metrics. No matter how I tweak this value,
2222 // there are a few conditions in which the drawn text extends below the bounds
2223 // of the row cell. In previous versions, which ran under Tiger, I was
2224 // reducing width by 47 pixles.
2225 width -= 2; // (?) for intercell spacing
2227 CGFloat height = [item heightOfDescriptionForWidth: width];
2231 return HB_ROW_HEIGHT_TITLE_ONLY;
2234 - (id)outlineView:(NSOutlineView *)outlineView objectValueForTableColumn:(NSTableColumn *)tableColumn byItem:(id)item
2236 // nb: The "desc" column is currently an HBImageAndTextCell. However, we are longer
2237 // using the image portion of the cell so we could switch back to a regular NSTextFieldCell.
2239 if ([[tableColumn identifier] isEqualToString:@"desc"])
2240 return [item attributedDescription];
2241 else if ([[tableColumn identifier] isEqualToString:@"icon"])
2243 switch ([(HBJobGroup*)item status])
2245 case HBStatusCanceled:
2246 return [NSImage imageNamed:@"EncodeCanceled"];
2248 case HBStatusCompleted:
2249 return [NSImage imageNamed:@"EncodeComplete"];
2251 case HBStatusWorking:
2252 return [NSImage imageNamed: [NSString stringWithFormat: @"EncodeWorking%d", fAnimationIndex]];
2255 return [NSImage imageNamed:@"JobSmall"];
2263 - (void)outlineView:(NSOutlineView *)outlineView willDisplayCell:(id)cell forTableColumn:(NSTableColumn *)tableColumn item:(id)item
2265 if ([[tableColumn identifier] isEqualToString:@"desc"])
2267 #if HB_OUTLINE_METRIC_CONTROLS
2268 NSSize theSize = [cell imageSpacing];
2269 theSize.width = spacingWidth;
2270 [cell setImageSpacing: theSize];
2273 // nb: The "desc" column is currently an HBImageAndTextCell. However, we are longer
2274 // using the image portion of the cell so we could switch back to a regular NSTextFieldCell.
2276 // Set the image here since the value returned from outlineView:objectValueForTableColumn: didn't specify the image part
2277 [cell setImage:nil];
2280 else if ([[tableColumn identifier] isEqualToString:@"action"])
2282 [cell setEnabled: YES];
2283 BOOL highlighted = [outlineView isRowSelected:[outlineView rowForItem: item]] && [[outlineView window] isKeyWindow] && ([[outlineView window] firstResponder] == outlineView);
2284 if ([(HBJobGroup*)item status] == HBStatusCompleted)
2286 [cell setAction: @selector(revealSelectedJobGroups:)];
2289 [cell setImage:[NSImage imageNamed:@"RevealHighlight"]];
2290 [cell setAlternateImage:[NSImage imageNamed:@"RevealHighlightPressed"]];
2293 [cell setImage:[NSImage imageNamed:@"Reveal"]];
2297 [cell setAction: @selector(removeSelectedJobGroups:)];
2300 [cell setImage:[NSImage imageNamed:@"DeleteHighlight"]];
2301 [cell setAlternateImage:[NSImage imageNamed:@"DeleteHighlightPressed"]];
2304 [cell setImage:[NSImage imageNamed:@"Delete"]];
2309 - (void)outlineView:(NSOutlineView *)outlineView willDisplayOutlineCell:(id)cell forTableColumn:(NSTableColumn *)tableColumn item:(id)item
2311 // By default, the discolsure image gets centered vertically in the cell. We want
2312 // always at the top.
2313 if ([outlineView isItemExpanded: item])
2314 [cell setImagePosition: NSImageAbove];
2316 [cell setImagePosition: NSImageOnly];
2320 #pragma mark NSOutlineView delegate (dragging related)
2322 //------------------------------------------------------------------------------------
2323 // NSTableView delegate
2324 //------------------------------------------------------------------------------------
2326 #if HB_QUEUE_DRAGGING
2327 - (BOOL)outlineView:(NSOutlineView *)outlineView writeItems:(NSArray *)items toPasteboard:(NSPasteboard *)pboard
2329 // Dragging is only allowed of the pending items.
2330 for( HBJobGroup * group in items )
2332 if ([group status] != HBStatusPending)
2336 // Don't retain since this is just holding temporaral drag information, and it is
2337 //only used during a drag! We could put this in the pboard actually.
2338 fDraggedNodes = items;
2340 // Provide data for our custom type, and simple NSStrings.
2341 [pboard declareTypes:[NSArray arrayWithObjects: HBQueuePboardType, nil] owner:self];
2343 // the actual data doesn't matter since DragDropSimplePboardType drags aren't recognized by anyone but us!.
2344 [pboard setData:[NSData data] forType:HBQueuePboardType];
2350 #if HB_QUEUE_DRAGGING
2351 - (NSDragOperation)outlineView:(NSOutlineView *)outlineView validateDrop:(id <NSDraggingInfo>)info proposedItem:(id)item proposedChildIndex:(NSInteger)index
2353 // Don't allow dropping ONTO an item since they can't really contain any children.
2354 BOOL isOnDropTypeProposal = index == NSOutlineViewDropOnItemIndex;
2355 if (isOnDropTypeProposal)
2356 return NSDragOperationNone;
2358 // Don't allow dropping INTO an item since they can't really contain any children.
2361 index = [fOutlineView rowForItem: item] + 1;
2365 // Prevent dragging into the completed or current job.
2366 int firstPendingIndex = [self completedCount];
2367 if (fCurrentJobGroup)
2368 firstPendingIndex++;
2369 index = MAX (index, firstPendingIndex);
2371 [outlineView setDropItem:item dropChildIndex:index];
2372 return NSDragOperationGeneric;
2376 #if HB_QUEUE_DRAGGING
2377 - (BOOL)outlineView:(NSOutlineView *)outlineView acceptDrop:(id <NSDraggingInfo>)info item:(id)item childIndex:(NSInteger)index
2379 NSMutableIndexSet *moveItems = [NSMutableIndexSet indexSet];
2381 for( id obj in fDraggedNodes)
2383 [moveItems addIndex:[fJobGroups indexOfObject:obj]];
2386 // Rearrange the data and view
2387 [self saveOutlineViewState];
2388 [self moveObjectsInArray:fJobGroups fromIndexes:moveItems toIndex: index];
2389 [fOutlineView reloadData];
2390 [self restoreOutlineViewState];