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 #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"
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 = NULL;
104 static NSDictionary* _detailAttribute = NULL;
105 static NSDictionary* _detailBoldAttribute = NULL;
106 static NSDictionary* _titleAttribute = NULL;
107 static NSDictionary* _shortHeightAttribute = NULL;
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:@""];
153 for( int i = 0; i < hb_list_count(job->list_audio); i++ )
155 audio = (hb_audio_config_t *) hb_list_audio_config_item( job->list_audio, i );
157 NSString *outputCodec;
158 if (audio->out.codec == HB_ACODEC_AC3)
159 outputCodec = @"AC3";
160 else if (audio->out.codec == HB_ACODEC_FAAC)
161 outputCodec = @"AAC";
162 else if (audio->out.codec == HB_ACODEC_LAME)
163 outputCodec = @"MP3";
164 else if (audio->out.codec == HB_ACODEC_VORBIS)
165 outputCodec = @"Vorbis";
167 outputCodec = @"Unknown Codec";
168 /* Add the codec to the audio codecs list ( We should check against dupes)*/
169 thisJobAudioCodecs = [thisJobAudioCodecs stringByAppendingString:[NSString stringWithFormat:@" %@,",outputCodec]];
172 /* Insert a line break so that we get each track on a separate line */
173 /* Wicked HACK alert!!, use 18 whitespaces to align offset in display for list > 2 to offset "Audio" in the queue display
174 Please Fix Me because this is embarrassing (but it works) */
175 thisJobAudioInfo = [thisJobAudioInfo stringByAppendingString:@"\n "];
177 /* Detailed Job audio track info*/
178 /* Track Number and Mixdown Info */
179 if (audio->out.mixdown == HB_ACODEC_AC3)// Remember for ac3 passthru the mixdown uses the source codec
180 thisJobAudioInfo = [thisJobAudioInfo stringByAppendingString:[NSString stringWithFormat:@"Track %d: Source: %@ Output: %@, Pass-Thru", i + 1, [NSString stringWithUTF8String:audio->lang.description], outputCodec]];
181 else if (audio->out.mixdown == HB_AMIXDOWN_MONO)
182 thisJobAudioInfo = [thisJobAudioInfo stringByAppendingString:[NSString stringWithFormat:@"Track %d: Source: %@ Output: %@, Mono", i + 1, [NSString stringWithUTF8String:audio->lang.description], outputCodec]];
183 else if (audio->out.mixdown == HB_AMIXDOWN_STEREO)
184 thisJobAudioInfo = [thisJobAudioInfo stringByAppendingString:[NSString stringWithFormat:@"Track %d: Source: %@ Output: %@, Stereo", i + 1, [NSString stringWithUTF8String:audio->lang.description], outputCodec]];
185 else if (audio->out.mixdown == HB_AMIXDOWN_DOLBY)
186 thisJobAudioInfo = [thisJobAudioInfo stringByAppendingString:[NSString stringWithFormat:@"Track %d: Source: %@ Output: %@, Dolby Surround", i + 1, [NSString stringWithUTF8String:audio->lang.description], outputCodec]];
187 else if (audio->out.mixdown == HB_AMIXDOWN_DOLBYPLII)
188 thisJobAudioInfo = [thisJobAudioInfo stringByAppendingString:[NSString stringWithFormat:@"Track %d: Source: %@ Output: %@, Dolby Pro Logic II", i + 1, [NSString stringWithUTF8String:audio->lang.description], outputCodec]];
189 else if (audio->out.mixdown == HB_AMIXDOWN_6CH)
190 thisJobAudioInfo = [thisJobAudioInfo stringByAppendingString:[NSString stringWithFormat:@"Track %d: Source: %@ Output: %@, 6 Channel Discreet", i + 1, [NSString stringWithUTF8String:audio->lang.description], outputCodec]];
192 thisJobAudioInfo = [thisJobAudioInfo stringByAppendingString:[NSString stringWithFormat:@"Track %d: Source: %@ Output: Unknown Codec Info", i + 1, [NSString stringWithUTF8String:audio->lang.description]]];
194 thisJobAudioInfo = [thisJobAudioInfo stringByAppendingString:[NSString stringWithFormat:@", %d kbps, %d Hz", audio->out.bitrate, audio->out.samplerate]];
197 audioinfo_summary = [[NSString stringWithFormat:@"%@",thisJobAudioInfo]retain];
198 audioinfo_codecs = [[NSString stringWithFormat:@"%@",thisJobAudioCodecs]retain];
200 subtitle = job->subtitle;
203 file = [[NSString stringWithUTF8String:job->file] retain];
204 if (job->title->name)
205 titleName = [[NSString stringWithUTF8String:job->title->name] retain];
206 titleIndex = job->title->index;
207 titleWidth = job->title->width;
208 titleHeight = job->title->height;
209 if (job->subtitle >= 0)
211 hb_subtitle_t * aSubtitle = (hb_subtitle_t *) hb_list_item(job->title->list_subtitle, job->subtitle);
213 subtitleLang = [[NSString stringWithUTF8String:aSubtitle->lang] retain];
216 // Calculate and store output dimensions and anamorphic dimensions
217 if (pixel_ratio == 1) // Original PAR Implementation, now called Strict Anamorphic
219 output_width = titleWidth - crop[2] - crop[3];
220 output_height = titleHeight - crop[0] - crop[1];
221 anamorphic_width = output_width * pixel_aspect_width / pixel_aspect_height;
222 anamorphic_height = output_height;
224 else if (pixel_ratio == 2) // Loose Anamorphic
226 // call hb_set_anamorphic_size to do a "dry run" to get the values to be
227 // used by libhb for loose anamorphic.
228 int par_width, par_height;
229 hb_set_anamorphic_size(job, &output_width, &output_height, &par_width, &par_height);
230 anamorphic_width = output_width * par_width / par_height;
231 anamorphic_height = output_height;
233 else // No Anamorphic
235 output_width = width;
236 output_height = height;
237 anamorphic_width = 0; // not needed for this case
238 anamorphic_height = 0; // not needed for this case
247 // jobGroup is a weak reference and does not need to be deleted
251 [subtitleLang release];
252 [audioinfo_summary release];
253 [audioinfo_codecs release];
257 - (HBJobGroup *) jobGroup
262 - (void) setJobGroup: (HBJobGroup *)aJobGroup
264 // This is a weak reference. We don't retain or release it.
265 jobGroup = aJobGroup;
268 //------------------------------------------------------------------------------------
269 // Generate string to display in UI.
270 //------------------------------------------------------------------------------------
272 - (NSMutableAttributedString *) attributedDescriptionWithIcon: (BOOL)withIcon
273 withTitle: (BOOL)withTitle
274 withPassName: (BOOL)withPassName
275 withFormatInfo: (BOOL)withFormatInfo
276 withDestination: (BOOL)withDestination
277 withPictureInfo: (BOOL)withPictureInfo
278 withVideoInfo: (BOOL)withVideoInfo
279 withx264Info: (BOOL)withx264Info
280 withAudioInfo: (BOOL)withAudioInfo
281 withSubtitleInfo: (BOOL)withSubtitleInfo
284 NSMutableAttributedString * finalString = [[[NSMutableAttributedString alloc] initWithString: @""] autorelease];
287 NSMutableParagraphStyle * ps = [HBJob descriptionParagraphStyle];
288 NSDictionary* detailAttr = [HBJob descriptionDetailAttribute];
289 NSDictionary* detailBoldAttr = [HBJob descriptionDetailBoldAttribute];
290 NSDictionary* titleAttr = [HBJob descriptionTitleAttribute];
291 NSDictionary* shortHeightAttr = [HBJob descriptionShortHeightAttribute];
293 // Title with summary
298 NSFileWrapper * wrapper = [[[NSFileWrapper alloc] initWithPath:[[NSBundle mainBundle] pathForImageResource: @"JobSmall"]] autorelease];
299 NSTextAttachment * imageAttachment = [[[NSTextAttachment alloc] initWithFileWrapper:wrapper] autorelease];
301 NSDictionary* imageAttributes = [NSDictionary dictionaryWithObjectsAndKeys:
302 [NSNumber numberWithFloat: -2.0], NSBaselineOffsetAttributeName,
303 imageAttachment, NSAttachmentAttributeName,
304 ps, NSParagraphStyleAttributeName,
307 NSAttributedString * imageAsString = [[[NSAttributedString alloc]
308 initWithString: [NSString stringWithFormat:@"%C%C", NSAttachmentCharacter, NSTabCharacter]
309 attributes: imageAttributes] autorelease];
311 [finalString appendAttributedString:imageAsString];
314 // Note: use title->name instead of title->dvd since name is just the chosen
315 // folder, instead of dvd which is the full path
316 [finalString appendString:titleName withAttributes:titleAttr];
318 NSString * summaryInfo;
320 NSString * chapterString = (chapter_start == chapter_end) ?
321 [NSString stringWithFormat:@"Chapter %d", chapter_start] :
322 [NSString stringWithFormat:@"Chapters %d through %d", chapter_start, chapter_end];
324 BOOL hasIndepthScan = (pass == -1);
325 int numVideoPasses = 0;
327 // To determine number of video passes, we need to skip past the subtitle scan.
330 // When job is the one currently being processed, then the next in its group
331 // is the the first job in the queue.
332 HBJob * nextjob = nil;
333 NSUInteger index = [jobGroup indexOfJob:self];
334 if (index != NSNotFound)
335 nextjob = [jobGroup jobAtIndex:index+1];
336 if (nextjob) // Overly cautious in case there is no next job!
337 numVideoPasses = MIN( 2, nextjob->pass + 1 );
340 numVideoPasses = MIN( 2, pass + 1 );
342 if (hasIndepthScan && numVideoPasses == 1)
343 summaryInfo = [NSString stringWithFormat: @" (Title %d, %@, Deep Scan, Single Video Pass)", titleIndex, chapterString];
344 else if (hasIndepthScan && numVideoPasses > 1)
345 summaryInfo = [NSString stringWithFormat: @" (Title %d, %@, Deep Scan, %d Video Passes)", titleIndex, chapterString, numVideoPasses];
346 else if (numVideoPasses == 1)
347 summaryInfo = [NSString stringWithFormat: @" (Title %d, %@, Single Video Pass)", titleIndex, chapterString];
349 summaryInfo = [NSString stringWithFormat: @" (Title %d, %@, %d Video Passes)", titleIndex, chapterString, numVideoPasses];
351 [finalString appendString:[NSString stringWithFormat:@"%@\n", summaryInfo] withAttributes:detailAttr];
353 // Insert a short-in-height line to put some white space after the title
354 [finalString appendString:@"\n" withAttributes:shortHeightAttr];
357 // End of title stuff
365 NSString * imageName;
368 case -1: imageName = @"JobPassSubtitleSmall"; break;
369 case 0: imageName = @"JobPassFirstSmall"; break;
370 case 1: imageName = @"JobPassFirstSmall"; break;
371 case 2: imageName = @"JobPassSecondSmall"; break;
372 default: imageName = @"JobPassUnknownSmall"; break;
375 NSFileWrapper * wrapper = [[[NSFileWrapper alloc] initWithPath:[[NSBundle mainBundle] pathForImageResource: imageName]] autorelease];
376 NSTextAttachment * imageAttachment = [[[NSTextAttachment alloc] initWithFileWrapper:wrapper] autorelease];
378 NSDictionary* imageAttributes = [NSDictionary dictionaryWithObjectsAndKeys:
379 [NSNumber numberWithFloat: -2.0], NSBaselineOffsetAttributeName,
380 imageAttachment, NSAttachmentAttributeName,
381 ps, NSParagraphStyleAttributeName,
384 NSAttributedString * imageAsString = [[[NSAttributedString alloc]
385 initWithString: [NSString stringWithFormat:@"%C%C", NSAttachmentCharacter, NSTabCharacter]
386 attributes: imageAttributes] autorelease];
388 [finalString appendAttributedString:imageAsString];
391 NSString * jobPassName;
393 jobPassName = NSLocalizedString (@"Deep Scan", nil);
396 int passNum = MAX( 1, pass );
398 jobPassName = NSLocalizedString (@"1st Pass", nil);
399 else if (passNum == 1)
400 jobPassName = NSLocalizedString (@"1st Pass", nil);
401 else if (passNum == 2)
402 jobPassName = NSLocalizedString (@"2nd Pass", nil);
404 jobPassName = [NSString stringWithFormat: NSLocalizedString(@"Pass %d", nil), passNum];
406 [finalString appendString:[NSString stringWithFormat:@"%@\n", jobPassName] withAttributes:detailBoldAttr];
409 // Video Codec needed by FormatInfo and withVideoInfo
410 NSString * jobVideoCodec = nil;
411 if (withFormatInfo || withVideoInfo)
414 // Video Codec settings (Encoder in the gui)
415 if (vcodec == HB_VCODEC_FFMPEG)
416 jobVideoCodec = @"FFmpeg"; // HB_VCODEC_FFMPEG
417 else if (vcodec == HB_VCODEC_XVID)
418 jobVideoCodec = @"XviD"; // HB_VCODEC_XVID
419 else if (vcodec == HB_VCODEC_X264)
421 // Deterimine for sure how we are now setting iPod uuid atom
422 if (h264_level) // We are encoding for iPod
423 jobVideoCodec = @"x264 (H.264 iPod)"; // HB_VCODEC_X264
425 jobVideoCodec = @"x264 (H.264 Main)"; // HB_VCODEC_X264
428 if (jobVideoCodec == nil)
429 jobVideoCodec = @"unknown";
431 // Audio Codec needed by FormatInfo and AudioInfo
432 NSString * jobAudioCodec = nil;
433 if (withFormatInfo || withAudioInfo)
436 jobAudioCodec = @"AAC"; // HB_ACODEC_FAAC
437 else if (acodec == 512)
438 jobAudioCodec = @"MP3"; // HB_ACODEC_LAME
439 else if (acodec == 1024)
440 jobAudioCodec = @"Vorbis"; // HB_ACODEC_VORBIS
441 else if (acodec == 2048)
442 jobAudioCodec = @"AC3"; // HB_ACODEC_AC3
444 if (jobAudioCodec == nil)
445 jobAudioCodec = @"unknown";
450 NSString * jobFormatInfo;
451 // Muxer settings (File Format in the gui)
452 if (mux == 65536 || mux == 131072 || mux == 1048576)
453 jobFormatInfo = @"MP4"; // HB_MUX_MP4,HB_MUX_PSP,HB_MUX_IPOD
454 else if (mux == 262144)
455 jobFormatInfo = @"AVI"; // HB_MUX_AVI
456 else if (mux == 524288)
457 jobFormatInfo = @"OGM"; // HB_MUX_OGM
458 else if (mux == 2097152)
459 jobFormatInfo = @"MKV"; // HB_MUX_MKV
461 jobFormatInfo = @"unknown";
463 if (chapter_markers == 1)
464 jobFormatInfo = [NSString stringWithFormat:@"%@ Container, %@ Video + %@ Audio, Chapter Markers\n", jobFormatInfo, jobVideoCodec, audioinfo_codecs];
466 jobFormatInfo = [NSString stringWithFormat:@"%@ Container, %@ Video + %@ Audio\n", jobFormatInfo, jobVideoCodec, audioinfo_codecs];
468 [finalString appendString: @"Format: " withAttributes:detailBoldAttr];
469 [finalString appendString: jobFormatInfo withAttributes:detailAttr];
474 [finalString appendString: @"Destination: " withAttributes:detailBoldAttr];
475 [finalString appendString:[NSString stringWithFormat:@"%@\n", file] withAttributes:detailAttr];
481 NSString * jobPictureInfo;
482 if (pixel_ratio == 1) // Original PAR Implementation, now called Strict Anamorphic
483 jobPictureInfo = [NSString stringWithFormat:@"%d x %d (%d x %d Strict Anamorphic)", output_width, output_height, anamorphic_width, anamorphic_height];
484 else if (pixel_ratio == 2) // Loose Anamorphic
485 jobPictureInfo = [NSString stringWithFormat:@"%d x %d (%d x %d Loose Anamorphic)", output_width, output_height, anamorphic_width, anamorphic_height];
487 jobPictureInfo = [NSString stringWithFormat:@"%d x %d", output_width, output_height];
489 jobPictureInfo = [jobPictureInfo stringByAppendingString:@" Keep Aspect Ratio"];
492 jobPictureInfo = [jobPictureInfo stringByAppendingString:@", Grayscale"];
494 if (deinterlace == 1)
495 jobPictureInfo = [jobPictureInfo stringByAppendingString:@", Deinterlace"];
496 if (withIcon) // implies indent the info
497 [finalString appendString: @"\t" withAttributes:detailBoldAttr];
498 [finalString appendString: @"Picture: " withAttributes:detailBoldAttr];
499 [finalString appendString:[NSString stringWithFormat:@"%@\n", jobPictureInfo] withAttributes:detailAttr];
504 NSString * jobVideoQuality;
505 NSString * jobVideoDetail;
507 if (vquality <= 0 || vquality >= 1)
508 jobVideoQuality = [NSString stringWithFormat:@"%d kbps", vbitrate];
511 NSNumber * vidQuality;
512 vidQuality = [NSNumber numberWithInt:vquality * 100];
513 // this is screwed up kind of. Needs to be formatted properly.
515 jobVideoQuality = [NSString stringWithFormat:@"%@%% CRF", vidQuality];
517 jobVideoQuality = [NSString stringWithFormat:@"%@%% CQP", vidQuality];
520 if (vrate_base == 1126125)
523 jobVideoDetail = [NSString stringWithFormat:@"%@, %@, 23.976 fps", jobVideoCodec, jobVideoQuality];
525 else if (vrate_base == 900900)
528 jobVideoDetail = [NSString stringWithFormat:@"%@, %@, 29.97 fps", jobVideoCodec, jobVideoQuality];
533 jobVideoDetail = [NSString stringWithFormat:@"%@, %@, %d fps", jobVideoCodec, jobVideoQuality, vrate / vrate_base];
535 if (withIcon) // implies indent the info
536 [finalString appendString: @"\t" withAttributes:detailBoldAttr];
537 [finalString appendString: @"Video: " withAttributes:detailBoldAttr];
538 [finalString appendString:[NSString stringWithFormat:@"%@\n", jobVideoDetail] withAttributes:detailAttr];
543 if (vcodec == HB_VCODEC_X264 && x264opts)
545 if (withIcon) // implies indent the info
546 [finalString appendString: @"\t" withAttributes:detailBoldAttr];
547 [finalString appendString: @"x264 Options: " withAttributes:detailBoldAttr];
548 [finalString appendString:[NSString stringWithFormat:@"%@\n", x264opts] withAttributes:detailAttr];
554 if (withIcon) // implies indent the info
555 [finalString appendString: @"\t" withAttributes:detailBoldAttr];
556 [finalString appendString: @"Audio: " withAttributes:detailBoldAttr];
557 [finalString appendString:[NSString stringWithFormat:@"%@\n", audioinfo_summary] withAttributes:detailAttr];
560 if (withSubtitleInfo)
562 // subtitle scan == -1 in two cases:
563 // autoselect: when pass == -1
564 // none: when pass != -1
565 if ((subtitle == -1) && (pass == -1))
567 if (withIcon) // implies indent the info
568 [finalString appendString: @"\t" withAttributes:detailBoldAttr];
569 [finalString appendString: @"Subtitles: " withAttributes:detailBoldAttr];
570 [finalString appendString: @"Autoselect " withAttributes:detailAttr];
572 else if (subtitle >= 0)
576 if (withIcon) // implies indent the info
577 [finalString appendString: @"\t" withAttributes:detailBoldAttr];
578 [finalString appendString: @"Subtitles: " withAttributes:detailBoldAttr];
579 [finalString appendString: subtitleLang withAttributes:detailAttr];
585 if ([[finalString string] hasSuffix: @"\n"])
586 [finalString deleteCharactersInRange: NSMakeRange([[finalString string] length]-1, 1)];
591 + (NSMutableParagraphStyle *) descriptionParagraphStyle
593 if (!_descriptionParagraphStyle)
595 _descriptionParagraphStyle = [[[NSParagraphStyle defaultParagraphStyle] mutableCopy] retain];
596 [_descriptionParagraphStyle setHeadIndent: 40.0];
597 [_descriptionParagraphStyle setParagraphSpacing: 1.0];
598 [_descriptionParagraphStyle setTabStops:[NSArray array]]; // clear all tabs
599 [_descriptionParagraphStyle addTabStop: [[[NSTextTab alloc] initWithType: NSLeftTabStopType location: 20.0] autorelease]];
601 return _descriptionParagraphStyle;
604 + (NSDictionary *) descriptionDetailAttribute
606 if (!_detailAttribute)
607 _detailAttribute = [[NSDictionary dictionaryWithObjectsAndKeys:
608 [NSFont systemFontOfSize:10.0], NSFontAttributeName,
609 _descriptionParagraphStyle, NSParagraphStyleAttributeName,
611 return _detailAttribute;
614 + (NSDictionary *) descriptionDetailBoldAttribute
616 if (!_detailBoldAttribute)
617 _detailBoldAttribute = [[NSDictionary dictionaryWithObjectsAndKeys:
618 [NSFont boldSystemFontOfSize:10.0], NSFontAttributeName,
619 _descriptionParagraphStyle, NSParagraphStyleAttributeName,
621 return _detailBoldAttribute;
624 + (NSDictionary *) descriptionTitleAttribute
626 if (!_titleAttribute)
627 _titleAttribute = [[NSDictionary dictionaryWithObjectsAndKeys:
628 [NSFont systemFontOfSize:[NSFont systemFontSize]], NSFontAttributeName,
629 _descriptionParagraphStyle, NSParagraphStyleAttributeName,
631 return _titleAttribute;
634 + (NSDictionary *) descriptionShortHeightAttribute
636 if (!_shortHeightAttribute)
637 _shortHeightAttribute = [[NSDictionary dictionaryWithObjectsAndKeys:
638 [NSFont systemFontOfSize:2.0], NSFontAttributeName,
640 return _shortHeightAttribute;
648 //------------------------------------------------------------------------------------
650 //------------------------------------------------------------------------------------
652 // Notification sent from HBJobGroup setStatus whenever the status changes.
653 NSString *HBJobGroupStatusNotification = @"HBJobGroupStatusNotification";
655 @implementation HBJobGroup
657 + (HBJobGroup *) jobGroup;
659 return [[[HBJobGroup alloc] init] autorelease];
664 if (self = [super init])
666 fJobs = [[NSMutableArray arrayWithCapacity:0] retain];
667 fDescription = [[NSMutableAttributedString alloc] initWithString: @""];
668 [self setNeedsDescription: NO];
669 fStatus = HBStatusNone;
676 [fPresetName release];
681 - (unsigned int) count
683 return [fJobs count];
686 - (void) addJob: (HBJob *)aJob
688 [aJob setJobGroup:self];
689 [fJobs addObject: aJob];
690 [self setNeedsDescription: YES];
691 fLastDescriptionHeight = 0;
692 fLastDescriptionWidth = 0;
695 - (HBJob *) jobAtIndex: (unsigned)index
697 return [fJobs objectAtIndex: index];
700 - (unsigned) indexOfJob: (HBJob *)aJob;
702 return [fJobs indexOfObject: aJob];
705 - (NSEnumerator *) jobEnumerator
707 return [fJobs objectEnumerator];
710 - (void) setNeedsDescription: (BOOL)flag
712 fNeedsDescription = flag;
715 - (void) updateDescription
717 fNeedsDescription = NO;
719 [fDescription deleteCharactersInRange: NSMakeRange(0, [fDescription length])];
721 if ([self count] == 0)
723 NSAssert(NO, @" jobgroup with no jobs");
727 HBJob * job = [self jobAtIndex:0];
730 [fDescription appendAttributedString: [job attributedDescriptionWithIcon: NO
739 withSubtitleInfo: NO]];
741 // append the preset name
742 if ([fPresetName length])
744 [fDescription appendString:@"Preset: " withAttributes:[HBJob descriptionDetailBoldAttribute]];
745 [fDescription appendString:fPresetName withAttributes:[HBJob descriptionDetailAttribute]];
746 [fDescription appendString:@"\n" withAttributes:[HBJob descriptionDetailAttribute]];
749 // append the format and destinaton
750 [fDescription appendAttributedString: [job attributedDescriptionWithIcon: NO
759 withSubtitleInfo: NO]];
762 static NSAttributedString * carriageReturn = [[NSAttributedString alloc] initWithString:@"\n"];
764 NSEnumerator * e = [self jobEnumerator];
765 while ( (job = [e nextObject]) )
767 int pass = job->pass;
768 [fDescription appendAttributedString:carriageReturn];
769 [fDescription appendAttributedString:
770 [job attributedDescriptionWithIcon: YES
775 withPictureInfo: pass != -1
776 withVideoInfo: pass != -1
777 withx264Info: pass != -1
778 withAudioInfo: pass == 0 || pass == 2
779 withSubtitleInfo: YES]];
784 - (NSMutableAttributedString *) attributedDescription
786 if (fNeedsDescription)
787 [self updateDescription];
791 - (CGFloat) heightOfDescriptionForWidth:(CGFloat)width
793 // Try to return the cached value if no changes have happened since the last time
794 if ((width == fLastDescriptionWidth) && (fLastDescriptionHeight != 0) && !fNeedsDescription)
795 return fLastDescriptionHeight;
797 if (fNeedsDescription)
798 [self updateDescription];
800 // Calculate the height
801 NSRect bounds = [fDescription boundingRectWithSize:NSMakeSize(width, 10000) options:NSStringDrawingUsesLineFragmentOrigin];
802 fLastDescriptionHeight = bounds.size.height + 6.0; // add some border to bottom
803 fLastDescriptionWidth = width;
804 return fLastDescriptionHeight;
806 /* supposedly another way to do this, in case boundingRectWithSize isn't working
807 NSTextView* tmpView = [[NSTextView alloc] initWithFrame:NSMakeRect(0, 0, width, 1)];
808 [[tmpView textStorage] setAttributedString:aString];
809 [tmpView setHorizontallyResizable:NO];
810 [tmpView setVerticallyResizable:YES];
811 // [[tmpView textContainer] setHeightTracksTextView: YES];
812 // [[tmpView textContainer] setContainerSize: NSMakeSize(width, 10000)];
814 float height = [tmpView frame].size.height;
820 - (CGFloat) lastDescriptionHeight
822 return fLastDescriptionHeight;
825 - (void) setStatus: (HBQueueJobGroupStatus)status
827 // Create a dictionary with the old status
828 NSDictionary * userInfo = [NSDictionary dictionaryWithObject:[NSNumber numberWithInt:self->fStatus] forKey:@"HBOldJobGroupStatus"];
830 self->fStatus = status;
832 // Send notification with old status
833 [[NSNotificationCenter defaultCenter] postNotificationName:HBJobGroupStatusNotification object:self userInfo:userInfo];
836 - (HBQueueJobGroupStatus) status
838 return self->fStatus;
841 - (void) setPresetName: (NSString *)name
844 [fPresetName release];
848 - (NSString *) presetName
855 HBJob * firstJob = [self jobAtIndex:0];
856 return firstJob ? firstJob->titleName : nil;
859 - (NSString *) destinationPath
861 HBJob * firstJob = [self jobAtIndex:0];
862 return firstJob ? firstJob->file : nil;
870 // Toolbar identifiers
871 static NSString* HBQueueToolbar = @"HBQueueToolbar1";
872 static NSString* HBQueueStartCancelToolbarIdentifier = @"HBQueueStartCancelToolbarIdentifier";
873 static NSString* HBQueuePauseResumeToolbarIdentifier = @"HBQueuePauseResumeToolbarIdentifier";
877 @implementation HBQueueController
879 //------------------------------------------------------------------------------------
881 //------------------------------------------------------------------------------------
884 if (self = [super init])
887 [[NSUserDefaults standardUserDefaults] registerDefaults:[NSDictionary dictionaryWithObjectsAndKeys:
888 @"NO", @"QueueWindowIsOpen",
889 @"NO", @"QueueShowsDetail",
890 @"YES", @"QueueShowsJobsAsGroups",
893 fJobGroups = [[NSMutableArray arrayWithCapacity:0] retain];
895 BOOL loadSucceeded = [NSBundle loadNibNamed:@"Queue" owner:self] && fQueueWindow;
896 NSAssert(loadSucceeded, @"Could not open Queue nib");
897 NSAssert(fQueueWindow, @"fQueueWindow not found in Queue nib");
899 // Register for HBJobGroup status changes
900 [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(jobGroupStatusNotification:) name:HBJobGroupStatusNotification object:nil];
905 //------------------------------------------------------------------------------------
907 //------------------------------------------------------------------------------------
910 // clear the delegate so that windowWillClose is not attempted
911 if ([fQueueWindow delegate] == self)
912 [fQueueWindow setDelegate:nil];
914 [fJobGroups release];
915 [fCurrentJobGroup release];
916 [fSavedExpandedItems release];
917 [fSavedSelectedItems release];
919 [[NSNotificationCenter defaultCenter] removeObserver:self];
924 //------------------------------------------------------------------------------------
926 //------------------------------------------------------------------------------------
927 - (void)setHandle: (hb_handle_t *)handle
932 //------------------------------------------------------------------------------------
933 // Receive HBController
934 //------------------------------------------------------------------------------------
935 - (void)setHBController: (HBController *)controller
937 fHBController = controller;
941 #pragma mark - Getting the currently processing job group
943 //------------------------------------------------------------------------------------
944 // Returns the HBJobGroup that is currently being encoded; nil if no encoding is
946 //------------------------------------------------------------------------------------
947 - (HBJobGroup *) currentJobGroup;
949 return fCurrentJobGroup;
952 //------------------------------------------------------------------------------------
953 // Returns the HBJob (pass) that is currently being encoded; nil if no encoding is
955 //------------------------------------------------------------------------------------
956 - (HBJob *) currentJob
963 //------------------------------------------------------------------------------------
964 // Displays and brings the queue window to the front
965 //------------------------------------------------------------------------------------
966 - (IBAction) showQueueWindow: (id)sender
968 [fQueueWindow makeKeyAndOrderFront: self];
969 [[NSUserDefaults standardUserDefaults] setBool:YES forKey:@"QueueWindowIsOpen"];
972 //------------------------------------------------------------------------------------
973 // Show or hide the current job pane (fCurrentJobPane).
974 //------------------------------------------------------------------------------------
975 - (void) showCurrentJobPane: (BOOL)showPane
977 if (showPane == fCurrentJobPaneShown)
980 // Things to keep in mind:
981 // - When the current job pane is shown, it occupies the upper portion of the
982 // window with the queue occupying the bottom portion of the window.
983 // - When the current job pane is hidden, it slides up and out of view.
984 // NSView setHidden is NOT used. The queue pane is resized to occupy the full
987 NSRect windowFrame = [[fCurrentJobPane superview] frame];
988 NSRect queueFrame, jobFrame;
990 NSDivideRect(windowFrame, &jobFrame, &queueFrame, NSHeight([fCurrentJobPane frame]), NSMaxYEdge);
993 queueFrame = windowFrame;
994 jobFrame = [fCurrentJobPane frame];
995 jobFrame.origin.y = NSHeight(windowFrame);
998 // Move fCurrentJobPane
999 NSDictionary * dict1 = [NSDictionary dictionaryWithObjectsAndKeys:
1000 fCurrentJobPane, NSViewAnimationTargetKey,
1001 [NSValue valueWithRect:jobFrame], NSViewAnimationEndFrameKey,
1004 // Resize fQueuePane
1005 NSDictionary * dict2 = [NSDictionary dictionaryWithObjectsAndKeys:
1006 fQueuePane, NSViewAnimationTargetKey,
1007 [NSValue valueWithRect:queueFrame], NSViewAnimationEndFrameKey,
1010 NSViewAnimation * anAnimation = [[[NSViewAnimation alloc] initWithViewAnimations:nil] autorelease];
1011 [anAnimation setViewAnimations:[NSArray arrayWithObjects:dict1, dict2, nil]];
1012 [anAnimation setDuration:0.25];
1013 [anAnimation setAnimationBlockingMode:NSAnimationBlocking]; // prevent user from resizing the window during an animation
1014 [anAnimation startAnimation];
1016 fCurrentJobPaneShown = showPane;
1019 //------------------------------------------------------------------------------------
1020 // Sets fCurrentJobGroup to a new job group.
1021 //------------------------------------------------------------------------------------
1022 - (void) setCurrentJobGroup: (HBJobGroup *)aJobGroup
1025 [aJobGroup setStatus: HBStatusWorking];
1028 [fCurrentJobGroup release];
1029 fCurrentJobGroup = aJobGroup;
1032 #pragma mark - Finding job groups
1034 //------------------------------------------------------------------------------------
1035 // Returns the first pending job with a specified destination path or nil if no such
1037 //------------------------------------------------------------------------------------
1038 - (HBJobGroup *) pendingJobGroupWithDestinationPath: (NSString *)path
1040 HBJobGroup * aJobGroup;
1041 NSEnumerator * groupEnum = [fJobGroups objectEnumerator];
1042 while ( (aJobGroup = [groupEnum nextObject]) )
1044 if ([[aJobGroup destinationPath] isEqualToString: path])
1050 //------------------------------------------------------------------------------------
1051 // Locates and returns a HBJob whose sequence_id matches a specified value.
1052 //------------------------------------------------------------------------------------
1053 - (HBJob *) findJobWithID: (int)aJobID
1055 HBJobGroup * aJobGroup;
1056 NSEnumerator * groupEnum = [fJobGroups objectEnumerator];
1057 while ( (aJobGroup = [groupEnum nextObject]) )
1060 NSEnumerator * jobEnum = [aJobGroup jobEnumerator];
1061 while ( (job = [jobEnum nextObject]) )
1063 if (job->sequence_id == aJobID)
1070 //------------------------------------------------------------------------------------
1071 // Locates and returns a libhb job whose sequence_id matches a specified value.
1072 //------------------------------------------------------------------------------------
1073 - (hb_job_t *) findLibhbJobWithID: (int)aJobID
1077 while( ( job = hb_job( fHandle, index++ ) ) )
1079 if (job->sequence_id == aJobID)
1086 #pragma mark Queue Counts
1088 //------------------------------------------------------------------------------------
1089 // Sets a flag indicating that the values for fPendingCount, fCompletedCount,
1090 // fCanceledCount, and fWorkingCount need to be recalculated.
1091 //------------------------------------------------------------------------------------
1092 - (void) setJobGroupCountsNeedUpdating: (BOOL)flag
1094 fJobGroupCountsNeedUpdating = flag;
1097 //------------------------------------------------------------------------------------
1098 // Recalculates and stores new values in fPendingCount, fCompletedCount,
1099 // fCanceledCount, and fWorkingCount.
1100 //------------------------------------------------------------------------------------
1101 - (void) recalculateJobGroupCounts
1104 fCompletedCount = 0;
1108 NSEnumerator * groupEnum = [fJobGroups objectEnumerator];
1109 HBJobGroup * aJobGroup;
1110 while ( (aJobGroup = [groupEnum nextObject]) )
1112 switch ([aJobGroup status])
1115 // We don't track these.
1117 case HBStatusPending:
1120 case HBStatusCompleted:
1123 case HBStatusCanceled:
1126 case HBStatusWorking:
1131 fJobGroupCountsNeedUpdating = NO;
1134 //------------------------------------------------------------------------------------
1135 // Returns the number of job groups whose status is HBStatusPending.
1136 //------------------------------------------------------------------------------------
1137 - (unsigned int) pendingCount
1139 if (fJobGroupCountsNeedUpdating)
1140 [self recalculateJobGroupCounts];
1141 return fPendingCount;
1144 //------------------------------------------------------------------------------------
1145 // Returns the number of job groups whose status is HBStatusCompleted.
1146 //------------------------------------------------------------------------------------
1147 - (unsigned int) completedCount
1149 if (fJobGroupCountsNeedUpdating)
1150 [self recalculateJobGroupCounts];
1151 return fCompletedCount;
1154 //------------------------------------------------------------------------------------
1155 // Returns the number of job groups whose status is HBStatusCanceled.
1156 //------------------------------------------------------------------------------------
1157 - (unsigned int) canceledCount
1159 if (fJobGroupCountsNeedUpdating)
1160 [self recalculateJobGroupCounts];
1161 return fCanceledCount;
1164 //------------------------------------------------------------------------------------
1165 // Returns the number of job groups whose status is HBStatusWorking.
1166 //------------------------------------------------------------------------------------
1167 - (unsigned int) workingCount
1169 if (fJobGroupCountsNeedUpdating)
1170 [self recalculateJobGroupCounts];
1171 return fWorkingCount;
1175 #pragma mark UI Updating
1177 //------------------------------------------------------------------------------------
1178 // Saves the state of the items that are currently expanded and selected. Calling
1179 // restoreOutlineViewState will restore the state of all items to match what was saved
1180 // by saveOutlineViewState. Nested calls to saveOutlineViewState are not supported.
1181 //------------------------------------------------------------------------------------
1182 - (void) saveOutlineViewState
1184 if (!fSavedExpandedItems)
1185 fSavedExpandedItems = [[NSMutableIndexSet alloc] init];
1187 [fSavedExpandedItems removeAllIndexes];
1189 // This code stores the sequence_id of the first job of each job group into an
1190 // index set. This is sufficient to identify each group uniquely.
1192 HBJobGroup * aJobGroup;
1193 NSEnumerator * e = [fJobGroups objectEnumerator];
1194 while ( (aJobGroup = [e nextObject]) )
1196 if ([fOutlineView isItemExpanded: aJobGroup])
1197 [fSavedExpandedItems addIndex: [aJobGroup jobAtIndex:0]->sequence_id];
1200 // Save the selection also.
1202 if (!fSavedSelectedItems)
1203 fSavedSelectedItems = [[NSMutableIndexSet alloc] init];
1205 [fSavedSelectedItems removeAllIndexes];
1207 NSIndexSet * selectedRows = [fOutlineView selectedRowIndexes];
1208 NSInteger row = [selectedRows firstIndex];
1209 while (row != NSNotFound)
1211 aJobGroup = [fOutlineView itemAtRow: row];
1212 [fSavedSelectedItems addIndex: [aJobGroup jobAtIndex:0]->sequence_id];
1213 row = [selectedRows indexGreaterThanIndex: row];
1218 //------------------------------------------------------------------------------------
1219 // Restores the expanded state of items in the outline view to match those saved by a
1220 // previous call to saveOutlineViewState.
1221 //------------------------------------------------------------------------------------
1222 - (void) restoreOutlineViewState
1224 if (fSavedExpandedItems)
1226 HBJobGroup * aJobGroup;
1227 NSEnumerator * e = [fJobGroups objectEnumerator];
1228 while ( (aJobGroup = [e nextObject]) )
1230 HBJob * job = [aJobGroup jobAtIndex:0];
1231 if (job && [fSavedExpandedItems containsIndex: job->sequence_id])
1232 [fOutlineView expandItem: aJobGroup];
1236 if (fSavedSelectedItems)
1238 NSMutableIndexSet * rowsToSelect = [[[NSMutableIndexSet alloc] init] autorelease];
1239 HBJobGroup * aJobGroup;
1240 NSEnumerator * e = [fJobGroups objectEnumerator];
1242 while ( (aJobGroup = [e nextObject]) )
1244 HBJob * job = [aJobGroup jobAtIndex:0];
1245 if (job && [fSavedSelectedItems containsIndex: job->sequence_id])
1246 [rowsToSelect addIndex: i];
1249 if ([rowsToSelect count] == 0)
1250 [fOutlineView deselectAll: nil];
1252 [fOutlineView selectRowIndexes:rowsToSelect byExtendingSelection:NO];
1256 //------------------------------------------------------------------------------------
1257 // Marks the icon region of a job group in the queue view as needing display.
1258 //------------------------------------------------------------------------------------
1259 - (void) updateJobGroupIconInQueue:(HBJobGroup*)aJobGroup
1261 NSInteger row = [fOutlineView rowForItem: aJobGroup];
1262 NSInteger col = [fOutlineView columnWithIdentifier: @"icon"];
1263 if (row != -1 && col != -1)
1265 NSRect frame = [fOutlineView frameOfCellAtColumn:col row:row];
1266 [fOutlineView setNeedsDisplayInRect: frame];
1270 //------------------------------------------------------------------------------------
1271 // Marks the entire region of a job group in the queue view as needing display.
1272 //------------------------------------------------------------------------------------
1273 - (void) updateJobGroupInQueue:(HBJobGroup*)aJobGroup
1275 NSInteger row = [fOutlineView rowForItem: aJobGroup];
1278 NSRect frame = [fOutlineView rectOfRow:row];
1279 [fOutlineView setNeedsDisplayInRect: frame];
1283 //------------------------------------------------------------------------------------
1284 // If a job is currently processing, its job icon in the queue outline view is
1285 // animated to its next state.
1286 //------------------------------------------------------------------------------------
1287 - (void) animateCurrentJobGroupInQueue:(NSTimer*)theTimer
1289 if (fCurrentJobGroup)
1292 fAnimationIndex %= 6; // there are 6 animation images; see outlineView:objectValueForTableColumn:byItem: below.
1293 [self updateJobGroupIconInQueue: fCurrentJobGroup];
1297 //------------------------------------------------------------------------------------
1298 // Starts animating the job icon of the currently processing job in the queue outline
1300 //------------------------------------------------------------------------------------
1301 - (void) startAnimatingCurrentJobGroupInQueue
1303 if (!fAnimationTimer)
1304 fAnimationTimer = [[NSTimer scheduledTimerWithTimeInterval:1.0/12.0 // 1/12 because there are 6 images in the animation cycle
1306 selector:@selector(animateCurrentJobGroupInQueue:)
1308 repeats:YES] retain];
1311 //------------------------------------------------------------------------------------
1312 // Stops animating the job icon of the currently processing job in the queue outline
1314 //------------------------------------------------------------------------------------
1315 - (void) stopAnimatingCurrentJobGroupInQueue
1317 if (fAnimationTimer && [fAnimationTimer isValid])
1319 [fAnimationTimer invalidate];
1320 [fAnimationTimer release];
1321 fAnimationTimer = nil;
1325 //------------------------------------------------------------------------------------
1326 // Generate string to display in UI.
1327 //------------------------------------------------------------------------------------
1328 - (NSString *) progressStatusStringForJob: (HBJob *)job state: (hb_state_t *)s
1330 if (s->state == HB_STATE_WORKING)
1333 if (job->pass == -1)
1334 msg = NSLocalizedString( @"Deep Scan", nil );
1335 else if (job->pass == 1)
1336 msg = NSLocalizedString( @"Analyzing video", nil );
1337 else if ((job->pass == 0) || (job->pass == 2))
1338 msg = NSLocalizedString( @"Encoding movie", nil );
1340 return @""; // unknown condition!
1342 if( s->param.working.seconds > -1 )
1344 return [NSString stringWithFormat:
1345 NSLocalizedString( @"%@ (%.2f fps, avg %.2f fps)", nil ),
1346 msg, s->param.working.rate_cur, s->param.working.rate_avg];
1353 else if (s->state == HB_STATE_MUXING)
1354 return NSLocalizedString( @"Muxing", nil );
1356 else if (s->state == HB_STATE_PAUSED)
1357 return NSLocalizedString( @"Paused", nil );
1359 else if (s->state == HB_STATE_WORKDONE)
1360 return NSLocalizedString( @"Done", nil );
1365 //------------------------------------------------------------------------------------
1366 // Generate string to display in UI.
1367 //------------------------------------------------------------------------------------
1368 - (NSString *) progressTimeRemainingStringForJob: (HBJob *)job state: (hb_state_t *)s
1370 if (s->state == HB_STATE_WORKING)
1372 #define p s->param.working
1376 // Minutes always needed
1379 minutes = [NSString stringWithFormat:NSLocalizedString( @"%d minutes ", nil ), p.minutes];
1380 else if (p.minutes == 1)
1381 minutes = NSLocalizedString( @"1 minute ", nil );
1389 hours = [NSString stringWithFormat:NSLocalizedString( @"%d hours ", nil ), p.hours];
1391 hours = NSLocalizedString( @"1 hour ", nil );
1393 return [NSString stringWithFormat:NSLocalizedString( @"%@%@remaining", nil ), hours, minutes];
1400 seconds = [NSString stringWithFormat:NSLocalizedString( @"%d seconds ", nil ), p.seconds];
1402 seconds = NSLocalizedString( @"1 second ", nil );
1404 return [NSString stringWithFormat:NSLocalizedString( @"%@%@remaining", nil ), minutes, seconds];
1407 /* here is code that does it more like the Finder
1408 if( p.seconds > -1 )
1410 float estHours = (p.hours + (p.minutes / 60.0));
1411 float estMinutes = (p.minutes + (p.seconds / 60.0));
1414 return [NSString stringWithFormat:NSLocalizedString( @"Time remaining: About %d hours", nil ), lrintf(estHours)];
1415 else if (estHours > 0.983) // 59 minutes
1416 return NSLocalizedString( @"Time remaining: About 1 hour", nil );
1417 else if (estMinutes > 1.5)
1418 return [NSString stringWithFormat:NSLocalizedString( @"Time remaining: About %d minutes", nil ), lrintf(estMinutes)];
1419 else if (estMinutes > 0.983) // 59 seconds
1420 return NSLocalizedString( @"Time remaining: About 1 minute", nil );
1421 else if (p.seconds <= 5)
1422 return NSLocalizedString( @"Time remaining: Less than 5 seconds", nil );
1423 else if (p.seconds <= 10)
1424 return NSLocalizedString( @"Time remaining: Less than 10 seconds", nil );
1426 return NSLocalizedString( @"Time remaining: Less than 1 minute", nil );
1429 return NSLocalizedString( @"Time remaining: Calculating...", nil );
1437 //------------------------------------------------------------------------------------
1438 // Refresh progress bar (fProgressTextField) from current state.
1439 //------------------------------------------------------------------------------------
1440 - (void) updateProgressTextForJob: (HBJob *)job state: (hb_state_t *)s
1442 NSString * statusMsg = [self progressStatusStringForJob:job state:s];
1443 NSString * timeMsg = [self progressTimeRemainingStringForJob:job state:s];
1444 if ([timeMsg length] > 0)
1445 statusMsg = [NSString stringWithFormat:@"%@ - %@", statusMsg, timeMsg];
1446 [fProgressTextField setStringValue:statusMsg];
1449 //------------------------------------------------------------------------------------
1450 // Refresh progress bar (fProgressBar) from current state.
1451 //------------------------------------------------------------------------------------
1452 - (void) updateProgressBarWithState: (hb_state_t *)s
1454 if (s->state == HB_STATE_WORKING)
1456 #define p s->param.working
1457 [fProgressBar setIndeterminate:NO];
1458 float progress_total = 100.0 * ( p.progress + p.job_cur - 1 ) / p.job_count;
1459 [fProgressBar setDoubleValue:progress_total];
1463 else if (s->state == HB_STATE_MUXING)
1465 #define p s->param.muxing
1466 [fProgressBar setIndeterminate:YES];
1467 [fProgressBar startAnimation:nil];
1471 else if (s->state == HB_STATE_WORKDONE)
1473 [fProgressBar setIndeterminate:NO];
1474 [fProgressBar stopAnimation:nil];
1475 [fProgressBar setDoubleValue:0.0];
1481 [fProgressBar stopAnimation:nil]; // just in case in was animating
1484 //------------------------------------------------------------------------------------
1485 // Refresh queue count text field (fQueueCountField).
1486 //------------------------------------------------------------------------------------
1487 - (void)updateQueueCountField
1490 int jobCount = [fJobGroups count];
1491 int pendingCount = [self pendingCount];
1493 msg = NSLocalizedString(@"No encodes", nil);
1494 else if ((jobCount == 1) && (pendingCount == 0))
1495 msg = NSLocalizedString(@"1 encode", nil);
1496 else if (jobCount == pendingCount) // ie, all jobs listed are pending
1499 msg = NSLocalizedString(@"1 pending encode", nil);
1501 msg = [NSString stringWithFormat:NSLocalizedString(@"%d pending encodes", nil), pendingCount];
1503 else // some completed, some pending
1504 msg = [NSString stringWithFormat:NSLocalizedString(@"%d encodes (%d pending)", nil), jobCount, pendingCount];
1506 [fQueueCountField setStringValue:msg];
1509 //------------------------------------------------------------------------------------
1510 // Refresh the UI in the current job pane. Should be called whenever the current job
1511 // being processed has changed.
1512 //------------------------------------------------------------------------------------
1513 - (void)updateCurrentJobDescription
1517 switch (fCurrentJob->pass)
1519 case -1: // Subtitle scan
1520 [fJobDescTextField setAttributedStringValue:
1521 [fCurrentJob attributedDescriptionWithIcon: NO
1530 withSubtitleInfo: YES]];
1533 case 1: // video 1st pass
1534 [fJobDescTextField setAttributedStringValue:
1535 [fCurrentJob attributedDescriptionWithIcon: NO
1540 withPictureInfo: YES
1544 withSubtitleInfo: NO]];
1547 case 0: // single pass
1548 case 2: // video 2nd pass + audio
1549 [fJobDescTextField setAttributedStringValue:
1550 [fCurrentJob attributedDescriptionWithIcon: NO
1555 withPictureInfo: YES
1559 withSubtitleInfo: YES]];
1563 [fJobDescTextField setAttributedStringValue:
1564 [fCurrentJob attributedDescriptionWithIcon: NO
1569 withPictureInfo: YES
1573 withSubtitleInfo: YES]];
1578 [fJobDescTextField setStringValue: @"No encodes pending"];
1584 //------------------------------------------------------------------------------------
1585 // Refresh the UI in the current job pane. Should be called whenever the current job
1586 // being processed has changed or when progress has changed.
1587 //------------------------------------------------------------------------------------
1588 - (void)updateCurrentJobProgress
1591 hb_get_state2( fHandle, &s );
1592 [self updateProgressTextForJob: fCurrentJob state: &s];
1593 [self updateProgressBarWithState:&s];
1596 //------------------------------------------------------------------------------------
1597 // Notifies HBQueuecontroller that the contents of fJobGroups is about to be modified.
1598 // HBQueuecontroller remembers the state of the UI (selection and expanded items).
1599 //------------------------------------------------------------------------------------
1600 - (void) beginEditingJobGroupsArray
1602 [self saveOutlineViewState];
1605 //------------------------------------------------------------------------------------
1606 // Notifies HBQueuecontroller that modifications to fJobGroups as indicated by a prior
1607 // call to beginEditingJobGroupsArray have been completed. HBQueuecontroller reloads
1608 // the queue view and restores the state of the UI (selection and expanded items).
1609 //------------------------------------------------------------------------------------
1610 - (void) endEditingJobGroupsArray
1612 [self setJobGroupCountsNeedUpdating:YES];
1613 [fOutlineView noteNumberOfRowsChanged];
1614 [fOutlineView reloadData];
1615 [self restoreOutlineViewState];
1616 [self updateQueueCountField];
1620 #pragma mark Actions
1622 //------------------------------------------------------------------------------------
1623 // Deletes the selected jobs from HB and the queue UI
1624 //------------------------------------------------------------------------------------
1625 - (IBAction)removeSelectedJobGroups: (id)sender
1627 if (!fHandle) return;
1629 NSIndexSet * selectedRows = [fOutlineView selectedRowIndexes];
1630 NSInteger row = [selectedRows firstIndex];
1631 if (row != NSNotFound)
1633 [self beginEditingJobGroupsArray];
1634 while (row != NSNotFound)
1636 HBJobGroup * jobGroup = [fOutlineView itemAtRow: row];
1637 switch ([jobGroup status])
1639 case HBStatusCompleted:
1640 case HBStatusCanceled:
1641 [fJobGroups removeObject: jobGroup];
1643 case HBStatusWorking:
1644 [self cancelCurrentJob: sender];
1646 case HBStatusPending:
1647 // Remove from libhb
1649 NSEnumerator * e = [jobGroup jobEnumerator];
1650 while (job = [e nextObject])
1652 hb_job_t * libhbJob = [self findLibhbJobWithID:job->sequence_id];
1654 hb_rem( fHandle, libhbJob );
1656 // Remove from our list
1657 [fJobGroups removeObject: jobGroup];
1663 row = [selectedRows indexGreaterThanIndex: row];
1665 [self endEditingJobGroupsArray];
1669 //------------------------------------------------------------------------------------
1670 // Reveals the file icons in the Finder of the selected job groups.
1671 //------------------------------------------------------------------------------------
1672 - (IBAction)revealSelectedJobGroups: (id)sender
1674 if (!fHandle) return;
1676 NSIndexSet * selectedRows = [fOutlineView selectedRowIndexes];
1677 NSInteger row = [selectedRows firstIndex];
1678 if (row != NSNotFound)
1680 while (row != NSNotFound)
1682 HBJobGroup * jobGroup = [fOutlineView itemAtRow: row];
1683 if ([[jobGroup destinationPath] length])
1684 [[NSWorkspace sharedWorkspace] selectFile:[jobGroup destinationPath] inFileViewerRootedAtPath:nil];
1686 row = [selectedRows indexGreaterThanIndex: row];
1691 //------------------------------------------------------------------------------------
1692 // Calls HBController Cancel: which displays an alert asking user if they want to
1693 // cancel encoding of current job. cancelCurrentJob: returns immediately after posting
1694 // the alert. Later, when the user acknowledges the alert, HBController will call
1695 // libhb to cancel the job.
1696 //------------------------------------------------------------------------------------
1697 - (IBAction)cancelCurrentJob: (id)sender
1699 [fHBController Cancel:sender];
1702 //------------------------------------------------------------------------------------
1703 // Starts or cancels the processing of jobs depending on the current state
1704 //------------------------------------------------------------------------------------
1705 - (IBAction)toggleStartCancel: (id)sender
1707 if (!fHandle) return;
1710 hb_get_state2 (fHandle, &s);
1712 if ((s.state == HB_STATE_PAUSED) || (s.state == HB_STATE_WORKING) || (s.state == HB_STATE_MUXING))
1713 [fHBController Cancel: fQueuePane]; // sender == fQueuePane so that warning alert shows up on queue window
1715 else if ([self pendingCount] > 0)
1716 [fHBController doRip];
1719 //------------------------------------------------------------------------------------
1720 // Toggles the pause/resume state of libhb
1721 //------------------------------------------------------------------------------------
1722 - (IBAction)togglePauseResume: (id)sender
1724 if (!fHandle) return;
1727 hb_get_state2 (fHandle, &s);
1729 if (s.state == HB_STATE_PAUSED)
1730 hb_resume (fHandle);
1731 else if ((s.state == HB_STATE_WORKING) || (s.state == HB_STATE_MUXING))
1736 #pragma mark Synchronizing with libhb
1738 //------------------------------------------------------------------------------------
1739 // Queues a job group. The job group's status is set to HBStatusPending.
1740 //------------------------------------------------------------------------------------
1741 - (void) addJobGroup: (HBJobGroup *) aJobGroup
1743 NSAssert(![fJobGroups containsObject:aJobGroup], @"Duplicate job group");
1744 [aJobGroup setStatus:HBStatusPending];
1746 [self beginEditingJobGroupsArray];
1747 [fJobGroups addObject:aJobGroup];
1748 [self endEditingJobGroupsArray];
1751 //------------------------------------------------------------------------------------
1752 // Notifies HBQueueController that libhb's current job has changed
1753 //------------------------------------------------------------------------------------
1754 - (void)currentJobChanged: (HBJob *) currentJob
1756 /* if the job has a destination path, lets perform finished job notifications in fHBController
1757 * We call this here so that we pickup the last job in the queue and single encodes before fCurrentJob
1758 * is released. So for the first job and the beginning of single encodes we check for the existence
1759 * of a valid fCurrentJob jobGroup
1761 [currentJob retain];
1762 /* We need to compare the job group to determine if this is the end of a job group
1763 * or just the end of a job within a group to keep from sending encode done notification
1764 * after the first pass in a two pass encode
1766 HBJobGroup * theJobGroupCheck = [currentJob jobGroup];
1767 if ((theJobGroupCheck == nil) || (theJobGroupCheck != fCurrentJobGroup))
1769 /* we need to make sure that we are not at the beginning of a queue and also that the job hasn't
1772 if ([[fCurrentJob jobGroup] destinationPath] && [fCurrentJobGroup status] != HBStatusCanceled)
1774 /* send encode messages to fHBController. User prefs are grokked there. */
1775 [fHBController showGrowlDoneNotification: [[fCurrentJob jobGroup] destinationPath]];
1776 [fHBController sendToMetaX: [[fCurrentJob jobGroup] destinationPath]];
1779 [fCurrentJob release];
1780 fCurrentJob = currentJob;
1782 // Log info about the preset name. We do this for each job, since libhb logs each
1783 // job separately. The preset name is found in the job's job group object.
1784 if (fCurrentJob && [fCurrentJob jobGroup] && ([[[fCurrentJob jobGroup] presetName] length] > 0))
1785 [fHBController writeToActivityLog: "Using preset: %s", [[[fCurrentJob jobGroup] presetName] UTF8String]];
1787 // Check to see if this is also a change in Job Group
1789 HBJobGroup * theJobGroup = [currentJob jobGroup];
1790 if ((theJobGroup == nil) || (theJobGroup != fCurrentJobGroup)) // no more job groups or start of a new group
1792 // Previous job has completed
1793 if (fCurrentJobGroup)
1795 // Update the status of the job that just finished. If the user canceled,
1796 // the status will have already been set to canceled by libhbWillStop. So
1797 // all other cases are assumed to be a successful encode. BTW, libhb
1798 // doesn't currently report errors back to the GUI.
1799 if ([fCurrentJobGroup status] != HBStatusCanceled)
1801 [fCurrentJobGroup setStatus:HBStatusCompleted];
1807 // Set the new group
1808 [self setCurrentJobGroup: theJobGroup];
1811 [self updateCurrentJobDescription];
1812 [self updateCurrentJobProgress];
1813 [self showCurrentJobPane: fCurrentJobGroup != nil];
1814 if (fCurrentJobGroup)
1815 [self startAnimatingCurrentJobGroupInQueue];
1817 [self stopAnimatingCurrentJobGroupInQueue];
1820 else // start a new job/pass in the same group
1823 [self updateCurrentJobDescription];
1824 [self updateCurrentJobProgress];
1829 //------------------------------------------------------------------------------------
1830 // Notifies HBQueueController that hb_stop is about to be called. This signals us that
1831 // the current job is going to be canceled and deleted. This is somewhat of a hack to
1832 // let HBQueueController know when a job group has been cancelled. Otherwise, we'd
1833 // have no way of knowing if a job was canceled or completed sucessfully.
1834 //------------------------------------------------------------------------------------
1835 - (void)libhbWillStop
1837 if (fCurrentJobGroup)
1838 [fCurrentJobGroup setStatus: HBStatusCanceled];
1841 //------------------------------------------------------------------------------------
1842 // Notifies HBQueueController that libhb's state has changed
1843 //------------------------------------------------------------------------------------
1844 - (void)libhbStateChanged: (hb_state_t &)state
1846 switch( state.state )
1848 case HB_STATE_WORKING:
1850 //NSLog(@"job = %x; job_cur = %d; job_count = %d", state.param.working.sequence_id, state.param.working.job_cur, state.param.working.job_count);
1851 // First check to see if libhb has moved on to another job. We get no direct
1852 // message when this happens, so we have to detect it ourself. The new job could
1853 // be either just the next job in the current group, or the start of a new group.
1854 if (fCurrentJobID != state.param.working.sequence_id)
1856 fCurrentJobID = state.param.working.sequence_id;
1857 HBJob * currentJob = [self findJobWithID:fCurrentJobID];
1858 [self currentJobChanged: currentJob];
1863 [self updateCurrentJobProgress];
1864 [self startAnimatingCurrentJobGroupInQueue];
1869 case HB_STATE_MUXING:
1871 [self updateCurrentJobProgress];
1875 case HB_STATE_PAUSED:
1877 [self updateCurrentJobProgress];
1878 [self stopAnimatingCurrentJobGroupInQueue];
1882 case HB_STATE_WORKDONE:
1884 // HB_STATE_WORKDONE means that libhb has finished processing all the jobs
1885 // in *its* queue. This message is NOT sent as each individual job is
1888 [self currentJobChanged: nil];
1897 #if HB_OUTLINE_METRIC_CONTROLS
1898 static CGFloat spacingWidth = 3.0;
1899 - (IBAction)imageSpacingChanged: (id)sender;
1901 spacingWidth = [sender floatValue];
1902 [fOutlineView setNeedsDisplay: YES];
1904 - (IBAction)indentChanged: (id)sender
1906 [fOutlineView setIndentationPerLevel: [sender floatValue]];
1907 [fOutlineView setNeedsDisplay: YES];
1913 //------------------------------------------------------------------------------------
1914 // Receives notification whenever an HBJobGroup's status is changed.
1915 //------------------------------------------------------------------------------------
1916 - (void) jobGroupStatusNotification:(NSNotification *)notification
1918 [self setJobGroupCountsNeedUpdating: YES];
1919 // HBQueueJobGroupStatus oldStatus = (HBQueueJobGroupStatus) [[[notification userInfo] objectForKey:@"HBOldJobGroupStatus"] intValue];
1920 HBJobGroup * jobGroup = [notification object];
1922 [self updateJobGroupInQueue:jobGroup];
1923 [self updateQueueCountField];
1928 #pragma mark Toolbar
1930 //------------------------------------------------------------------------------------
1932 //------------------------------------------------------------------------------------
1933 - (void)setupToolbar
1935 // Create a new toolbar instance, and attach it to our window
1936 NSToolbar *toolbar = [[[NSToolbar alloc] initWithIdentifier: HBQueueToolbar] autorelease];
1938 // Set up toolbar properties: Allow customization, give a default display mode, and remember state in user defaults
1939 [toolbar setAllowsUserCustomization: YES];
1940 [toolbar setAutosavesConfiguration: YES];
1941 [toolbar setDisplayMode: NSToolbarDisplayModeIconAndLabel];
1943 // We are the delegate
1944 [toolbar setDelegate: self];
1946 // Attach the toolbar to our window
1947 [fQueueWindow setToolbar: toolbar];
1950 //------------------------------------------------------------------------------------
1951 // toolbar:itemForItemIdentifier:willBeInsertedIntoToolbar:
1952 //------------------------------------------------------------------------------------
1953 - (NSToolbarItem *)toolbar:(NSToolbar *)toolbar
1954 itemForItemIdentifier:(NSString *)itemIdentifier
1955 willBeInsertedIntoToolbar:(BOOL)flag
1957 // Required delegate method: Given an item identifier, this method returns an item.
1958 // The toolbar will use this method to obtain toolbar items that can be displayed
1959 // in the customization sheet, or in the toolbar itself.
1961 NSToolbarItem *toolbarItem = nil;
1963 if ([itemIdentifier isEqual: HBQueueStartCancelToolbarIdentifier])
1965 toolbarItem = [[[NSToolbarItem alloc] initWithItemIdentifier: itemIdentifier] autorelease];
1967 // Set the text label to be displayed in the toolbar and customization palette
1968 [toolbarItem setLabel: @"Start"];
1969 [toolbarItem setPaletteLabel: @"Start/Cancel"];
1971 // Set up a reasonable tooltip, and image
1972 [toolbarItem setToolTip: @"Start Encoding"];
1973 [toolbarItem setImage: [NSImage imageNamed: @"Play"]];
1975 // Tell the item what message to send when it is clicked
1976 [toolbarItem setTarget: self];
1977 [toolbarItem setAction: @selector(toggleStartCancel:)];
1980 if ([itemIdentifier isEqual: HBQueuePauseResumeToolbarIdentifier])
1982 toolbarItem = [[[NSToolbarItem alloc] initWithItemIdentifier: itemIdentifier] autorelease];
1984 // Set the text label to be displayed in the toolbar and customization palette
1985 [toolbarItem setLabel: @"Pause"];
1986 [toolbarItem setPaletteLabel: @"Pause/Resume"];
1988 // Set up a reasonable tooltip, and image
1989 [toolbarItem setToolTip: @"Pause Encoding"];
1990 [toolbarItem setImage: [NSImage imageNamed: @"Pause"]];
1992 // Tell the item what message to send when it is clicked
1993 [toolbarItem setTarget: self];
1994 [toolbarItem setAction: @selector(togglePauseResume:)];
2000 //------------------------------------------------------------------------------------
2001 // toolbarDefaultItemIdentifiers:
2002 //------------------------------------------------------------------------------------
2003 - (NSArray *) toolbarDefaultItemIdentifiers: (NSToolbar *) toolbar
2005 // Required delegate method: Returns the ordered list of items to be shown in the
2006 // toolbar by default.
2008 return [NSArray arrayWithObjects:
2009 HBQueueStartCancelToolbarIdentifier,
2010 HBQueuePauseResumeToolbarIdentifier,
2014 //------------------------------------------------------------------------------------
2015 // toolbarAllowedItemIdentifiers:
2016 //------------------------------------------------------------------------------------
2017 - (NSArray *) toolbarAllowedItemIdentifiers: (NSToolbar *) toolbar
2019 // Required delegate method: Returns the list of all allowed items by identifier.
2020 // By default, the toolbar does not assume any items are allowed, even the
2021 // separator. So, every allowed item must be explicitly listed.
2023 return [NSArray arrayWithObjects:
2024 HBQueueStartCancelToolbarIdentifier,
2025 HBQueuePauseResumeToolbarIdentifier,
2026 NSToolbarCustomizeToolbarItemIdentifier,
2027 NSToolbarFlexibleSpaceItemIdentifier,
2028 NSToolbarSpaceItemIdentifier,
2029 NSToolbarSeparatorItemIdentifier,
2033 //------------------------------------------------------------------------------------
2034 // validateToolbarItem:
2035 //------------------------------------------------------------------------------------
2036 - (BOOL) validateToolbarItem: (NSToolbarItem *) toolbarItem
2038 // Optional method: This message is sent to us since we are the target of some
2039 // toolbar item actions.
2041 if (!fHandle) return NO;
2046 hb_get_state2 (fHandle, &s);
2048 if ([[toolbarItem itemIdentifier] isEqual: HBQueueStartCancelToolbarIdentifier])
2050 if ((s.state == HB_STATE_PAUSED) || (s.state == HB_STATE_WORKING) || (s.state == HB_STATE_MUXING))
2053 [toolbarItem setImage:[NSImage imageNamed: @"Stop"]];
2054 [toolbarItem setLabel: @"Stop"];
2055 [toolbarItem setToolTip: @"Stop Encoding"];
2058 else if ([self pendingCount] > 0)
2061 [toolbarItem setImage:[NSImage imageNamed: @"Play"]];
2062 [toolbarItem setLabel: @"Start"];
2063 [toolbarItem setToolTip: @"Start Encoding"];
2069 [toolbarItem setImage:[NSImage imageNamed: @"Play"]];
2070 [toolbarItem setLabel: @"Start"];
2071 [toolbarItem setToolTip: @"Start Encoding"];
2075 if ([[toolbarItem itemIdentifier] isEqual: HBQueuePauseResumeToolbarIdentifier])
2077 if (s.state == HB_STATE_PAUSED)
2080 [toolbarItem setImage:[NSImage imageNamed: @"Play"]];
2081 [toolbarItem setLabel: @"Resume"];
2082 [toolbarItem setToolTip: @"Resume Encoding"];
2085 else if ((s.state == HB_STATE_WORKING) || (s.state == HB_STATE_MUXING))
2088 [toolbarItem setImage:[NSImage imageNamed: @"Pause"]];
2089 [toolbarItem setLabel: @"Pause"];
2090 [toolbarItem setToolTip: @"Pause Encoding"];
2095 [toolbarItem setImage:[NSImage imageNamed: @"Pause"]];
2096 [toolbarItem setLabel: @"Pause"];
2097 [toolbarItem setToolTip: @"Pause Encoding"];
2106 //------------------------------------------------------------------------------------
2108 //------------------------------------------------------------------------------------
2109 - (void)awakeFromNib
2111 [self setupToolbar];
2113 if (![fQueueWindow setFrameUsingName:@"Queue"])
2114 [fQueueWindow center];
2115 [fQueueWindow setFrameAutosaveName: @"Queue"];
2116 [fQueueWindow setExcludedFromWindowsMenu:YES];
2118 #if HB_QUEUE_DRAGGING
2119 [fOutlineView registerForDraggedTypes: [NSArray arrayWithObject:HBQueuePboardType] ];
2120 [fOutlineView setDraggingSourceOperationMask:NSDragOperationEvery forLocal:YES];
2121 [fOutlineView setVerticalMotionCanBeginDrag: YES];
2124 // Don't allow autoresizing of main column, else the "delete" column will get
2125 // pushed out of view.
2126 [fOutlineView setAutoresizesOutlineColumn: NO];
2128 #if HB_OUTLINE_METRIC_CONTROLS
2129 [fIndentation setHidden: NO];
2130 [fSpacing setHidden: NO];
2131 [fIndentation setIntValue:[fOutlineView indentationPerLevel]]; // debug
2132 [fSpacing setIntValue:3]; // debug
2135 // Show/hide UI elements
2136 fCurrentJobPaneShown = YES; // it's shown in the nib
2137 [self showCurrentJobPane:NO];
2139 [self updateQueueCountField];
2143 //------------------------------------------------------------------------------------
2145 //------------------------------------------------------------------------------------
2146 - (void)windowWillClose:(NSNotification *)aNotification
2148 [[NSUserDefaults standardUserDefaults] setBool:NO forKey:@"QueueWindowIsOpen"];
2153 - (void)moveObjectsInArray:(NSMutableArray *)array fromIndexes:(NSIndexSet *)indexSet toIndex:(NSUInteger)insertIndex
2155 NSUInteger index = [indexSet lastIndex];
2156 NSUInteger aboveInsertIndexCount = 0;
2158 while (index != NSNotFound)
2160 NSUInteger removeIndex;
2162 if (index >= insertIndex)
2164 removeIndex = index + aboveInsertIndexCount;
2165 aboveInsertIndexCount++;
2169 removeIndex = index;
2173 id object = [[array objectAtIndex:removeIndex] retain];
2174 [array removeObjectAtIndex:removeIndex];
2175 [array insertObject:object atIndex:insertIndex];
2178 index = [indexSet indexLessThanIndex:index];
2183 #pragma mark NSOutlineView delegate
2185 - (id)outlineView:(NSOutlineView *)outlineView child:(NSInteger)index ofItem:(id)item
2188 return [fJobGroups objectAtIndex:index];
2190 // We are only one level deep, so we can't be asked about children
2191 NSAssert (NO, @"HBQueueController outlineView:child:ofItem: can't handle nested items.");
2195 - (BOOL)outlineView:(NSOutlineView *)outlineView isItemExpandable:(id)item
2197 // Our outline view has no levels, but we can still expand every item. Doing so
2198 // just makes the row taller. See heightOfRowByItem below.
2202 - (BOOL)outlineView:(NSOutlineView *)outlineView shouldExpandItem:(id)item
2204 // Our outline view has no levels, but we can still expand every item. Doing so
2205 // just makes the row taller. See heightOfRowByItem below.
2206 #if HB_QUEUE_DRAGGING
2207 // Don't autoexpand while dragging, since we can't drop into the items
2208 return ![(HBQueueOutlineView*)outlineView isDragging];
2214 - (NSInteger)outlineView:(NSOutlineView *)outlineView numberOfChildrenOfItem:(id)item
2216 // Our outline view has no levels, so number of children will be zero for all
2219 return [fJobGroups count];
2224 - (void)outlineViewItemDidCollapse:(NSNotification *)notification
2226 id item = [[notification userInfo] objectForKey:@"NSObject"];
2227 NSInteger row = [fOutlineView rowForItem:item];
2228 [fOutlineView noteHeightOfRowsWithIndexesChanged:[NSIndexSet indexSetWithIndexesInRange:NSMakeRange(row,1)]];
2231 - (void)outlineViewItemDidExpand:(NSNotification *)notification
2233 id item = [[notification userInfo] objectForKey:@"NSObject"];
2234 NSInteger row = [fOutlineView rowForItem:item];
2235 [fOutlineView noteHeightOfRowsWithIndexesChanged:[NSIndexSet indexSetWithIndexesInRange:NSMakeRange(row,1)]];
2238 - (CGFloat)outlineView:(NSOutlineView *)outlineView heightOfRowByItem:(id)item
2240 if ([outlineView isItemExpanded: item])
2242 // Short-circuit here if in a live resize primarily to fix a bug but also to
2243 // increase resposivness during a resize. There's a bug in NSTableView that
2244 // causes row heights to get messed up if you try to change them during a live
2245 // resize. So if in a live resize, simply return the previously calculated
2246 // height. The row heights will get fixed up after the resize because we have
2247 // implemented viewDidEndLiveResize to force all of them to be recalculated.
2248 if ([outlineView inLiveResize] && [item lastDescriptionHeight] > 0)
2249 return [item lastDescriptionHeight];
2251 CGFloat width = [[outlineView tableColumnWithIdentifier: @"desc"] width];
2252 // Column width is NOT what is ultimately used. I can't quite figure out what
2253 // width to use for calculating text metrics. No matter how I tweak this value,
2254 // there are a few conditions in which the drawn text extends below the bounds
2255 // of the row cell. In previous versions, which ran under Tiger, I was
2256 // reducing width by 47 pixles.
2257 width -= 2; // (?) for intercell spacing
2259 CGFloat height = [item heightOfDescriptionForWidth: width];
2263 return HB_ROW_HEIGHT_TITLE_ONLY;
2266 - (id)outlineView:(NSOutlineView *)outlineView objectValueForTableColumn:(NSTableColumn *)tableColumn byItem:(id)item
2268 // nb: The "desc" column is currently an HBImageAndTextCell. However, we are longer
2269 // using the image portion of the cell so we could switch back to a regular NSTextFieldCell.
2271 if ([[tableColumn identifier] isEqualToString:@"desc"])
2272 return [item attributedDescription];
2273 else if ([[tableColumn identifier] isEqualToString:@"icon"])
2275 switch ([(HBJobGroup*)item status])
2277 case HBStatusCanceled:
2278 return [NSImage imageNamed:@"EncodeCanceled"];
2280 case HBStatusCompleted:
2281 return [NSImage imageNamed:@"EncodeComplete"];
2283 case HBStatusWorking:
2284 return [NSImage imageNamed: [NSString stringWithFormat: @"EncodeWorking%d", fAnimationIndex]];
2287 return [NSImage imageNamed:@"JobSmall"];
2295 - (void)outlineView:(NSOutlineView *)outlineView willDisplayCell:(id)cell forTableColumn:(NSTableColumn *)tableColumn item:(id)item
2297 if ([[tableColumn identifier] isEqualToString:@"desc"])
2299 #if HB_OUTLINE_METRIC_CONTROLS
2300 NSSize theSize = [cell imageSpacing];
2301 theSize.width = spacingWidth;
2302 [cell setImageSpacing: theSize];
2305 // nb: The "desc" column is currently an HBImageAndTextCell. However, we are longer
2306 // using the image portion of the cell so we could switch back to a regular NSTextFieldCell.
2308 // Set the image here since the value returned from outlineView:objectValueForTableColumn: didn't specify the image part
2309 [cell setImage:nil];
2312 else if ([[tableColumn identifier] isEqualToString:@"action"])
2314 [cell setEnabled: YES];
2315 BOOL highlighted = [outlineView isRowSelected:[outlineView rowForItem: item]] && [[outlineView window] isKeyWindow] && ([[outlineView window] firstResponder] == outlineView);
2316 if ([(HBJobGroup*)item status] == HBStatusCompleted)
2318 [cell setAction: @selector(revealSelectedJobGroups:)];
2321 [cell setImage:[NSImage imageNamed:@"RevealHighlight"]];
2322 [cell setAlternateImage:[NSImage imageNamed:@"RevealHighlightPressed"]];
2325 [cell setImage:[NSImage imageNamed:@"Reveal"]];
2329 [cell setAction: @selector(removeSelectedJobGroups:)];
2332 [cell setImage:[NSImage imageNamed:@"DeleteHighlight"]];
2333 [cell setAlternateImage:[NSImage imageNamed:@"DeleteHighlightPressed"]];
2336 [cell setImage:[NSImage imageNamed:@"Delete"]];
2341 - (void)outlineView:(NSOutlineView *)outlineView willDisplayOutlineCell:(id)cell forTableColumn:(NSTableColumn *)tableColumn item:(id)item
2343 // By default, the discolsure image gets centered vertically in the cell. We want
2344 // always at the top.
2345 if ([outlineView isItemExpanded: item])
2346 [cell setImagePosition: NSImageAbove];
2348 [cell setImagePosition: NSImageOnly];
2352 #pragma mark NSOutlineView delegate (dragging related)
2354 //------------------------------------------------------------------------------------
2355 // NSTableView delegate
2356 //------------------------------------------------------------------------------------
2358 #if HB_QUEUE_DRAGGING
2359 - (BOOL)outlineView:(NSOutlineView *)outlineView writeItems:(NSArray *)items toPasteboard:(NSPasteboard *)pboard
2361 // Dragging is only allowed of the pending items.
2362 NSEnumerator * e = [items objectEnumerator];
2364 while ( (group = [e nextObject]) )
2366 if ([group status] != HBStatusPending)
2370 // Don't retain since this is just holding temporaral drag information, and it is
2371 //only used during a drag! We could put this in the pboard actually.
2372 fDraggedNodes = items;
2374 // Provide data for our custom type, and simple NSStrings.
2375 [pboard declareTypes:[NSArray arrayWithObjects: HBQueuePboardType, nil] owner:self];
2377 // the actual data doesn't matter since DragDropSimplePboardType drags aren't recognized by anyone but us!.
2378 [pboard setData:[NSData data] forType:HBQueuePboardType];
2384 #if HB_QUEUE_DRAGGING
2385 - (NSDragOperation)outlineView:(NSOutlineView *)outlineView validateDrop:(id <NSDraggingInfo>)info proposedItem:(id)item proposedChildIndex:(NSInteger)index
2387 // Don't allow dropping ONTO an item since they can't really contain any children.
2388 BOOL isOnDropTypeProposal = index == NSOutlineViewDropOnItemIndex;
2389 if (isOnDropTypeProposal)
2390 return NSDragOperationNone;
2392 // Don't allow dropping INTO an item since they can't really contain any children.
2395 index = [fOutlineView rowForItem: item] + 1;
2399 // Prevent dragging into the completed or current job.
2400 int firstPendingIndex = [fCompleted count];
2401 if (fCurrentJobGroup)
2402 firstPendingIndex++;
2403 index = MAX (index, firstPendingIndex);
2405 [outlineView setDropItem:item dropChildIndex:index];
2406 return NSDragOperationGeneric;
2410 #if HB_QUEUE_DRAGGING
2411 - (BOOL)outlineView:(NSOutlineView *)outlineView acceptDrop:(id <NSDraggingInfo>)info item:(id)item childIndex:(NSInteger)index
2413 NSMutableIndexSet *moveItems = [NSMutableIndexSet indexSet];
2416 NSEnumerator *enumerator = [fDraggedNodes objectEnumerator];
2417 while (obj = [enumerator nextObject])
2419 [moveItems addIndex:[fJobGroups indexOfObject:obj]];
2422 // Rearrange the data and view
2423 [self saveOutlineViewState];
2424 [self moveObjectsInArray:fJobGroups fromIndexes:moveItems toIndex: index];
2425 [fOutlineView reloadData];
2426 [self restoreOutlineViewState];