3 This file is part of the HandBrake source code.
4 Homepage: <http://handbrake.m0k.org/>.
5 It may be used under the terms of the GNU General Public License. */
7 #include "HBQueueController.h"
8 #include "Controller.h"
9 #import "HBImageAndTextCell.h"
11 #define HB_ROW_HEIGHT_TITLE_ONLY 17.0
13 // Pasteboard type for or drag operations
14 #define HBQueuePboardType @"HBQueuePboardType"
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 memcpy(audio_mixdowns, job->audio_mixdowns, sizeof(audio_mixdowns));
145 acodec = job->acodec;
146 abitrate = job->abitrate;
148 subtitle = job->subtitle;
151 file = [[NSString stringWithUTF8String:job->file] retain];
152 if (job->title->name)
153 titleName = [[NSString stringWithUTF8String:job->title->name] retain];
154 titleIndex = job->title->index;
155 titleWidth = job->title->width;
156 titleHeight = job->title->height;
157 if (job->subtitle >= 0)
159 hb_subtitle_t * aSubtitle = (hb_subtitle_t *) hb_list_item(job->title->list_subtitle, job->subtitle);
161 subtitleLang = [[NSString stringWithUTF8String:aSubtitle->lang] retain];
164 // Calculate and store output dimensions and anamorphic dimensions
165 if (pixel_ratio == 1) // Original PAR Implementation, now called Strict Anamorphic
167 output_width = titleWidth - crop[2] - crop[3];
168 output_height = titleHeight - crop[0] - crop[1];
169 anamorphic_width = output_width * pixel_aspect_width / pixel_aspect_height;
170 anamorphic_height = output_height;
172 else if (pixel_ratio == 2) // Loose Anamorphic
174 // call hb_set_anamorphic_size to do a "dry run" to get the values to be
175 // used by libhb for loose anamorphic.
176 int par_width, par_height;
177 hb_set_anamorphic_size(job, &output_width, &output_height, &par_width, &par_height);
178 anamorphic_width = output_width * par_width / par_height;
179 anamorphic_height = output_height;
181 else // No Anamorphic
183 output_width = width;
184 output_height = height;
185 anamorphic_width = 0; // not needed for this case
186 anamorphic_height = 0; // not needed for this case
195 // jobGroup is a weak reference and does not need to be deleted
199 [subtitleLang release];
203 - (HBJobGroup *) jobGroup
208 - (void) setJobGroup: (HBJobGroup *)aJobGroup
210 // This is a weak reference. We don't retain or release it.
211 jobGroup = aJobGroup;
214 //------------------------------------------------------------------------------------
215 // Generate string to display in UI.
216 //------------------------------------------------------------------------------------
218 - (NSMutableAttributedString *) attributedDescriptionWithIcon: (BOOL)withIcon
219 withTitle: (BOOL)withTitle
220 withPassName: (BOOL)withPassName
221 withFormatInfo: (BOOL)withFormatInfo
222 withDestination: (BOOL)withDestination
223 withPictureInfo: (BOOL)withPictureInfo
224 withVideoInfo: (BOOL)withVideoInfo
225 withx264Info: (BOOL)withx264Info
226 withAudioInfo: (BOOL)withAudioInfo
227 withSubtitleInfo: (BOOL)withSubtitleInfo
230 NSMutableAttributedString * finalString = [[[NSMutableAttributedString alloc] initWithString: @""] autorelease];
233 NSMutableParagraphStyle * ps = [HBJob descriptionParagraphStyle];
234 NSDictionary* detailAttr = [HBJob descriptionDetailAttribute];
235 NSDictionary* detailBoldAttr = [HBJob descriptionDetailBoldAttribute];
236 NSDictionary* titleAttr = [HBJob descriptionTitleAttribute];
237 NSDictionary* shortHeightAttr = [HBJob descriptionShortHeightAttribute];
239 // Title with summary
244 NSFileWrapper * wrapper = [[[NSFileWrapper alloc] initWithPath:[[NSBundle mainBundle] pathForImageResource: @"JobSmall"]] autorelease];
245 NSTextAttachment * imageAttachment = [[[NSTextAttachment alloc] initWithFileWrapper:wrapper] autorelease];
247 NSDictionary* imageAttributes = [NSDictionary dictionaryWithObjectsAndKeys:
248 [NSNumber numberWithFloat: -2.0], NSBaselineOffsetAttributeName,
249 imageAttachment, NSAttachmentAttributeName,
250 ps, NSParagraphStyleAttributeName,
253 NSAttributedString * imageAsString = [[[NSAttributedString alloc]
254 initWithString: [NSString stringWithFormat:@"%C%C", NSAttachmentCharacter, NSTabCharacter]
255 attributes: imageAttributes] autorelease];
257 [finalString appendAttributedString:imageAsString];
260 // Note: use title->name instead of title->dvd since name is just the chosen
261 // folder, instead of dvd which is the full path
262 [finalString appendString:titleName withAttributes:titleAttr];
264 NSString * summaryInfo;
266 NSString * chapterString = (chapter_start == chapter_end) ?
267 [NSString stringWithFormat:@"Chapter %d", chapter_start] :
268 [NSString stringWithFormat:@"Chapters %d through %d", chapter_start, chapter_end];
270 BOOL hasIndepthScan = (pass == -1);
271 int numVideoPasses = 0;
273 // To determine number of video passes, we need to skip past the subtitle scan.
276 // When job is the one currently being processed, then the next in its group
277 // is the the first job in the queue.
278 HBJob * nextjob = nil;
279 unsigned int index = [jobGroup indexOfJob:self];
280 if (index != NSNotFound)
281 nextjob = [jobGroup jobAtIndex:index+1];
282 if (nextjob) // Overly cautious in case there is no next job!
283 numVideoPasses = MIN( 2, nextjob->pass + 1 );
286 numVideoPasses = MIN( 2, pass + 1 );
288 if (hasIndepthScan && numVideoPasses == 1)
289 summaryInfo = [NSString stringWithFormat: @" (Title %d, %@, Deep Scan, Single Video Pass)", titleIndex, chapterString];
290 else if (hasIndepthScan && numVideoPasses > 1)
291 summaryInfo = [NSString stringWithFormat: @" (Title %d, %@, Deep Scan, %d Video Passes)", titleIndex, chapterString, numVideoPasses];
292 else if (numVideoPasses == 1)
293 summaryInfo = [NSString stringWithFormat: @" (Title %d, %@, Single Video Pass)", titleIndex, chapterString];
295 summaryInfo = [NSString stringWithFormat: @" (Title %d, %@, %d Video Passes)", titleIndex, chapterString, numVideoPasses];
297 [finalString appendString:[NSString stringWithFormat:@"%@\n", summaryInfo] withAttributes:detailAttr];
299 // Insert a short-in-height line to put some white space after the title
300 [finalString appendString:@"\n" withAttributes:shortHeightAttr];
303 // End of title stuff
311 NSString * imageName;
314 case -1: imageName = @"JobPassSubtitleSmall"; break;
315 case 0: imageName = @"JobPassFirstSmall"; break;
316 case 1: imageName = @"JobPassFirstSmall"; break;
317 case 2: imageName = @"JobPassSecondSmall"; break;
318 default: imageName = @"JobPassUnknownSmall"; break;
321 NSFileWrapper * wrapper = [[[NSFileWrapper alloc] initWithPath:[[NSBundle mainBundle] pathForImageResource: imageName]] autorelease];
322 NSTextAttachment * imageAttachment = [[[NSTextAttachment alloc] initWithFileWrapper:wrapper] autorelease];
324 NSDictionary* imageAttributes = [NSDictionary dictionaryWithObjectsAndKeys:
325 [NSNumber numberWithFloat: -2.0], NSBaselineOffsetAttributeName,
326 imageAttachment, NSAttachmentAttributeName,
327 ps, NSParagraphStyleAttributeName,
330 NSAttributedString * imageAsString = [[[NSAttributedString alloc]
331 initWithString: [NSString stringWithFormat:@"%C%C", NSAttachmentCharacter, NSTabCharacter]
332 attributes: imageAttributes] autorelease];
334 [finalString appendAttributedString:imageAsString];
337 NSString * jobPassName;
339 jobPassName = NSLocalizedString (@"Deep Scan", nil);
342 int passNum = MAX( 1, pass );
344 jobPassName = NSLocalizedString (@"1st Pass", nil);
345 else if (passNum == 1)
346 jobPassName = NSLocalizedString (@"1st Pass", nil);
347 else if (passNum == 2)
348 jobPassName = NSLocalizedString (@"2nd Pass", nil);
350 jobPassName = [NSString stringWithFormat: NSLocalizedString(@"Pass %d", nil), passNum];
352 [finalString appendString:[NSString stringWithFormat:@"%@\n", jobPassName] withAttributes:detailBoldAttr];
355 // Video Codec needed by FormatInfo and withVideoInfo
356 NSString * jobVideoCodec = nil;
357 if (withFormatInfo || withVideoInfo)
360 // Video Codec settings (Encoder in the gui)
361 if (vcodec == HB_VCODEC_FFMPEG)
362 jobVideoCodec = @"FFmpeg"; // HB_VCODEC_FFMPEG
363 else if (vcodec == HB_VCODEC_XVID)
364 jobVideoCodec = @"XviD"; // HB_VCODEC_XVID
365 else if (vcodec == HB_VCODEC_X264)
367 // Deterimine for sure how we are now setting iPod uuid atom
368 if (h264_level) // We are encoding for iPod
369 jobVideoCodec = @"x264 (H.264 iPod)"; // HB_VCODEC_X264
371 jobVideoCodec = @"x264 (H.264 Main)"; // HB_VCODEC_X264
374 if (jobVideoCodec == nil)
375 jobVideoCodec = @"unknown";
377 // Audio Codec needed by FormatInfo and AudioInfo
378 NSString * jobAudioCodec = nil;
379 if (withFormatInfo || withAudioInfo)
382 jobAudioCodec = @"AAC"; // HB_ACODEC_FAAC
383 else if (acodec == 512)
384 jobAudioCodec = @"MP3"; // HB_ACODEC_LAME
385 else if (acodec == 1024)
386 jobAudioCodec = @"Vorbis"; // HB_ACODEC_VORBIS
387 else if (acodec == 2048)
388 jobAudioCodec = @"AC3"; // HB_ACODEC_AC3
390 if (jobAudioCodec == nil)
391 jobAudioCodec = @"unknown";
396 NSString * jobFormatInfo;
397 // Muxer settings (File Format in the gui)
398 if (mux == 65536 || mux == 131072 || mux == 1048576)
399 jobFormatInfo = @"MP4"; // HB_MUX_MP4,HB_MUX_PSP,HB_MUX_IPOD
400 else if (mux == 262144)
401 jobFormatInfo = @"AVI"; // HB_MUX_AVI
402 else if (mux == 524288)
403 jobFormatInfo = @"OGM"; // HB_MUX_OGM
404 else if (mux == 2097152)
405 jobFormatInfo = @"MKV"; // HB_MUX_MKV
407 jobFormatInfo = @"unknown";
409 if (chapter_markers == 1)
410 jobFormatInfo = [NSString stringWithFormat:@"%@ Container, %@ Video + %@ Audio, Chapter Markers\n", jobFormatInfo, jobVideoCodec, jobAudioCodec];
412 jobFormatInfo = [NSString stringWithFormat:@"%@ Container, %@ Video + %@ Audio\n", jobFormatInfo, jobVideoCodec, jobAudioCodec];
414 [finalString appendString: @"Format: " withAttributes:detailBoldAttr];
415 [finalString appendString: jobFormatInfo withAttributes:detailAttr];
420 [finalString appendString: @"Destination: " withAttributes:detailBoldAttr];
421 [finalString appendString:[NSString stringWithFormat:@"%@\n", file] withAttributes:detailAttr];
427 NSString * jobPictureInfo;
428 if (pixel_ratio == 1) // Original PAR Implementation, now called Strict Anamorphic
429 jobPictureInfo = [NSString stringWithFormat:@"%d x %d (%d x %d Strict Anamorphic)", output_width, output_height, anamorphic_width, anamorphic_height];
430 else if (pixel_ratio == 2) // Loose Anamorphic
431 jobPictureInfo = [NSString stringWithFormat:@"%d x %d (%d x %d Loose Anamorphic)", output_width, output_height, anamorphic_width, anamorphic_height];
433 jobPictureInfo = [NSString stringWithFormat:@"%d x %d", output_width, output_height];
435 jobPictureInfo = [jobPictureInfo stringByAppendingString:@" Keep Aspect Ratio"];
438 jobPictureInfo = [jobPictureInfo stringByAppendingString:@", Grayscale"];
440 if (deinterlace == 1)
441 jobPictureInfo = [jobPictureInfo stringByAppendingString:@", Deinterlace"];
442 if (withIcon) // implies indent the info
443 [finalString appendString: @"\t" withAttributes:detailBoldAttr];
444 [finalString appendString: @"Picture: " withAttributes:detailBoldAttr];
445 [finalString appendString:[NSString stringWithFormat:@"%@\n", jobPictureInfo] withAttributes:detailAttr];
450 NSString * jobVideoQuality;
451 NSString * jobVideoDetail;
453 if (vquality <= 0 || vquality >= 1)
454 jobVideoQuality = [NSString stringWithFormat:@"%d kbps", vbitrate];
457 NSNumber * vidQuality;
458 vidQuality = [NSNumber numberWithInt:vquality * 100];
459 // this is screwed up kind of. Needs to be formatted properly.
461 jobVideoQuality = [NSString stringWithFormat:@"%@%% CRF", vidQuality];
463 jobVideoQuality = [NSString stringWithFormat:@"%@%% CQP", vidQuality];
466 if (vrate_base == 1126125)
469 jobVideoDetail = [NSString stringWithFormat:@"%@, %@, 23.976 fps", jobVideoCodec, jobVideoQuality];
471 else if (vrate_base == 900900)
474 jobVideoDetail = [NSString stringWithFormat:@"%@, %@, 29.97 fps", jobVideoCodec, jobVideoQuality];
479 jobVideoDetail = [NSString stringWithFormat:@"%@, %@, %d fps", jobVideoCodec, jobVideoQuality, vrate / vrate_base];
481 if (withIcon) // implies indent the info
482 [finalString appendString: @"\t" withAttributes:detailBoldAttr];
483 [finalString appendString: @"Video: " withAttributes:detailBoldAttr];
484 [finalString appendString:[NSString stringWithFormat:@"%@\n", jobVideoDetail] withAttributes:detailAttr];
489 if (vcodec == HB_VCODEC_X264 && x264opts)
491 if (withIcon) // implies indent the info
492 [finalString appendString: @"\t" withAttributes:detailBoldAttr];
493 [finalString appendString: @"x264 Options: " withAttributes:detailBoldAttr];
494 [finalString appendString:[NSString stringWithFormat:@"%@\n", x264opts] withAttributes:detailAttr];
500 NSString * jobAudioInfo;
501 if ([jobAudioCodec isEqualToString: @"AC3"])
502 jobAudioInfo = [NSString stringWithFormat:@"%@, Pass-Through", jobAudioCodec];
504 jobAudioInfo = [NSString stringWithFormat:@"%@, %d kbps, %d Hz", jobAudioCodec, abitrate, arate];
506 // we now get the audio mixdown info for each of the two gui audio tracks
507 // lets do it the long way here to get a handle on things.
508 // Hardcoded for two tracks for gui: audio_mixdowns[i] audio_mixdowns[i]
509 int ai; // counter for each audios [] , macgui only allows for two audio tracks currently
510 for( ai = 0; ai < 2; ai++ )
512 if (audio_mixdowns[ai] == HB_AMIXDOWN_MONO)
513 jobAudioInfo = [jobAudioInfo stringByAppendingString:[NSString stringWithFormat:@", Track %d: Mono", ai + 1]];
514 if (audio_mixdowns[ai] == HB_AMIXDOWN_STEREO)
515 jobAudioInfo = [jobAudioInfo stringByAppendingString:[NSString stringWithFormat:@", Track %d: Stereo", ai + 1]];
516 if (audio_mixdowns[ai] == HB_AMIXDOWN_DOLBY)
517 jobAudioInfo = [jobAudioInfo stringByAppendingString:[NSString stringWithFormat:@", Track %d: Dolby Surround", ai + 1]];
518 if (audio_mixdowns[ai] == HB_AMIXDOWN_DOLBYPLII)
519 jobAudioInfo = [jobAudioInfo stringByAppendingString:[NSString stringWithFormat:@", Track %d: Dolby Pro Logic II", ai + 1]];
520 if (audio_mixdowns[ai] == HB_AMIXDOWN_AC3)
521 jobAudioInfo = [jobAudioInfo stringByAppendingString:[NSString stringWithFormat:@", Pass-Through", ai + 1]];
524 if (withIcon) // implies indent the info
525 [finalString appendString: @"\t" withAttributes:detailBoldAttr];
526 [finalString appendString: @"Audio: " withAttributes:detailBoldAttr];
527 [finalString appendString:[NSString stringWithFormat:@"%@\n", jobAudioInfo] withAttributes:detailAttr];
530 if (withSubtitleInfo)
532 // subtitle scan == -1 in two cases:
533 // autoselect: when pass == -1
534 // none: when pass != -1
535 if ((subtitle == -1) && (pass == -1))
537 if (withIcon) // implies indent the info
538 [finalString appendString: @"\t" withAttributes:detailBoldAttr];
539 [finalString appendString: @"Subtitles: " withAttributes:detailBoldAttr];
540 [finalString appendString: @"Autoselect " withAttributes:detailAttr];
542 else if (subtitle >= 0)
546 if (withIcon) // implies indent the info
547 [finalString appendString: @"\t" withAttributes:detailBoldAttr];
548 [finalString appendString: @"Subtitles: " withAttributes:detailBoldAttr];
549 [finalString appendString: subtitleLang withAttributes:detailAttr];
555 if ([[finalString string] hasSuffix: @"\n"])
556 [finalString deleteCharactersInRange: NSMakeRange([[finalString string] length]-1, 1)];
561 + (NSMutableParagraphStyle *) descriptionParagraphStyle
563 if (!_descriptionParagraphStyle)
565 _descriptionParagraphStyle = [[[NSParagraphStyle defaultParagraphStyle] mutableCopy] retain];
566 [_descriptionParagraphStyle setHeadIndent: 40.0];
567 [_descriptionParagraphStyle setParagraphSpacing: 1.0];
568 [_descriptionParagraphStyle setTabStops:[NSArray array]]; // clear all tabs
569 [_descriptionParagraphStyle addTabStop: [[[NSTextTab alloc] initWithType: NSLeftTabStopType location: 20.0] autorelease]];
571 return _descriptionParagraphStyle;
574 + (NSDictionary *) descriptionDetailAttribute
576 if (!_detailAttribute)
577 _detailAttribute = [[NSDictionary dictionaryWithObjectsAndKeys:
578 [NSFont systemFontOfSize:10.0], NSFontAttributeName,
579 _descriptionParagraphStyle, NSParagraphStyleAttributeName,
581 return _detailAttribute;
584 + (NSDictionary *) descriptionDetailBoldAttribute
586 if (!_detailBoldAttribute)
587 _detailBoldAttribute = [[NSDictionary dictionaryWithObjectsAndKeys:
588 [NSFont boldSystemFontOfSize:10.0], NSFontAttributeName,
589 _descriptionParagraphStyle, NSParagraphStyleAttributeName,
591 return _detailBoldAttribute;
594 + (NSDictionary *) descriptionTitleAttribute
596 if (!_titleAttribute)
597 _titleAttribute = [[NSDictionary dictionaryWithObjectsAndKeys:
598 [NSFont systemFontOfSize:[NSFont systemFontSize]], NSFontAttributeName,
599 _descriptionParagraphStyle, NSParagraphStyleAttributeName,
601 return _titleAttribute;
604 + (NSDictionary *) descriptionShortHeightAttribute
606 if (!_shortHeightAttribute)
607 _shortHeightAttribute = [[NSDictionary dictionaryWithObjectsAndKeys:
608 [NSFont systemFontOfSize:2.0], NSFontAttributeName,
610 return _shortHeightAttribute;
618 //------------------------------------------------------------------------------------
620 //------------------------------------------------------------------------------------
622 // Notification sent from HBJobGroup setStatus whenever the status changes.
623 NSString *HBJobGroupStatusNotification = @"HBJobGroupStatusNotification";
625 @implementation HBJobGroup
627 + (HBJobGroup *) jobGroup;
629 return [[[HBJobGroup alloc] init] autorelease];
634 if (self = [super init])
636 fJobs = [[NSMutableArray arrayWithCapacity:0] retain];
637 fDescription = [[NSMutableAttributedString alloc] initWithString: @""];
638 [self setNeedsDescription: NO];
639 fStatus = HBStatusNone;
646 [fPresetName release];
651 - (unsigned int) count
653 return [fJobs count];
656 - (void) addJob: (HBJob *)aJob
658 [aJob setJobGroup:self];
659 [fJobs addObject: aJob];
660 [self setNeedsDescription: YES];
661 fLastDescriptionHeight = 0;
662 fLastDescriptionWidth = 0;
665 - (HBJob *) jobAtIndex: (unsigned)index
667 return [fJobs objectAtIndex: index];
670 - (unsigned) indexOfJob: (HBJob *)aJob;
672 return [fJobs indexOfObject: aJob];
675 - (NSEnumerator *) jobEnumerator
677 return [fJobs objectEnumerator];
680 - (void) setNeedsDescription: (BOOL)flag
682 fNeedsDescription = flag;
685 - (void) updateDescription
687 fNeedsDescription = NO;
689 [fDescription deleteCharactersInRange: NSMakeRange(0, [fDescription length])];
691 if ([self count] == 0)
693 NSAssert(NO, @" jobgroup with no jobs");
697 HBJob * job = [self jobAtIndex:0];
700 [fDescription appendAttributedString: [job attributedDescriptionWithIcon: NO
709 withSubtitleInfo: NO]];
711 // append the preset name
712 if ([fPresetName length])
714 [fDescription appendString:@"Preset: " withAttributes:[HBJob descriptionDetailBoldAttribute]];
715 [fDescription appendString:fPresetName withAttributes:[HBJob descriptionDetailAttribute]];
716 [fDescription appendString:@"\n" withAttributes:[HBJob descriptionDetailAttribute]];
719 // append the format and destinaton
720 [fDescription appendAttributedString: [job attributedDescriptionWithIcon: NO
729 withSubtitleInfo: NO]];
732 static NSAttributedString * carriageReturn = [[NSAttributedString alloc] initWithString:@"\n"];
734 NSEnumerator * e = [self jobEnumerator];
735 while ( (job = [e nextObject]) )
737 int pass = job->pass;
738 [fDescription appendAttributedString:carriageReturn];
739 [fDescription appendAttributedString:
740 [job attributedDescriptionWithIcon: YES
745 withPictureInfo: pass != -1
746 withVideoInfo: pass != -1
747 withx264Info: pass != -1
748 withAudioInfo: pass == 0 || pass == 2
749 withSubtitleInfo: YES]];
754 - (NSMutableAttributedString *) attributedDescription
756 if (fNeedsDescription)
757 [self updateDescription];
761 - (float) heightOfDescriptionForWidth:(float)width
763 // Try to return the cached value if no changes have happened since the last time
764 if ((width == fLastDescriptionWidth) && (fLastDescriptionHeight != 0) && !fNeedsDescription)
765 return fLastDescriptionHeight;
767 if (fNeedsDescription)
768 [self updateDescription];
770 // Calculate the height
771 NSRect bounds = [fDescription boundingRectWithSize:NSMakeSize(width, 10000) options:NSStringDrawingUsesLineFragmentOrigin];
772 fLastDescriptionHeight = bounds.size.height + 6.0; // add some border to bottom
773 fLastDescriptionWidth = width;
774 return fLastDescriptionHeight;
776 /* supposedly another way to do this, in case boundingRectWithSize isn't working
777 NSTextView* tmpView = [[NSTextView alloc] initWithFrame:NSMakeRect(0, 0, width, 1)];
778 [[tmpView textStorage] setAttributedString:aString];
779 [tmpView setHorizontallyResizable:NO];
780 [tmpView setVerticallyResizable:YES];
781 // [[tmpView textContainer] setHeightTracksTextView: YES];
782 // [[tmpView textContainer] setContainerSize: NSMakeSize(width, 10000)];
784 float height = [tmpView frame].size.height;
790 - (float) lastDescriptionHeight
792 return fLastDescriptionHeight;
795 - (void) setStatus: (HBQueueJobGroupStatus)status
797 // Create a dictionary with the old status
798 NSDictionary * userInfo = [NSDictionary dictionaryWithObject:[NSNumber numberWithInt:self->fStatus] forKey:@"HBOldJobGroupStatus"];
800 self->fStatus = status;
802 // Send notification with old status
803 [[NSNotificationCenter defaultCenter] postNotificationName:HBJobGroupStatusNotification object:self userInfo:userInfo];
806 - (HBQueueJobGroupStatus) status
808 return self->fStatus;
811 - (void) setPresetName: (NSString *)name
814 [fPresetName release];
818 - (NSString *) presetName
825 HBJob * firstJob = [self jobAtIndex:0];
826 return firstJob ? firstJob->titleName : nil;
829 - (NSString *) destinationPath
831 HBJob * firstJob = [self jobAtIndex:0];
832 return firstJob ? firstJob->file : nil;
840 // Toolbar identifiers
841 static NSString* HBQueueToolbar = @"HBQueueToolbar1";
842 static NSString* HBQueueStartCancelToolbarIdentifier = @"HBQueueStartCancelToolbarIdentifier";
843 static NSString* HBQueuePauseResumeToolbarIdentifier = @"HBQueuePauseResumeToolbarIdentifier";
847 @implementation HBQueueController
849 //------------------------------------------------------------------------------------
851 //------------------------------------------------------------------------------------
854 if (self = [super init])
857 [[NSUserDefaults standardUserDefaults] registerDefaults:[NSDictionary dictionaryWithObjectsAndKeys:
858 @"NO", @"QueueWindowIsOpen",
859 @"NO", @"QueueShowsDetail",
860 @"YES", @"QueueShowsJobsAsGroups",
863 fJobGroups = [[NSMutableArray arrayWithCapacity:0] retain];
865 BOOL loadSucceeded = [NSBundle loadNibNamed:@"Queue" owner:self] && fQueueWindow;
866 NSAssert(loadSucceeded, @"Could not open Queue nib");
867 NSAssert(fQueueWindow, @"fQueueWindow not found in Queue nib");
869 // Register for HBJobGroup status changes
870 [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(jobGroupStatusNotification:) name:HBJobGroupStatusNotification object:nil];
875 //------------------------------------------------------------------------------------
877 //------------------------------------------------------------------------------------
880 // clear the delegate so that windowWillClose is not attempted
881 if ([fQueueWindow delegate] == self)
882 [fQueueWindow setDelegate:nil];
884 [fJobGroups release];
885 [fCurrentJobGroup release];
886 [fSavedExpandedItems release];
887 [fSavedSelectedItems release];
889 [[NSNotificationCenter defaultCenter] removeObserver:self];
894 //------------------------------------------------------------------------------------
896 //------------------------------------------------------------------------------------
897 - (void)setHandle: (hb_handle_t *)handle
902 //------------------------------------------------------------------------------------
903 // Receive HBController
904 //------------------------------------------------------------------------------------
905 - (void)setHBController: (HBController *)controller
907 fHBController = controller;
911 #pragma mark - Getting the currently processing job group
913 //------------------------------------------------------------------------------------
914 // Returns the HBJobGroup that is currently being encoded; nil if no encoding is
916 //------------------------------------------------------------------------------------
917 - (HBJobGroup *) currentJobGroup;
919 return fCurrentJobGroup;
922 //------------------------------------------------------------------------------------
923 // Returns the HBJob (pass) that is currently being encoded; nil if no encoding is
925 //------------------------------------------------------------------------------------
926 - (HBJob *) currentJob
933 //------------------------------------------------------------------------------------
934 // Displays and brings the queue window to the front
935 //------------------------------------------------------------------------------------
936 - (IBAction) showQueueWindow: (id)sender
938 [fQueueWindow makeKeyAndOrderFront: self];
939 [[NSUserDefaults standardUserDefaults] setBool:YES forKey:@"QueueWindowIsOpen"];
942 //------------------------------------------------------------------------------------
943 // Show or hide the current job pane (fCurrentJobPane).
944 //------------------------------------------------------------------------------------
945 - (void) showCurrentJobPane: (BOOL)showPane
947 if (showPane == fCurrentJobPaneShown)
950 // Things to keep in mind:
951 // - When the current job pane is shown, it occupies the upper portion of the
952 // window with the queue occupying the bottom portion of the window.
953 // - When the current job pane is hidden, it slides up and out of view.
954 // NSView setHidden is NOT used. The queue pane is resized to occupy the full
957 NSRect windowFrame = [[fCurrentJobPane superview] frame];
958 NSRect queueFrame, jobFrame;
960 NSDivideRect(windowFrame, &jobFrame, &queueFrame, NSHeight([fCurrentJobPane frame]), NSMaxYEdge);
963 queueFrame = windowFrame;
964 jobFrame = [fCurrentJobPane frame];
965 jobFrame.origin.y = NSHeight(windowFrame);
968 // Move fCurrentJobPane
969 NSDictionary * dict1 = [NSDictionary dictionaryWithObjectsAndKeys:
970 fCurrentJobPane, NSViewAnimationTargetKey,
971 [NSValue valueWithRect:jobFrame], NSViewAnimationEndFrameKey,
975 NSDictionary * dict2 = [NSDictionary dictionaryWithObjectsAndKeys:
976 fQueuePane, NSViewAnimationTargetKey,
977 [NSValue valueWithRect:queueFrame], NSViewAnimationEndFrameKey,
980 NSViewAnimation * anAnimation = [[[NSViewAnimation alloc] initWithViewAnimations:nil] autorelease];
981 [anAnimation setViewAnimations:[NSArray arrayWithObjects:dict1, dict2, nil]];
982 [anAnimation setDuration:0.25];
983 [anAnimation setAnimationBlockingMode:NSAnimationBlocking]; // prevent user from resizing the window during an animation
984 [anAnimation startAnimation];
986 fCurrentJobPaneShown = showPane;
989 //------------------------------------------------------------------------------------
990 // Sets fCurrentJobGroup to a new job group.
991 //------------------------------------------------------------------------------------
992 - (void) setCurrentJobGroup: (HBJobGroup *)aJobGroup
995 [aJobGroup setStatus: HBStatusWorking];
998 [fCurrentJobGroup release];
999 fCurrentJobGroup = aJobGroup;
1002 #pragma mark - Finding job groups
1004 //------------------------------------------------------------------------------------
1005 // Returns the first pending job with a specified destination path or nil if no such
1007 //------------------------------------------------------------------------------------
1008 - (HBJobGroup *) pendingJobGroupWithDestinationPath: (NSString *)path
1010 HBJobGroup * aJobGroup;
1011 NSEnumerator * groupEnum = [fJobGroups objectEnumerator];
1012 while ( (aJobGroup = [groupEnum nextObject]) )
1014 if ([[aJobGroup destinationPath] isEqualToString: path])
1020 //------------------------------------------------------------------------------------
1021 // Locates and returns a HBJob whose sequence_id matches a specified value.
1022 //------------------------------------------------------------------------------------
1023 - (HBJob *) findJobWithID: (int)aJobID
1025 HBJobGroup * aJobGroup;
1026 NSEnumerator * groupEnum = [fJobGroups objectEnumerator];
1027 while ( (aJobGroup = [groupEnum nextObject]) )
1030 NSEnumerator * jobEnum = [aJobGroup jobEnumerator];
1031 while ( (job = [jobEnum nextObject]) )
1033 if (job->sequence_id == aJobID)
1040 //------------------------------------------------------------------------------------
1041 // Locates and returns a libhb job whose sequence_id matches a specified value.
1042 //------------------------------------------------------------------------------------
1043 - (hb_job_t *) findLibhbJobWithID: (int)aJobID
1047 while( ( job = hb_job( fHandle, index++ ) ) )
1049 if (job->sequence_id == aJobID)
1056 #pragma mark Queue Counts
1058 //------------------------------------------------------------------------------------
1059 // Sets a flag indicating that the values for fPendingCount, fCompletedCount,
1060 // fCanceledCount, and fWorkingCount need to be recalculated.
1061 //------------------------------------------------------------------------------------
1062 - (void) setJobGroupCountsNeedUpdating: (BOOL)flag
1064 fJobGroupCountsNeedUpdating = flag;
1067 //------------------------------------------------------------------------------------
1068 // Recalculates and stores new values in fPendingCount, fCompletedCount,
1069 // fCanceledCount, and fWorkingCount.
1070 //------------------------------------------------------------------------------------
1071 - (void) recalculateJobGroupCounts
1074 fCompletedCount = 0;
1078 NSEnumerator * groupEnum = [fJobGroups objectEnumerator];
1079 HBJobGroup * aJobGroup;
1080 while ( (aJobGroup = [groupEnum nextObject]) )
1082 switch ([aJobGroup status])
1085 // We don't track these.
1087 case HBStatusPending:
1090 case HBStatusCompleted:
1093 case HBStatusCanceled:
1096 case HBStatusWorking:
1101 fJobGroupCountsNeedUpdating = NO;
1104 //------------------------------------------------------------------------------------
1105 // Returns the number of job groups whose status is HBStatusPending.
1106 //------------------------------------------------------------------------------------
1107 - (unsigned int) pendingCount
1109 if (fJobGroupCountsNeedUpdating)
1110 [self recalculateJobGroupCounts];
1111 return fPendingCount;
1114 //------------------------------------------------------------------------------------
1115 // Returns the number of job groups whose status is HBStatusCompleted.
1116 //------------------------------------------------------------------------------------
1117 - (unsigned int) completedCount
1119 if (fJobGroupCountsNeedUpdating)
1120 [self recalculateJobGroupCounts];
1121 return fCompletedCount;
1124 //------------------------------------------------------------------------------------
1125 // Returns the number of job groups whose status is HBStatusCanceled.
1126 //------------------------------------------------------------------------------------
1127 - (unsigned int) canceledCount
1129 if (fJobGroupCountsNeedUpdating)
1130 [self recalculateJobGroupCounts];
1131 return fCanceledCount;
1134 //------------------------------------------------------------------------------------
1135 // Returns the number of job groups whose status is HBStatusWorking.
1136 //------------------------------------------------------------------------------------
1137 - (unsigned int) workingCount
1139 if (fJobGroupCountsNeedUpdating)
1140 [self recalculateJobGroupCounts];
1141 return fWorkingCount;
1145 #pragma mark UI Updating
1147 //------------------------------------------------------------------------------------
1148 // Saves the state of the items that are currently expanded and selected. Calling
1149 // restoreOutlineViewState will restore the state of all items to match what was saved
1150 // by saveOutlineViewState. Nested calls to saveOutlineViewState are not supported.
1151 //------------------------------------------------------------------------------------
1152 - (void) saveOutlineViewState
1154 if (!fSavedExpandedItems)
1155 fSavedExpandedItems = [[NSMutableIndexSet alloc] init];
1157 [fSavedExpandedItems removeAllIndexes];
1159 // This code stores the sequence_id of the first job of each job group into an
1160 // index set. This is sufficient to identify each group uniquely.
1162 HBJobGroup * aJobGroup;
1163 NSEnumerator * e = [fJobGroups objectEnumerator];
1164 while ( (aJobGroup = [e nextObject]) )
1166 if ([fOutlineView isItemExpanded: aJobGroup])
1167 [fSavedExpandedItems addIndex: [aJobGroup jobAtIndex:0]->sequence_id];
1170 // Save the selection also.
1172 if (!fSavedSelectedItems)
1173 fSavedSelectedItems = [[NSMutableIndexSet alloc] init];
1175 [fSavedSelectedItems removeAllIndexes];
1177 NSIndexSet * selectedRows = [fOutlineView selectedRowIndexes];
1178 int row = [selectedRows firstIndex];
1179 while (row != NSNotFound)
1181 aJobGroup = [fOutlineView itemAtRow: row];
1182 [fSavedSelectedItems addIndex: [aJobGroup jobAtIndex:0]->sequence_id];
1183 row = [selectedRows indexGreaterThanIndex: row];
1188 //------------------------------------------------------------------------------------
1189 // Restores the expanded state of items in the outline view to match those saved by a
1190 // previous call to saveOutlineViewState.
1191 //------------------------------------------------------------------------------------
1192 - (void) restoreOutlineViewState
1194 if (fSavedExpandedItems)
1196 HBJobGroup * aJobGroup;
1197 NSEnumerator * e = [fJobGroups objectEnumerator];
1198 while ( (aJobGroup = [e nextObject]) )
1200 HBJob * job = [aJobGroup jobAtIndex:0];
1201 if (job && [fSavedExpandedItems containsIndex: job->sequence_id])
1202 [fOutlineView expandItem: aJobGroup];
1206 if (fSavedSelectedItems)
1208 NSMutableIndexSet * rowsToSelect = [[[NSMutableIndexSet alloc] init] autorelease];
1209 HBJobGroup * aJobGroup;
1210 NSEnumerator * e = [fJobGroups objectEnumerator];
1212 while ( (aJobGroup = [e nextObject]) )
1214 HBJob * job = [aJobGroup jobAtIndex:0];
1215 if (job && [fSavedSelectedItems containsIndex: job->sequence_id])
1216 [rowsToSelect addIndex: i];
1219 if ([rowsToSelect count] == 0)
1220 [fOutlineView deselectAll: nil];
1222 [fOutlineView selectRowIndexes:rowsToSelect byExtendingSelection:NO];
1226 //------------------------------------------------------------------------------------
1227 // Marks the icon region of a job group in the queue view as needing display.
1228 //------------------------------------------------------------------------------------
1229 - (void) updateJobGroupIconInQueue:(HBJobGroup*)aJobGroup
1231 int row = [fOutlineView rowForItem: aJobGroup];
1232 int col = [fOutlineView columnWithIdentifier: @"icon"];
1233 if (row != -1 && col != -1)
1235 NSRect frame = [fOutlineView frameOfCellAtColumn:col row:row];
1236 [fOutlineView setNeedsDisplayInRect: frame];
1240 //------------------------------------------------------------------------------------
1241 // Marks the entire region of a job group in the queue view as needing display.
1242 //------------------------------------------------------------------------------------
1243 - (void) updateJobGroupInQueue:(HBJobGroup*)aJobGroup
1245 int row = [fOutlineView rowForItem: aJobGroup];
1248 NSRect frame = [fOutlineView rectOfRow:row];
1249 [fOutlineView setNeedsDisplayInRect: frame];
1253 //------------------------------------------------------------------------------------
1254 // If a job is currently processing, its job icon in the queue outline view is
1255 // animated to its next state.
1256 //------------------------------------------------------------------------------------
1257 - (void) animateCurrentJobGroupInQueue:(NSTimer*)theTimer
1259 if (fCurrentJobGroup)
1262 fAnimationIndex %= 6; // there are 6 animation images; see outlineView:objectValueForTableColumn:byItem: below.
1263 [self updateJobGroupIconInQueue: fCurrentJobGroup];
1267 //------------------------------------------------------------------------------------
1268 // Starts animating the job icon of the currently processing job in the queue outline
1270 //------------------------------------------------------------------------------------
1271 - (void) startAnimatingCurrentJobGroupInQueue
1273 if (!fAnimationTimer)
1274 fAnimationTimer = [[NSTimer scheduledTimerWithTimeInterval:1.0/12.0 // 1/12 because there are 6 images in the animation cycle
1276 selector:@selector(animateCurrentJobGroupInQueue:)
1278 repeats:YES] retain];
1281 //------------------------------------------------------------------------------------
1282 // Stops animating the job icon of the currently processing job in the queue outline
1284 //------------------------------------------------------------------------------------
1285 - (void) stopAnimatingCurrentJobGroupInQueue
1287 if (fAnimationTimer && [fAnimationTimer isValid])
1289 [fAnimationTimer invalidate];
1290 [fAnimationTimer release];
1291 fAnimationTimer = nil;
1295 //------------------------------------------------------------------------------------
1296 // Generate string to display in UI.
1297 //------------------------------------------------------------------------------------
1298 - (NSString *) progressStatusStringForJob: (HBJob *)job state: (hb_state_t *)s
1300 if (s->state == HB_STATE_WORKING)
1303 if (job->pass == -1)
1304 msg = NSLocalizedString( @"Deep Scan", nil );
1305 else if (job->pass == 1)
1306 msg = NSLocalizedString( @"Analyzing video", nil );
1307 else if ((job->pass == 0) || (job->pass == 2))
1308 msg = NSLocalizedString( @"Encoding movie", nil );
1310 return @""; // unknown condition!
1312 if( s->param.working.seconds > -1 )
1314 return [NSString stringWithFormat:
1315 NSLocalizedString( @"%@ (%.2f fps, avg %.2f fps)", nil ),
1316 msg, s->param.working.rate_cur, s->param.working.rate_avg];
1323 else if (s->state == HB_STATE_MUXING)
1324 return NSLocalizedString( @"Muxing", nil );
1326 else if (s->state == HB_STATE_PAUSED)
1327 return NSLocalizedString( @"Paused", nil );
1329 else if (s->state == HB_STATE_WORKDONE)
1330 return NSLocalizedString( @"Done", nil );
1335 //------------------------------------------------------------------------------------
1336 // Generate string to display in UI.
1337 //------------------------------------------------------------------------------------
1338 - (NSString *) progressTimeRemainingStringForJob: (HBJob *)job state: (hb_state_t *)s
1340 if (s->state == HB_STATE_WORKING)
1342 #define p s->param.working
1346 // Minutes always needed
1349 minutes = [NSString stringWithFormat:NSLocalizedString( @"%d minutes ", nil ), p.minutes];
1350 else if (p.minutes == 1)
1351 minutes = NSLocalizedString( @"1 minute ", nil );
1359 hours = [NSString stringWithFormat:NSLocalizedString( @"%d hours ", nil ), p.hours];
1361 hours = NSLocalizedString( @"1 hour ", nil );
1363 return [NSString stringWithFormat:NSLocalizedString( @"%@%@remaining", nil ), hours, minutes];
1370 seconds = [NSString stringWithFormat:NSLocalizedString( @"%d seconds ", nil ), p.seconds];
1372 seconds = NSLocalizedString( @"1 second ", nil );
1374 return [NSString stringWithFormat:NSLocalizedString( @"%@%@remaining", nil ), minutes, seconds];
1377 /* here is code that does it more like the Finder
1378 if( p.seconds > -1 )
1380 float estHours = (p.hours + (p.minutes / 60.0));
1381 float estMinutes = (p.minutes + (p.seconds / 60.0));
1384 return [NSString stringWithFormat:NSLocalizedString( @"Time remaining: About %d hours", nil ), lrintf(estHours)];
1385 else if (estHours > 0.983) // 59 minutes
1386 return NSLocalizedString( @"Time remaining: About 1 hour", nil );
1387 else if (estMinutes > 1.5)
1388 return [NSString stringWithFormat:NSLocalizedString( @"Time remaining: About %d minutes", nil ), lrintf(estMinutes)];
1389 else if (estMinutes > 0.983) // 59 seconds
1390 return NSLocalizedString( @"Time remaining: About 1 minute", nil );
1391 else if (p.seconds <= 5)
1392 return NSLocalizedString( @"Time remaining: Less than 5 seconds", nil );
1393 else if (p.seconds <= 10)
1394 return NSLocalizedString( @"Time remaining: Less than 10 seconds", nil );
1396 return NSLocalizedString( @"Time remaining: Less than 1 minute", nil );
1399 return NSLocalizedString( @"Time remaining: Calculating...", nil );
1407 //------------------------------------------------------------------------------------
1408 // Refresh progress bar (fProgressTextField) from current state.
1409 //------------------------------------------------------------------------------------
1410 - (void) updateProgressTextForJob: (HBJob *)job state: (hb_state_t *)s
1412 NSString * statusMsg = [self progressStatusStringForJob:job state:s];
1413 NSString * timeMsg = [self progressTimeRemainingStringForJob:job state:s];
1414 if ([timeMsg length] > 0)
1415 statusMsg = [NSString stringWithFormat:@"%@ - %@", statusMsg, timeMsg];
1416 [fProgressTextField setStringValue:statusMsg];
1419 //------------------------------------------------------------------------------------
1420 // Refresh progress bar (fProgressBar) from current state.
1421 //------------------------------------------------------------------------------------
1422 - (void) updateProgressBarWithState: (hb_state_t *)s
1424 if (s->state == HB_STATE_WORKING)
1426 #define p s->param.working
1427 [fProgressBar setIndeterminate:NO];
1428 float progress_total = 100.0 * ( p.progress + p.job_cur - 1 ) / p.job_count;
1429 [fProgressBar setDoubleValue:progress_total];
1433 else if (s->state == HB_STATE_MUXING)
1435 #define p s->param.muxing
1436 [fProgressBar setIndeterminate:YES];
1437 [fProgressBar startAnimation:nil];
1441 else if (s->state == HB_STATE_WORKDONE)
1443 [fProgressBar setIndeterminate:NO];
1444 [fProgressBar stopAnimation:nil];
1445 [fProgressBar setDoubleValue:0.0];
1449 [fProgressBar stopAnimation:nil]; // just in case in was animating
1452 //------------------------------------------------------------------------------------
1453 // Refresh queue count text field (fQueueCountField).
1454 //------------------------------------------------------------------------------------
1455 - (void)updateQueueCountField
1458 int jobCount = [fJobGroups count];
1459 int pendingCount = [self pendingCount];
1461 msg = NSLocalizedString(@"No encodes", nil);
1462 else if ((jobCount == 1) && (pendingCount == 0))
1463 msg = NSLocalizedString(@"1 encode", nil);
1464 else if (jobCount == pendingCount) // ie, all jobs listed are pending
1467 msg = NSLocalizedString(@"1 pending encode", nil);
1469 msg = [NSString stringWithFormat:NSLocalizedString(@"%d pending encodes", nil), pendingCount];
1471 else // some completed, some pending
1472 msg = [NSString stringWithFormat:NSLocalizedString(@"%d encodes (%d pending)", nil), jobCount, pendingCount];
1474 [fQueueCountField setStringValue:msg];
1477 //------------------------------------------------------------------------------------
1478 // Refresh the UI in the current job pane. Should be called whenever the current job
1479 // being processed has changed.
1480 //------------------------------------------------------------------------------------
1481 - (void)updateCurrentJobDescription
1485 switch (fCurrentJob->pass)
1487 case -1: // Subtitle scan
1488 [fJobDescTextField setAttributedStringValue:
1489 [fCurrentJob attributedDescriptionWithIcon: NO
1498 withSubtitleInfo: YES]];
1501 case 1: // video 1st pass
1502 [fJobDescTextField setAttributedStringValue:
1503 [fCurrentJob attributedDescriptionWithIcon: NO
1508 withPictureInfo: YES
1512 withSubtitleInfo: NO]];
1515 case 0: // single pass
1516 case 2: // video 2nd pass + audio
1517 [fJobDescTextField setAttributedStringValue:
1518 [fCurrentJob attributedDescriptionWithIcon: NO
1523 withPictureInfo: YES
1527 withSubtitleInfo: YES]];
1531 [fJobDescTextField setAttributedStringValue:
1532 [fCurrentJob attributedDescriptionWithIcon: NO
1537 withPictureInfo: YES
1541 withSubtitleInfo: YES]];
1546 [fJobDescTextField setStringValue: @"No encodes pending"];
1549 //------------------------------------------------------------------------------------
1550 // Refresh the UI in the current job pane. Should be called whenever the current job
1551 // being processed has changed or when progress has changed.
1552 //------------------------------------------------------------------------------------
1553 - (void)updateCurrentJobProgress
1556 hb_get_state2( fHandle, &s );
1557 [self updateProgressTextForJob: fCurrentJob state: &s];
1558 [self updateProgressBarWithState:&s];
1561 //------------------------------------------------------------------------------------
1562 // Notifies HBQueuecontroller that the contents of fJobGroups is about to be modified.
1563 // HBQueuecontroller remembers the state of the UI (selection and expanded items).
1564 //------------------------------------------------------------------------------------
1565 - (void) beginEditingJobGroupsArray
1567 [self saveOutlineViewState];
1570 //------------------------------------------------------------------------------------
1571 // Notifies HBQueuecontroller that modifications to fJobGroups as indicated by a prior
1572 // call to beginEditingJobGroupsArray have been completed. HBQueuecontroller reloads
1573 // the queue view and restores the state of the UI (selection and expanded items).
1574 //------------------------------------------------------------------------------------
1575 - (void) endEditingJobGroupsArray
1577 [self setJobGroupCountsNeedUpdating:YES];
1578 [fOutlineView noteNumberOfRowsChanged];
1579 [fOutlineView reloadData];
1580 [self restoreOutlineViewState];
1581 [self updateQueueCountField];
1585 #pragma mark Actions
1587 //------------------------------------------------------------------------------------
1588 // Deletes the selected jobs from HB and the queue UI
1589 //------------------------------------------------------------------------------------
1590 - (IBAction)removeSelectedJobGroups: (id)sender
1592 if (!fHandle) return;
1594 NSIndexSet * selectedRows = [fOutlineView selectedRowIndexes];
1595 int row = [selectedRows firstIndex];
1596 if (row != NSNotFound)
1598 [self beginEditingJobGroupsArray];
1599 while (row != NSNotFound)
1601 HBJobGroup * jobGroup = [fOutlineView itemAtRow: row];
1602 switch ([jobGroup status])
1604 case HBStatusCompleted:
1605 case HBStatusCanceled:
1606 [fJobGroups removeObject: jobGroup];
1608 case HBStatusWorking:
1609 [self cancelCurrentJob: sender];
1611 case HBStatusPending:
1612 // Remove from libhb
1614 NSEnumerator * e = [jobGroup jobEnumerator];
1615 while (job = [e nextObject])
1617 hb_job_t * libhbJob = [self findLibhbJobWithID:job->sequence_id];
1619 hb_rem( fHandle, libhbJob );
1621 // Remove from our list
1622 [fJobGroups removeObject: jobGroup];
1628 row = [selectedRows indexGreaterThanIndex: row];
1630 [self endEditingJobGroupsArray];
1634 //------------------------------------------------------------------------------------
1635 // Reveals the file icons in the Finder of the selected job groups.
1636 //------------------------------------------------------------------------------------
1637 - (IBAction)revealSelectedJobGroups: (id)sender
1639 if (!fHandle) return;
1641 NSIndexSet * selectedRows = [fOutlineView selectedRowIndexes];
1642 int row = [selectedRows firstIndex];
1643 if (row != NSNotFound)
1645 while (row != NSNotFound)
1647 HBJobGroup * jobGroup = [fOutlineView itemAtRow: row];
1648 if ([[jobGroup destinationPath] length])
1649 [[NSWorkspace sharedWorkspace] selectFile:[jobGroup destinationPath] inFileViewerRootedAtPath:nil];
1651 row = [selectedRows indexGreaterThanIndex: row];
1656 //------------------------------------------------------------------------------------
1657 // Calls HBController Cancel: which displays an alert asking user if they want to
1658 // cancel encoding of current job. cancelCurrentJob: returns immediately after posting
1659 // the alert. Later, when the user acknowledges the alert, HBController will call
1660 // libhb to cancel the job.
1661 //------------------------------------------------------------------------------------
1662 - (IBAction)cancelCurrentJob: (id)sender
1664 [fHBController Cancel:sender];
1667 //------------------------------------------------------------------------------------
1668 // Starts or cancels the processing of jobs depending on the current state
1669 //------------------------------------------------------------------------------------
1670 - (IBAction)toggleStartCancel: (id)sender
1672 if (!fHandle) return;
1675 hb_get_state2 (fHandle, &s);
1677 if ((s.state == HB_STATE_PAUSED) || (s.state == HB_STATE_WORKING) || (s.state == HB_STATE_MUXING))
1678 [fHBController Cancel: fQueuePane]; // sender == fQueuePane so that warning alert shows up on queue window
1680 else if ([self pendingCount] > 0)
1681 [fHBController doRip];
1684 //------------------------------------------------------------------------------------
1685 // Toggles the pause/resume state of libhb
1686 //------------------------------------------------------------------------------------
1687 - (IBAction)togglePauseResume: (id)sender
1689 if (!fHandle) return;
1692 hb_get_state2 (fHandle, &s);
1694 if (s.state == HB_STATE_PAUSED)
1695 hb_resume (fHandle);
1696 else if ((s.state == HB_STATE_WORKING) || (s.state == HB_STATE_MUXING))
1701 #pragma mark Synchronizing with libhb
1703 //------------------------------------------------------------------------------------
1704 // Queues a job group. The job group's status is set to HBStatusPending.
1705 //------------------------------------------------------------------------------------
1706 - (void) addJobGroup: (HBJobGroup *) aJobGroup
1708 NSAssert(![fJobGroups containsObject:aJobGroup], @"Duplicate job group");
1709 [aJobGroup setStatus:HBStatusPending];
1711 [self beginEditingJobGroupsArray];
1712 [fJobGroups addObject:aJobGroup];
1713 [self endEditingJobGroupsArray];
1716 //------------------------------------------------------------------------------------
1717 // Notifies HBQueueController that libhb's current job has changed
1718 //------------------------------------------------------------------------------------
1719 - (void)currentJobChanged: (HBJob *) currentJob
1721 [currentJob retain];
1722 [fCurrentJob release];
1723 fCurrentJob = currentJob;
1725 // Log info about the preset name. We do this for each job, since libhb logs each
1726 // job separately. The preset name is found in the job's job group object.
1727 if (fCurrentJob && [fCurrentJob jobGroup] && ([[[fCurrentJob jobGroup] presetName] length] > 0))
1728 [fHBController writeToActivityLog: "Using preset: %s", [[[fCurrentJob jobGroup] presetName] UTF8String]];
1730 // Check to see if this is also a change in Job Group
1732 HBJobGroup * theJobGroup = [currentJob jobGroup];
1733 if ((theJobGroup == nil) || (theJobGroup != fCurrentJobGroup)) // no more job groups or start of a new group
1735 // Previous job has completed
1736 if (fCurrentJobGroup)
1738 // Update the status of the job that just finished. If the user canceled,
1739 // the status will have already been set to canceled by libhbWillStop. So
1740 // all other cases are assumed to be a successful encode. BTW, libhb
1741 // doesn't currently report errors back to the GUI.
1742 if ([fCurrentJobGroup status] != HBStatusCanceled)
1743 [fCurrentJobGroup setStatus:HBStatusCompleted];
1746 // Set the new group
1747 [self setCurrentJobGroup: theJobGroup];
1750 [self updateCurrentJobDescription];
1751 [self updateCurrentJobProgress];
1752 [self showCurrentJobPane: fCurrentJobGroup != nil];
1753 if (fCurrentJobGroup)
1754 [self startAnimatingCurrentJobGroupInQueue];
1756 [self stopAnimatingCurrentJobGroupInQueue];
1759 else // start a new job/pass in the same group
1762 [self updateCurrentJobDescription];
1763 [self updateCurrentJobProgress];
1768 //------------------------------------------------------------------------------------
1769 // Notifies HBQueueController that hb_stop is about to be called. This signals us that
1770 // the current job is going to be canceled and deleted. This is somewhat of a hack to
1771 // let HBQueueController know when a job group has been cancelled. Otherwise, we'd
1772 // have no way of knowing if a job was canceled or completed sucessfully.
1773 //------------------------------------------------------------------------------------
1774 - (void)libhbWillStop
1776 if (fCurrentJobGroup)
1777 [fCurrentJobGroup setStatus: HBStatusCanceled];
1780 //------------------------------------------------------------------------------------
1781 // Notifies HBQueueController that libhb's state has changed
1782 //------------------------------------------------------------------------------------
1783 - (void)libhbStateChanged: (hb_state_t &)state
1785 switch( state.state )
1787 case HB_STATE_WORKING:
1789 //NSLog(@"job = %x; job_cur = %d; job_count = %d", state.param.working.sequence_id, state.param.working.job_cur, state.param.working.job_count);
1790 // First check to see if libhb has moved on to another job. We get no direct
1791 // message when this happens, so we have to detect it ourself. The new job could
1792 // be either just the next job in the current group, or the start of a new group.
1793 if (fCurrentJobID != state.param.working.sequence_id)
1795 fCurrentJobID = state.param.working.sequence_id;
1796 HBJob * currentJob = [self findJobWithID:fCurrentJobID];
1797 [self currentJobChanged: currentJob];
1802 [self updateCurrentJobProgress];
1803 [self startAnimatingCurrentJobGroupInQueue];
1808 case HB_STATE_MUXING:
1810 [self updateCurrentJobProgress];
1814 case HB_STATE_PAUSED:
1816 [self updateCurrentJobProgress];
1817 [self stopAnimatingCurrentJobGroupInQueue];
1821 case HB_STATE_WORKDONE:
1823 // HB_STATE_WORKDONE means that libhb has finished processing all the jobs
1824 // in *its* queue. This message is NOT sent as each individual job is
1827 [self currentJobChanged: nil];
1836 #if HB_OUTLINE_METRIC_CONTROLS
1837 static float spacingWidth = 3.0;
1838 - (IBAction)imageSpacingChanged: (id)sender;
1840 spacingWidth = [sender floatValue];
1841 [fOutlineView setNeedsDisplay: YES];
1843 - (IBAction)indentChanged: (id)sender
1845 [fOutlineView setIndentationPerLevel: [sender floatValue]];
1846 [fOutlineView setNeedsDisplay: YES];
1852 //------------------------------------------------------------------------------------
1853 // Receives notification whenever an HBJobGroup's status is changed.
1854 //------------------------------------------------------------------------------------
1855 - (void) jobGroupStatusNotification:(NSNotification *)notification
1857 [self setJobGroupCountsNeedUpdating: YES];
1858 // HBQueueJobGroupStatus oldStatus = (HBQueueJobGroupStatus) [[[notification userInfo] objectForKey:@"HBOldJobGroupStatus"] intValue];
1859 HBJobGroup * jobGroup = [notification object];
1861 [self updateJobGroupInQueue:jobGroup];
1862 [self updateQueueCountField];
1867 #pragma mark Toolbar
1869 //------------------------------------------------------------------------------------
1871 //------------------------------------------------------------------------------------
1872 - (void)setupToolbar
1874 // Create a new toolbar instance, and attach it to our window
1875 NSToolbar *toolbar = [[[NSToolbar alloc] initWithIdentifier: HBQueueToolbar] autorelease];
1877 // Set up toolbar properties: Allow customization, give a default display mode, and remember state in user defaults
1878 [toolbar setAllowsUserCustomization: YES];
1879 [toolbar setAutosavesConfiguration: YES];
1880 [toolbar setDisplayMode: NSToolbarDisplayModeIconAndLabel];
1882 // We are the delegate
1883 [toolbar setDelegate: self];
1885 // Attach the toolbar to our window
1886 [fQueueWindow setToolbar: toolbar];
1889 //------------------------------------------------------------------------------------
1890 // toolbar:itemForItemIdentifier:willBeInsertedIntoToolbar:
1891 //------------------------------------------------------------------------------------
1892 - (NSToolbarItem *)toolbar:(NSToolbar *)toolbar
1893 itemForItemIdentifier:(NSString *)itemIdentifier
1894 willBeInsertedIntoToolbar:(BOOL)flag
1896 // Required delegate method: Given an item identifier, this method returns an item.
1897 // The toolbar will use this method to obtain toolbar items that can be displayed
1898 // in the customization sheet, or in the toolbar itself.
1900 NSToolbarItem *toolbarItem = nil;
1902 if ([itemIdentifier isEqual: HBQueueStartCancelToolbarIdentifier])
1904 toolbarItem = [[[NSToolbarItem alloc] initWithItemIdentifier: itemIdentifier] autorelease];
1906 // Set the text label to be displayed in the toolbar and customization palette
1907 [toolbarItem setLabel: @"Start"];
1908 [toolbarItem setPaletteLabel: @"Start/Cancel"];
1910 // Set up a reasonable tooltip, and image
1911 [toolbarItem setToolTip: @"Start Encoding"];
1912 [toolbarItem setImage: [NSImage imageNamed: @"Play"]];
1914 // Tell the item what message to send when it is clicked
1915 [toolbarItem setTarget: self];
1916 [toolbarItem setAction: @selector(toggleStartCancel:)];
1919 if ([itemIdentifier isEqual: HBQueuePauseResumeToolbarIdentifier])
1921 toolbarItem = [[[NSToolbarItem alloc] initWithItemIdentifier: itemIdentifier] autorelease];
1923 // Set the text label to be displayed in the toolbar and customization palette
1924 [toolbarItem setLabel: @"Pause"];
1925 [toolbarItem setPaletteLabel: @"Pause/Resume"];
1927 // Set up a reasonable tooltip, and image
1928 [toolbarItem setToolTip: @"Pause Encoding"];
1929 [toolbarItem setImage: [NSImage imageNamed: @"Pause"]];
1931 // Tell the item what message to send when it is clicked
1932 [toolbarItem setTarget: self];
1933 [toolbarItem setAction: @selector(togglePauseResume:)];
1939 //------------------------------------------------------------------------------------
1940 // toolbarDefaultItemIdentifiers:
1941 //------------------------------------------------------------------------------------
1942 - (NSArray *) toolbarDefaultItemIdentifiers: (NSToolbar *) toolbar
1944 // Required delegate method: Returns the ordered list of items to be shown in the
1945 // toolbar by default.
1947 return [NSArray arrayWithObjects:
1948 HBQueueStartCancelToolbarIdentifier,
1949 HBQueuePauseResumeToolbarIdentifier,
1953 //------------------------------------------------------------------------------------
1954 // toolbarAllowedItemIdentifiers:
1955 //------------------------------------------------------------------------------------
1956 - (NSArray *) toolbarAllowedItemIdentifiers: (NSToolbar *) toolbar
1958 // Required delegate method: Returns the list of all allowed items by identifier.
1959 // By default, the toolbar does not assume any items are allowed, even the
1960 // separator. So, every allowed item must be explicitly listed.
1962 return [NSArray arrayWithObjects:
1963 HBQueueStartCancelToolbarIdentifier,
1964 HBQueuePauseResumeToolbarIdentifier,
1965 NSToolbarCustomizeToolbarItemIdentifier,
1966 NSToolbarFlexibleSpaceItemIdentifier,
1967 NSToolbarSpaceItemIdentifier,
1968 NSToolbarSeparatorItemIdentifier,
1972 //------------------------------------------------------------------------------------
1973 // validateToolbarItem:
1974 //------------------------------------------------------------------------------------
1975 - (BOOL) validateToolbarItem: (NSToolbarItem *) toolbarItem
1977 // Optional method: This message is sent to us since we are the target of some
1978 // toolbar item actions.
1980 if (!fHandle) return NO;
1985 hb_get_state2 (fHandle, &s);
1987 if ([[toolbarItem itemIdentifier] isEqual: HBQueueStartCancelToolbarIdentifier])
1989 if ((s.state == HB_STATE_PAUSED) || (s.state == HB_STATE_WORKING) || (s.state == HB_STATE_MUXING))
1992 [toolbarItem setImage:[NSImage imageNamed: @"Stop"]];
1993 [toolbarItem setLabel: @"Stop"];
1994 [toolbarItem setToolTip: @"Stop Encoding"];
1997 else if ([self pendingCount] > 0)
2000 [toolbarItem setImage:[NSImage imageNamed: @"Play"]];
2001 [toolbarItem setLabel: @"Start"];
2002 [toolbarItem setToolTip: @"Start Encoding"];
2008 [toolbarItem setImage:[NSImage imageNamed: @"Play"]];
2009 [toolbarItem setLabel: @"Start"];
2010 [toolbarItem setToolTip: @"Start Encoding"];
2014 if ([[toolbarItem itemIdentifier] isEqual: HBQueuePauseResumeToolbarIdentifier])
2016 if (s.state == HB_STATE_PAUSED)
2019 [toolbarItem setImage:[NSImage imageNamed: @"Play"]];
2020 [toolbarItem setLabel: @"Resume"];
2021 [toolbarItem setToolTip: @"Resume Encoding"];
2024 else if ((s.state == HB_STATE_WORKING) || (s.state == HB_STATE_MUXING))
2027 [toolbarItem setImage:[NSImage imageNamed: @"Pause"]];
2028 [toolbarItem setLabel: @"Pause"];
2029 [toolbarItem setToolTip: @"Pause Encoding"];
2034 [toolbarItem setImage:[NSImage imageNamed: @"Pause"]];
2035 [toolbarItem setLabel: @"Pause"];
2036 [toolbarItem setToolTip: @"Pause Encoding"];
2045 //------------------------------------------------------------------------------------
2047 //------------------------------------------------------------------------------------
2048 - (void)awakeFromNib
2050 [self setupToolbar];
2052 if (![fQueueWindow setFrameUsingName:@"Queue"])
2053 [fQueueWindow center];
2054 [fQueueWindow setFrameAutosaveName: @"Queue"];
2055 [fQueueWindow setExcludedFromWindowsMenu:YES];
2057 #if HB_QUEUE_DRAGGING
2058 [fOutlineView registerForDraggedTypes: [NSArray arrayWithObject:HBQueuePboardType] ];
2059 [fOutlineView setDraggingSourceOperationMask:NSDragOperationEvery forLocal:YES];
2060 [fOutlineView setVerticalMotionCanBeginDrag: YES];
2063 // Don't allow autoresizing of main column, else the "delete" column will get
2064 // pushed out of view.
2065 [fOutlineView setAutoresizesOutlineColumn: NO];
2067 #if HB_OUTLINE_METRIC_CONTROLS
2068 [fIndentation setHidden: NO];
2069 [fSpacing setHidden: NO];
2070 [fIndentation setIntValue:[fOutlineView indentationPerLevel]]; // debug
2071 [fSpacing setIntValue:3]; // debug
2074 // Show/hide UI elements
2075 fCurrentJobPaneShown = YES; // it's shown in the nib
2076 [self showCurrentJobPane:NO];
2078 [self updateQueueCountField];
2082 //------------------------------------------------------------------------------------
2084 //------------------------------------------------------------------------------------
2085 - (void)windowWillClose:(NSNotification *)aNotification
2087 [[NSUserDefaults standardUserDefaults] setBool:NO forKey:@"QueueWindowIsOpen"];
2092 - (void)moveObjectsInArray:(NSMutableArray *)array fromIndexes:(NSIndexSet *)indexSet toIndex:(unsigned)insertIndex
2094 unsigned index = [indexSet lastIndex];
2095 unsigned aboveInsertIndexCount = 0;
2097 while (index != NSNotFound)
2099 unsigned removeIndex;
2101 if (index >= insertIndex)
2103 removeIndex = index + aboveInsertIndexCount;
2104 aboveInsertIndexCount++;
2108 removeIndex = index;
2112 id object = [[array objectAtIndex:removeIndex] retain];
2113 [array removeObjectAtIndex:removeIndex];
2114 [array insertObject:object atIndex:insertIndex];
2117 index = [indexSet indexLessThanIndex:index];
2122 #pragma mark NSOutlineView delegate
2124 - (id)outlineView:(NSOutlineView *)outlineView child:(int)index ofItem:(id)item
2127 return [fJobGroups objectAtIndex:index];
2129 // We are only one level deep, so we can't be asked about children
2130 NSAssert (NO, @"HBQueueController outlineView:child:ofItem: can't handle nested items.");
2134 - (BOOL)outlineView:(NSOutlineView *)outlineView isItemExpandable:(id)item
2136 // Our outline view has no levels, but we can still expand every item. Doing so
2137 // just makes the row taller. See heightOfRowByItem below.
2141 - (BOOL)outlineView:(NSOutlineView *)outlineView shouldExpandItem:(id)item
2143 // Our outline view has no levels, but we can still expand every item. Doing so
2144 // just makes the row taller. See heightOfRowByItem below.
2145 #if HB_QUEUE_DRAGGING
2146 // Don't autoexpand while dragging, since we can't drop into the items
2147 return ![(HBQueueOutlineView*)outlineView isDragging];
2153 - (int)outlineView:(NSOutlineView *)outlineView numberOfChildrenOfItem:(id)item
2155 // Our outline view has no levels, so number of children will be zero for all
2158 return [fJobGroups count];
2163 - (void)outlineViewItemDidCollapse:(NSNotification *)notification
2165 id item = [[notification userInfo] objectForKey:@"NSObject"];
2166 int row = [fOutlineView rowForItem:item];
2167 [fOutlineView noteHeightOfRowsWithIndexesChanged:[NSIndexSet indexSetWithIndexesInRange:NSMakeRange(row,1)]];
2170 - (void)outlineViewItemDidExpand:(NSNotification *)notification
2172 id item = [[notification userInfo] objectForKey:@"NSObject"];
2173 int row = [fOutlineView rowForItem:item];
2174 [fOutlineView noteHeightOfRowsWithIndexesChanged:[NSIndexSet indexSetWithIndexesInRange:NSMakeRange(row,1)]];
2177 - (float)outlineView:(NSOutlineView *)outlineView heightOfRowByItem:(id)item
2179 if ([outlineView isItemExpanded: item])
2181 // Short-circuit here if in a live resize primarily to fix a bug but also to
2182 // increase resposivness during a resize. There's a bug in NSTableView that
2183 // causes row heights to get messed up if you try to change them during a live
2184 // resize. So if in a live resize, simply return the previously calculated
2185 // height. The row heights will get fixed up after the resize because we have
2186 // implemented viewDidEndLiveResize to force all of them to be recalculated.
2187 if ([outlineView inLiveResize] && [item lastDescriptionHeight] > 0)
2188 return [item lastDescriptionHeight];
2190 float width = [[outlineView tableColumnWithIdentifier: @"desc"] width];
2191 // Column width is NOT what is ultimately used. I can't quite figure out what
2192 // width to use for calculating text metrics. No matter how I tweak this value,
2193 // there are a few conditions in which the drawn text extends below the bounds
2194 // of the row cell. In previous versions, which ran under Tiger, I was
2195 // reducing width by 47 pixles.
2196 width -= 2; // (?) for intercell spacing
2198 float height = [item heightOfDescriptionForWidth: width];
2202 return HB_ROW_HEIGHT_TITLE_ONLY;
2205 - (id)outlineView:(NSOutlineView *)outlineView objectValueForTableColumn:(NSTableColumn *)tableColumn byItem:(id)item
2207 // nb: The "desc" column is currently an HBImageAndTextCell. However, we are longer
2208 // using the image portion of the cell so we could switch back to a regular NSTextFieldCell.
2210 if ([[tableColumn identifier] isEqualToString:@"desc"])
2211 return [item attributedDescription];
2212 else if ([[tableColumn identifier] isEqualToString:@"icon"])
2214 switch ([(HBJobGroup*)item status])
2216 case HBStatusCanceled:
2217 return [NSImage imageNamed:@"EncodeCanceled"];
2219 case HBStatusCompleted:
2220 return [NSImage imageNamed:@"EncodeComplete"];
2222 case HBStatusWorking:
2223 return [NSImage imageNamed: [NSString stringWithFormat: @"EncodeWorking%d", fAnimationIndex]];
2226 return [NSImage imageNamed:@"JobSmall"];
2234 - (void)outlineView:(NSOutlineView *)outlineView willDisplayCell:(id)cell forTableColumn:(NSTableColumn *)tableColumn item:(id)item
2236 if ([[tableColumn identifier] isEqualToString:@"desc"])
2238 #if HB_OUTLINE_METRIC_CONTROLS
2239 NSSize theSize = [cell imageSpacing];
2240 theSize.width = spacingWidth;
2241 [cell setImageSpacing: theSize];
2244 // nb: The "desc" column is currently an HBImageAndTextCell. However, we are longer
2245 // using the image portion of the cell so we could switch back to a regular NSTextFieldCell.
2247 // Set the image here since the value returned from outlineView:objectValueForTableColumn: didn't specify the image part
2248 [cell setImage:nil];
2251 else if ([[tableColumn identifier] isEqualToString:@"action"])
2253 [cell setEnabled: YES];
2254 BOOL highlighted = [outlineView isRowSelected:[outlineView rowForItem: item]] && [[outlineView window] isKeyWindow] && ([[outlineView window] firstResponder] == outlineView);
2255 if ([(HBJobGroup*)item status] == HBStatusCompleted)
2257 [cell setAction: @selector(revealSelectedJobGroups:)];
2260 [cell setImage:[NSImage imageNamed:@"RevealHighlight"]];
2261 [cell setAlternateImage:[NSImage imageNamed:@"RevealHighlightPressed"]];
2264 [cell setImage:[NSImage imageNamed:@"Reveal"]];
2268 [cell setAction: @selector(removeSelectedJobGroups:)];
2271 [cell setImage:[NSImage imageNamed:@"DeleteHighlight"]];
2272 [cell setAlternateImage:[NSImage imageNamed:@"DeleteHighlightPressed"]];
2275 [cell setImage:[NSImage imageNamed:@"Delete"]];
2280 - (void)outlineView:(NSOutlineView *)outlineView willDisplayOutlineCell:(id)cell forTableColumn:(NSTableColumn *)tableColumn item:(id)item
2282 // By default, the discolsure image gets centered vertically in the cell. We want
2283 // always at the top.
2284 if ([outlineView isItemExpanded: item])
2285 [cell setImagePosition: NSImageAbove];
2287 [cell setImagePosition: NSImageOnly];
2291 #pragma mark NSOutlineView delegate (dragging related)
2293 //------------------------------------------------------------------------------------
2294 // NSTableView delegate
2295 //------------------------------------------------------------------------------------
2297 #if HB_QUEUE_DRAGGING
2298 - (BOOL)outlineView:(NSOutlineView *)outlineView writeItems:(NSArray *)items toPasteboard:(NSPasteboard *)pboard
2300 // Dragging is only allowed of the pending items.
2301 NSEnumerator * e = [items objectEnumerator];
2303 while ( (group = [e nextObject]) )
2305 if ([group status] != HBStatusPending)
2309 // Don't retain since this is just holding temporaral drag information, and it is
2310 //only used during a drag! We could put this in the pboard actually.
2311 fDraggedNodes = items;
2313 // Provide data for our custom type, and simple NSStrings.
2314 [pboard declareTypes:[NSArray arrayWithObjects: HBQueuePboardType, nil] owner:self];
2316 // the actual data doesn't matter since DragDropSimplePboardType drags aren't recognized by anyone but us!.
2317 [pboard setData:[NSData data] forType:HBQueuePboardType];
2323 #if HB_QUEUE_DRAGGING
2324 - (NSDragOperation)outlineView:(NSOutlineView *)outlineView validateDrop:(id <NSDraggingInfo>)info proposedItem:(id)item proposedChildIndex:(int)index
2326 // Don't allow dropping ONTO an item since they can't really contain any children.
2327 BOOL isOnDropTypeProposal = index == NSOutlineViewDropOnItemIndex;
2328 if (isOnDropTypeProposal)
2329 return NSDragOperationNone;
2331 // Don't allow dropping INTO an item since they can't really contain any children.
2334 index = [fOutlineView rowForItem: item] + 1;
2338 // Prevent dragging into the completed or current job.
2339 int firstPendingIndex = [fCompleted count];
2340 if (fCurrentJobGroup)
2341 firstPendingIndex++;
2342 index = MAX (index, firstPendingIndex);
2344 [outlineView setDropItem:item dropChildIndex:index];
2345 return NSDragOperationGeneric;
2349 #if HB_QUEUE_DRAGGING
2350 - (BOOL)outlineView:(NSOutlineView *)outlineView acceptDrop:(id <NSDraggingInfo>)info item:(id)item childIndex:(int)index
2352 NSMutableIndexSet *moveItems = [NSMutableIndexSet indexSet];
2355 NSEnumerator *enumerator = [fDraggedNodes objectEnumerator];
2356 while (obj = [enumerator nextObject])
2358 [moveItems addIndex:[fJobGroups indexOfObject:obj]];
2361 // Rearrange the data and view
2362 [self saveOutlineViewState];
2363 [self moveObjectsInArray:fJobGroups fromIndexes:moveItems toIndex: index];
2364 [fOutlineView reloadData];
2365 [self restoreOutlineViewState];