It may be used under the terms of the GNU General Public License. */
#include "HBQueueController.h"
+#include "Controller.h"
+#import "HBImageAndTextCell.h"
-/**
- * Returns the number of jobs groups in the queue.
- * @param h Handle to hb_handle_t.
- * @return Number of job groups.
- */
-static int hb_group_count(hb_handle_t * h)
+#define HB_ROW_HEIGHT_TITLE_ONLY 17.0
+
+// Pasteboard type for or drag operations
+#define HBQueuePboardType @"HBQueuePboardType"
+
+//------------------------------------------------------------------------------------
+// Job ID Utilities
+//------------------------------------------------------------------------------------
+
+int MakeJobID(int jobGroupID, int sequenceNum)
{
- hb_job_t * job;
- int count = 0;
- int index = 0;
- while( ( job = hb_job( h, index++ ) ) )
- {
- if (job->sequence_id == 0)
- count++;
- }
- return count;
+ return jobGroupID<<16 | sequenceNum;
}
-/**
- * Returns handle to the first job in the i-th group within the job list.
- * @param h Handle to hb_handle_t.
- * @param i Index of group.
- * @returns Handle to hb_job_t of desired job.
- */
-static hb_job_t * hb_group(hb_handle_t * h, int i)
+bool IsFirstPass(int jobID)
{
- hb_job_t * job;
- int count = 0;
- int index = 0;
- while( ( job = hb_job( h, index++ ) ) )
+ return LoWord(jobID) == 0;
+}
+
+//------------------------------------------------------------------------------------
+// NSMutableAttributedString (HBAdditions)
+//------------------------------------------------------------------------------------
+
+@interface NSMutableAttributedString (HBAdditions)
+- (void) appendString: (NSString*)aString withAttributes: (NSDictionary *)aDictionary;
+@end
+
+@implementation NSMutableAttributedString (HBAdditions)
+- (void) appendString: (NSString*)aString withAttributes: (NSDictionary *)aDictionary
+{
+ NSAttributedString * s = [[[NSAttributedString alloc]
+ initWithString: aString
+ attributes: aDictionary] autorelease];
+ [self appendAttributedString: s];
+}
+@end
+
+//------------------------------------------------------------------------------------
+#pragma mark -
+//------------------------------------------------------------------------------------
+
+@implementation HBQueueOutlineView
+
+- (void)viewDidEndLiveResize
+{
+ // Since we disabled calculating row heights during a live resize, force them to
+ // recalculate now.
+ [self noteHeightOfRowsWithIndexesChanged:
+ [NSIndexSet indexSetWithIndexesInRange: NSMakeRange(0, [self numberOfRows])]];
+ [super viewDidEndLiveResize];
+}
+
+#if HB_QUEUE_DRAGGING
+- (NSImage *)dragImageForRowsWithIndexes:(NSIndexSet *)dragRows tableColumns:(NSArray *)tableColumns event:(NSEvent*)dragEvent offset:(NSPointPointer)dragImageOffset
+{
+ // Set the fIsDragging flag so that other's know that a drag operation is being
+ // performed.
+ fIsDragging = YES;
+
+ // By default, NSTableView only drags an image of the first column. Change this to
+ // drag an image of the queue's icon and desc columns.
+ NSArray * cols = [NSArray arrayWithObjects: [self tableColumnWithIdentifier:@"icon"], [self tableColumnWithIdentifier:@"desc"], nil];
+ return [super dragImageForRowsWithIndexes:dragRows tableColumns:cols event:dragEvent offset:dragImageOffset];
+}
+#endif
+
+#if HB_QUEUE_DRAGGING
+- (void) mouseDown:(NSEvent *)theEvent
+{
+ // After a drag operation, reset fIsDragging back to NO. This is really the only way
+ // for us to detect when a drag has finished. You can't do it in acceptDrop because
+ // that won't be called if the dragged item is released outside the view.
+ [super mouseDown:theEvent];
+ fIsDragging = NO;
+}
+#endif
+
+#if HB_QUEUE_DRAGGING
+- (BOOL) isDragging;
+{
+ return fIsDragging;
+}
+#endif
+
+@end
+
+#pragma mark -
+
+//------------------------------------------------------------------------------------
+// HBJob
+//------------------------------------------------------------------------------------
+
+static NSMutableParagraphStyle * _descriptionParagraphStyle = NULL;
+static NSDictionary* _detailAttribute = NULL;
+static NSDictionary* _detailBoldAttribute = NULL;
+static NSDictionary* _titleAttribute = NULL;
+static NSDictionary* _shortHeightAttribute = NULL;
+
+@implementation HBJob
+
++ (HBJob*) jobWithLibhbJob: (hb_job_t *) job
+{
+ return [[[HBJob alloc] initWithLibhbJob:job] autorelease];
+}
+
+- (id) initWithLibhbJob: (hb_job_t *) job
+{
+ if (self = [super init])
{
- if (job->sequence_id == 0)
+ sequence_id = job->sequence_id;
+
+ chapter_start = job->chapter_start;
+ chapter_end = job->chapter_end;
+ chapter_markers = job->chapter_markers;
+ memcpy(crop, job->crop, sizeof(crop));
+ deinterlace = job->deinterlace;
+ width = job->width;
+ height = job->height;
+ keep_ratio = job->keep_ratio;
+ grayscale = job->grayscale;
+ pixel_ratio = job->pixel_ratio;
+ pixel_aspect_width = job->pixel_aspect_width;
+ pixel_aspect_height = job->pixel_aspect_height;
+ vcodec = job->vcodec;
+ vquality = job->vquality;
+ vbitrate = job->vbitrate;
+ vrate = job->vrate;
+ vrate_base = job->vrate_base;
+ pass = job->pass;
+ h264_level = job->h264_level;
+ crf = job->crf;
+ if (job->x264opts)
+ x264opts = [[NSString stringWithUTF8String:job->x264opts] retain];
+ /* So, with the advent of job->list_audio's I decided why not just use an NSString and concatanate
+ all of the info we need for all of the audio values to display right into an NSString here ? So I
+ did. I have no idea why we are reading libhb stuff just to display it in the queue gui. So here we
+ are with a huge string. But its easy to change and saves alot of messing about. Maybe we move a bunch
+ of other display stuff into strings for display for each job. It's not like they have to actually do
+ anything.*/
+ hb_audio_config_t * audio;
+ NSString * thisJobAudioCodecs = [NSString stringWithFormat:@""];
+ NSString * thisJobAudioInfo = [NSString stringWithFormat:@""]; // Setup a simple way to start the string
+ for( int i = 0; i < hb_list_count(job->list_audio); i++ )
{
- if (count == i)
- return job;
- count++;
+ audio = (hb_audio_config_t *) hb_list_audio_config_item( job->list_audio, i );
+ /* Output Codec */
+ NSString *outputCodec;
+ if (audio->out.codec == HB_ACODEC_AC3)
+ outputCodec = @"AC3";
+ else if (audio->out.codec == HB_ACODEC_FAAC)
+ outputCodec = @"AAC";
+ else if (audio->out.codec == HB_ACODEC_LAME)
+ outputCodec = @"MP3";
+ else if (audio->out.codec == HB_ACODEC_VORBIS)
+ outputCodec = @"Vorbis";
+ else
+ outputCodec = @"Unknown Codec";
+ /* Add the codec to the audio codecs list ( We should check against dupes)*/
+ thisJobAudioCodecs = [thisJobAudioCodecs stringByAppendingString:[NSString stringWithFormat:@" %@,",outputCodec]];
+ if (i > 0)
+ {
+ /* Insert a line break so that we get each track on a separate line */
+ /* Wicked HACK alert!!, use 18 whitespaces to align offset in display for list > 2 to offset "Audio" in the queue display
+ Please Fix Me because this is embarrassing (but it works) */
+ thisJobAudioInfo = [thisJobAudioInfo stringByAppendingString:@"\n "];
+ }
+ /* Detailed Job audio track info*/
+ /* Track Number and Mixdown Info */
+ if (audio->out.mixdown == HB_ACODEC_AC3)// Remember for ac3 passthru the mixdown uses the source codec
+ thisJobAudioInfo = [thisJobAudioInfo stringByAppendingString:[NSString stringWithFormat:@" - Track %d: Source: %@ Output: %@, Pass-Thru", i + 1, [NSString stringWithUTF8String:audio->lang.description], outputCodec]];
+ else if (audio->out.mixdown == HB_AMIXDOWN_MONO)
+ thisJobAudioInfo = [thisJobAudioInfo stringByAppendingString:[NSString stringWithFormat:@" - Track %d: Source: %@ Output: %@, Mono", i + 1, [NSString stringWithUTF8String:audio->lang.description], outputCodec]];
+ else if (audio->out.mixdown == HB_AMIXDOWN_STEREO)
+ thisJobAudioInfo = [thisJobAudioInfo stringByAppendingString:[NSString stringWithFormat:@" - Track %d: Source: %@ Output: %@, Stereo", i + 1, [NSString stringWithUTF8String:audio->lang.description], outputCodec]];
+ else if (audio->out.mixdown == HB_AMIXDOWN_DOLBY)
+ thisJobAudioInfo = [thisJobAudioInfo stringByAppendingString:[NSString stringWithFormat:@" - Track %d: Source: %@ Output: %@, Dolby Surround", i + 1, [NSString stringWithUTF8String:audio->lang.description], outputCodec]];
+ else if (audio->out.mixdown == HB_AMIXDOWN_DOLBYPLII)
+ thisJobAudioInfo = [thisJobAudioInfo stringByAppendingString:[NSString stringWithFormat:@" - Track %d: Source: %@ Output: %@, Dolby Pro Logic II", i + 1, [NSString stringWithUTF8String:audio->lang.description], outputCodec]];
+ else if (audio->out.mixdown == HB_AMIXDOWN_6CH)
+ thisJobAudioInfo = [thisJobAudioInfo stringByAppendingString:[NSString stringWithFormat:@" - Track %d: Source: %@ Output: %@, 6 Channel Discreet", i + 1, [NSString stringWithUTF8String:audio->lang.description], outputCodec]];
+ else
+ thisJobAudioInfo = [thisJobAudioInfo stringByAppendingString:[NSString stringWithFormat:@" - Track %d: Source: %@ Output: Unknown Codec Info", i + 1, [NSString stringWithUTF8String:audio->lang.description]]];
+
+ thisJobAudioInfo = [thisJobAudioInfo stringByAppendingString:[NSString stringWithFormat:@", %d kbps, %d Hz", audio->out.bitrate, audio->out.samplerate]];
+
+ }
+ audioinfo_summary = [[NSString stringWithFormat:@"%@",thisJobAudioInfo]retain];
+ audioinfo_codecs = [[NSString stringWithFormat:@"%@",thisJobAudioCodecs]retain];
+
+ subtitle = job->subtitle;
+ mux = job->mux;
+ if (job->file)
+ file = [[NSString stringWithUTF8String:job->file] retain];
+ if (job->title->name)
+ titleName = [[NSString stringWithUTF8String:job->title->name] retain];
+ titleIndex = job->title->index;
+ titleWidth = job->title->width;
+ titleHeight = job->title->height;
+ if (job->subtitle >= 0)
+ {
+ hb_subtitle_t * aSubtitle = (hb_subtitle_t *) hb_list_item(job->title->list_subtitle, job->subtitle);
+ if (aSubtitle)
+ subtitleLang = [[NSString stringWithUTF8String:aSubtitle->lang] retain];
}
+
+ // Calculate and store output dimensions and anamorphic dimensions
+ if (pixel_ratio == 1) // Original PAR Implementation, now called Strict Anamorphic
+ {
+ output_width = titleWidth - crop[2] - crop[3];
+ output_height = titleHeight - crop[0] - crop[1];
+ anamorphic_width = output_width * pixel_aspect_width / pixel_aspect_height;
+ anamorphic_height = output_height;
+ }
+ else if (pixel_ratio == 2) // Loose Anamorphic
+ {
+ // call hb_set_anamorphic_size to do a "dry run" to get the values to be
+ // used by libhb for loose anamorphic.
+ int par_width, par_height;
+ hb_set_anamorphic_size(job, &output_width, &output_height, &par_width, &par_height);
+ anamorphic_width = output_width * par_width / par_height;
+ anamorphic_height = output_height;
+ }
+ else // No Anamorphic
+ {
+ output_width = width;
+ output_height = height;
+ anamorphic_width = 0; // not needed for this case
+ anamorphic_height = 0; // not needed for this case
+ }
+
}
- return NULL;
+ return self;
}
-/**
- * Removes a groups of jobs from the job list.
- * @param h Handle to hb_handle_t.
- * @param job Handle to the first job in the group.
- */
-static void hb_rem_group( hb_handle_t * h, hb_job_t * job )
+- (void) dealloc
{
- // Find job in list
- hb_job_t * j;
- int index = 0;
- while( ( j = hb_job( h, index ) ) )
+ // jobGroup is a weak reference and does not need to be deleted
+ [x264opts release];
+ [file release];
+ [titleName release];
+ [subtitleLang release];
+ [audioinfo_summary release];
+ [audioinfo_codecs release];
+ [super dealloc];
+}
+
+- (HBJobGroup *) jobGroup
+{
+ return jobGroup;
+}
+
+- (void) setJobGroup: (HBJobGroup *)aJobGroup
+{
+ // This is a weak reference. We don't retain or release it.
+ jobGroup = aJobGroup;
+}
+
+//------------------------------------------------------------------------------------
+// Generate string to display in UI.
+//------------------------------------------------------------------------------------
+
+- (NSMutableAttributedString *) attributedDescriptionWithIcon: (BOOL)withIcon
+ withTitle: (BOOL)withTitle
+ withPassName: (BOOL)withPassName
+ withFormatInfo: (BOOL)withFormatInfo
+ withDestination: (BOOL)withDestination
+ withPictureInfo: (BOOL)withPictureInfo
+ withVideoInfo: (BOOL)withVideoInfo
+ withx264Info: (BOOL)withx264Info
+ withAudioInfo: (BOOL)withAudioInfo
+ withSubtitleInfo: (BOOL)withSubtitleInfo
+
+{
+ NSMutableAttributedString * finalString = [[[NSMutableAttributedString alloc] initWithString: @""] autorelease];
+
+ // Attributes
+ NSMutableParagraphStyle * ps = [HBJob descriptionParagraphStyle];
+ NSDictionary* detailAttr = [HBJob descriptionDetailAttribute];
+ NSDictionary* detailBoldAttr = [HBJob descriptionDetailBoldAttribute];
+ NSDictionary* titleAttr = [HBJob descriptionTitleAttribute];
+ NSDictionary* shortHeightAttr = [HBJob descriptionShortHeightAttribute];
+
+ // Title with summary
+ if (withTitle)
{
- if (j == job)
+ if (withIcon)
{
- // Delete this job plus the following ones in the sequence
- hb_rem( h, job );
- while( ( j = hb_job( h, index ) ) && (j->sequence_id != 0) )
- hb_rem( h, j );
- return;
+ NSFileWrapper * wrapper = [[[NSFileWrapper alloc] initWithPath:[[NSBundle mainBundle] pathForImageResource: @"JobSmall"]] autorelease];
+ NSTextAttachment * imageAttachment = [[[NSTextAttachment alloc] initWithFileWrapper:wrapper] autorelease];
+
+ NSDictionary* imageAttributes = [NSDictionary dictionaryWithObjectsAndKeys:
+ [NSNumber numberWithFloat: -2.0], NSBaselineOffsetAttributeName,
+ imageAttachment, NSAttachmentAttributeName,
+ ps, NSParagraphStyleAttributeName,
+ nil];
+
+ NSAttributedString * imageAsString = [[[NSAttributedString alloc]
+ initWithString: [NSString stringWithFormat:@"%C%C", NSAttachmentCharacter, NSTabCharacter]
+ attributes: imageAttributes] autorelease];
+
+ [finalString appendAttributedString:imageAsString];
+ }
+
+ // Note: use title->name instead of title->dvd since name is just the chosen
+ // folder, instead of dvd which is the full path
+ [finalString appendString:titleName withAttributes:titleAttr];
+
+ NSString * summaryInfo;
+
+ NSString * chapterString = (chapter_start == chapter_end) ?
+ [NSString stringWithFormat:@"Chapter %d", chapter_start] :
+ [NSString stringWithFormat:@"Chapters %d through %d", chapter_start, chapter_end];
+
+ BOOL hasIndepthScan = (pass == -1);
+ int numVideoPasses = 0;
+
+ // To determine number of video passes, we need to skip past the subtitle scan.
+ if (hasIndepthScan)
+ {
+ // When job is the one currently being processed, then the next in its group
+ // is the the first job in the queue.
+ HBJob * nextjob = nil;
+ unsigned int index = [jobGroup indexOfJob:self];
+ if (index != NSNotFound)
+ nextjob = [jobGroup jobAtIndex:index+1];
+ if (nextjob) // Overly cautious in case there is no next job!
+ numVideoPasses = MIN( 2, nextjob->pass + 1 );
+ }
+ else
+ numVideoPasses = MIN( 2, pass + 1 );
+
+ if (hasIndepthScan && numVideoPasses == 1)
+ summaryInfo = [NSString stringWithFormat: @" (Title %d, %@, Deep Scan, Single Video Pass)", titleIndex, chapterString];
+ else if (hasIndepthScan && numVideoPasses > 1)
+ summaryInfo = [NSString stringWithFormat: @" (Title %d, %@, Deep Scan, %d Video Passes)", titleIndex, chapterString, numVideoPasses];
+ else if (numVideoPasses == 1)
+ summaryInfo = [NSString stringWithFormat: @" (Title %d, %@, Single Video Pass)", titleIndex, chapterString];
+ else
+ summaryInfo = [NSString stringWithFormat: @" (Title %d, %@, %d Video Passes)", titleIndex, chapterString, numVideoPasses];
+
+ [finalString appendString:[NSString stringWithFormat:@"%@\n", summaryInfo] withAttributes:detailAttr];
+
+ // Insert a short-in-height line to put some white space after the title
+ [finalString appendString:@"\n" withAttributes:shortHeightAttr];
+ }
+
+ // End of title stuff
+
+
+ // Pass Name
+ if (withPassName)
+ {
+ if (withIcon)
+ {
+ NSString * imageName;
+ switch (pass)
+ {
+ case -1: imageName = @"JobPassSubtitleSmall"; break;
+ case 0: imageName = @"JobPassFirstSmall"; break;
+ case 1: imageName = @"JobPassFirstSmall"; break;
+ case 2: imageName = @"JobPassSecondSmall"; break;
+ default: imageName = @"JobPassUnknownSmall"; break;
+ }
+
+ NSFileWrapper * wrapper = [[[NSFileWrapper alloc] initWithPath:[[NSBundle mainBundle] pathForImageResource: imageName]] autorelease];
+ NSTextAttachment * imageAttachment = [[[NSTextAttachment alloc] initWithFileWrapper:wrapper] autorelease];
+
+ NSDictionary* imageAttributes = [NSDictionary dictionaryWithObjectsAndKeys:
+ [NSNumber numberWithFloat: -2.0], NSBaselineOffsetAttributeName,
+ imageAttachment, NSAttachmentAttributeName,
+ ps, NSParagraphStyleAttributeName,
+ nil];
+
+ NSAttributedString * imageAsString = [[[NSAttributedString alloc]
+ initWithString: [NSString stringWithFormat:@"%C%C", NSAttachmentCharacter, NSTabCharacter]
+ attributes: imageAttributes] autorelease];
+
+ [finalString appendAttributedString:imageAsString];
+ }
+
+ NSString * jobPassName;
+ if (pass == -1)
+ jobPassName = NSLocalizedString (@"Deep Scan", nil);
+ else
+ {
+ int passNum = MAX( 1, pass );
+ if (passNum == 0)
+ jobPassName = NSLocalizedString (@"1st Pass", nil);
+ else if (passNum == 1)
+ jobPassName = NSLocalizedString (@"1st Pass", nil);
+ else if (passNum == 2)
+ jobPassName = NSLocalizedString (@"2nd Pass", nil);
+ else
+ jobPassName = [NSString stringWithFormat: NSLocalizedString(@"Pass %d", nil), passNum];
+ }
+ [finalString appendString:[NSString stringWithFormat:@"%@\n", jobPassName] withAttributes:detailBoldAttr];
+ }
+
+ // Video Codec needed by FormatInfo and withVideoInfo
+ NSString * jobVideoCodec = nil;
+ if (withFormatInfo || withVideoInfo)
+ {
+ // 2097152
+ // Video Codec settings (Encoder in the gui)
+ if (vcodec == HB_VCODEC_FFMPEG)
+ jobVideoCodec = @"FFmpeg"; // HB_VCODEC_FFMPEG
+ else if (vcodec == HB_VCODEC_XVID)
+ jobVideoCodec = @"XviD"; // HB_VCODEC_XVID
+ else if (vcodec == HB_VCODEC_X264)
+ {
+ // Deterimine for sure how we are now setting iPod uuid atom
+ if (h264_level) // We are encoding for iPod
+ jobVideoCodec = @"x264 (H.264 iPod)"; // HB_VCODEC_X264
+ else
+ jobVideoCodec = @"x264 (H.264 Main)"; // HB_VCODEC_X264
}
+ }
+ if (jobVideoCodec == nil)
+ jobVideoCodec = @"unknown";
+
+ // Audio Codec needed by FormatInfo and AudioInfo
+ NSString * jobAudioCodec = nil;
+ if (withFormatInfo || withAudioInfo)
+ {
+ if (acodec == 256)
+ jobAudioCodec = @"AAC"; // HB_ACODEC_FAAC
+ else if (acodec == 512)
+ jobAudioCodec = @"MP3"; // HB_ACODEC_LAME
+ else if (acodec == 1024)
+ jobAudioCodec = @"Vorbis"; // HB_ACODEC_VORBIS
+ else if (acodec == 2048)
+ jobAudioCodec = @"AC3"; // HB_ACODEC_AC3
+ }
+ if (jobAudioCodec == nil)
+ jobAudioCodec = @"unknown";
+
+
+ if (withFormatInfo)
+ {
+ NSString * jobFormatInfo;
+ // Muxer settings (File Format in the gui)
+ if (mux == 65536 || mux == 131072 || mux == 1048576)
+ jobFormatInfo = @"MP4"; // HB_MUX_MP4,HB_MUX_PSP,HB_MUX_IPOD
+ else if (mux == 262144)
+ jobFormatInfo = @"AVI"; // HB_MUX_AVI
+ else if (mux == 524288)
+ jobFormatInfo = @"OGM"; // HB_MUX_OGM
+ else if (mux == 2097152)
+ jobFormatInfo = @"MKV"; // HB_MUX_MKV
+ else
+ jobFormatInfo = @"unknown";
+
+ if (chapter_markers == 1)
+ jobFormatInfo = [NSString stringWithFormat:@"%@ Container, %@ Video + %@ Audio, Chapter Markers\n", jobFormatInfo, jobVideoCodec, audioinfo_codecs];
else
- index++;
+ jobFormatInfo = [NSString stringWithFormat:@"%@ Container, %@ Video + %@ Audio\n", jobFormatInfo, jobVideoCodec, audioinfo_codecs];
+
+ [finalString appendString: @"Format: " withAttributes:detailBoldAttr];
+ [finalString appendString: jobFormatInfo withAttributes:detailAttr];
+ }
+
+ if (withDestination)
+ {
+ [finalString appendString: @"Destination: " withAttributes:detailBoldAttr];
+ [finalString appendString:[NSString stringWithFormat:@"%@\n", file] withAttributes:detailAttr];
}
+
+
+ if (withPictureInfo)
+ {
+ NSString * jobPictureInfo;
+ if (pixel_ratio == 1) // Original PAR Implementation, now called Strict Anamorphic
+ jobPictureInfo = [NSString stringWithFormat:@"%d x %d (%d x %d Strict Anamorphic)", output_width, output_height, anamorphic_width, anamorphic_height];
+ else if (pixel_ratio == 2) // Loose Anamorphic
+ jobPictureInfo = [NSString stringWithFormat:@"%d x %d (%d x %d Loose Anamorphic)", output_width, output_height, anamorphic_width, anamorphic_height];
+ else
+ jobPictureInfo = [NSString stringWithFormat:@"%d x %d", output_width, output_height];
+ if (keep_ratio == 1)
+ jobPictureInfo = [jobPictureInfo stringByAppendingString:@" Keep Aspect Ratio"];
+
+ if (grayscale == 1)
+ jobPictureInfo = [jobPictureInfo stringByAppendingString:@", Grayscale"];
+
+ if (deinterlace == 1)
+ jobPictureInfo = [jobPictureInfo stringByAppendingString:@", Deinterlace"];
+ if (withIcon) // implies indent the info
+ [finalString appendString: @"\t" withAttributes:detailBoldAttr];
+ [finalString appendString: @"Picture: " withAttributes:detailBoldAttr];
+ [finalString appendString:[NSString stringWithFormat:@"%@\n", jobPictureInfo] withAttributes:detailAttr];
+ }
+
+ if (withVideoInfo)
+ {
+ NSString * jobVideoQuality;
+ NSString * jobVideoDetail;
+
+ if (vquality <= 0 || vquality >= 1)
+ jobVideoQuality = [NSString stringWithFormat:@"%d kbps", vbitrate];
+ else
+ {
+ NSNumber * vidQuality;
+ vidQuality = [NSNumber numberWithInt:vquality * 100];
+ // this is screwed up kind of. Needs to be formatted properly.
+ if (crf == 1)
+ jobVideoQuality = [NSString stringWithFormat:@"%@%% CRF", vidQuality];
+ else
+ jobVideoQuality = [NSString stringWithFormat:@"%@%% CQP", vidQuality];
+ }
+
+ if (vrate_base == 1126125)
+ {
+ // NTSC FILM 23.976
+ jobVideoDetail = [NSString stringWithFormat:@"%@, %@, 23.976 fps", jobVideoCodec, jobVideoQuality];
+ }
+ else if (vrate_base == 900900)
+ {
+ // NTSC 29.97
+ jobVideoDetail = [NSString stringWithFormat:@"%@, %@, 29.97 fps", jobVideoCodec, jobVideoQuality];
+ }
+ else
+ {
+ // Everything else
+ jobVideoDetail = [NSString stringWithFormat:@"%@, %@, %d fps", jobVideoCodec, jobVideoQuality, vrate / vrate_base];
+ }
+ if (withIcon) // implies indent the info
+ [finalString appendString: @"\t" withAttributes:detailBoldAttr];
+ [finalString appendString: @"Video: " withAttributes:detailBoldAttr];
+ [finalString appendString:[NSString stringWithFormat:@"%@\n", jobVideoDetail] withAttributes:detailAttr];
+ }
+
+ if (withx264Info)
+ {
+ if (vcodec == HB_VCODEC_X264 && x264opts)
+ {
+ if (withIcon) // implies indent the info
+ [finalString appendString: @"\t" withAttributes:detailBoldAttr];
+ [finalString appendString: @"x264 Options: " withAttributes:detailBoldAttr];
+ [finalString appendString:[NSString stringWithFormat:@"%@\n", x264opts] withAttributes:detailAttr];
+ }
+ }
+
+ if (withAudioInfo)
+ {
+ if (withIcon) // implies indent the info
+ [finalString appendString: @"\t" withAttributes:detailBoldAttr];
+ [finalString appendString: @"Audio: " withAttributes:detailBoldAttr];
+ [finalString appendString:[NSString stringWithFormat:@"%@\n", audioinfo_summary] withAttributes:detailAttr];
+ }
+
+ if (withSubtitleInfo)
+ {
+ // subtitle scan == -1 in two cases:
+ // autoselect: when pass == -1
+ // none: when pass != -1
+ if ((subtitle == -1) && (pass == -1))
+ {
+ if (withIcon) // implies indent the info
+ [finalString appendString: @"\t" withAttributes:detailBoldAttr];
+ [finalString appendString: @"Subtitles: " withAttributes:detailBoldAttr];
+ [finalString appendString: @"Autoselect " withAttributes:detailAttr];
+ }
+ else if (subtitle >= 0)
+ {
+ if (subtitleLang)
+ {
+ if (withIcon) // implies indent the info
+ [finalString appendString: @"\t" withAttributes:detailBoldAttr];
+ [finalString appendString: @"Subtitles: " withAttributes:detailBoldAttr];
+ [finalString appendString: subtitleLang withAttributes:detailAttr];
+ }
+ }
+ }
+
+
+ if ([[finalString string] hasSuffix: @"\n"])
+ [finalString deleteCharactersInRange: NSMakeRange([[finalString string] length]-1, 1)];
+
+ return finalString;
}
-/**
- * Returns handle to the next job after the given job.
- * @param h Handle to hb_handle_t.
- * @param job Handle to the a job in the group.
- * @returns Handle to hb_job_t of desired job or NULL if no such job.
- */
-static hb_job_t * hb_next_job( hb_handle_t * h, hb_job_t * job )
++ (NSMutableParagraphStyle *) descriptionParagraphStyle
{
- hb_job_t * j = NULL;
- int index = 0;
- while( ( j = hb_job( h, index++ ) ) )
+ if (!_descriptionParagraphStyle)
{
- if (j == job)
- return hb_job( h, index+1 );
+ _descriptionParagraphStyle = [[[NSParagraphStyle defaultParagraphStyle] mutableCopy] retain];
+ [_descriptionParagraphStyle setHeadIndent: 40.0];
+ [_descriptionParagraphStyle setParagraphSpacing: 1.0];
+ [_descriptionParagraphStyle setTabStops:[NSArray array]]; // clear all tabs
+ [_descriptionParagraphStyle addTabStop: [[[NSTextTab alloc] initWithType: NSLeftTabStopType location: 20.0] autorelease]];
}
- return NULL;
+ return _descriptionParagraphStyle;
+}
+
++ (NSDictionary *) descriptionDetailAttribute
+{
+ if (!_detailAttribute)
+ _detailAttribute = [[NSDictionary dictionaryWithObjectsAndKeys:
+ [NSFont systemFontOfSize:10.0], NSFontAttributeName,
+ _descriptionParagraphStyle, NSParagraphStyleAttributeName,
+ nil] retain];
+ return _detailAttribute;
+}
+
++ (NSDictionary *) descriptionDetailBoldAttribute
+{
+ if (!_detailBoldAttribute)
+ _detailBoldAttribute = [[NSDictionary dictionaryWithObjectsAndKeys:
+ [NSFont boldSystemFontOfSize:10.0], NSFontAttributeName,
+ _descriptionParagraphStyle, NSParagraphStyleAttributeName,
+ nil] retain];
+ return _detailBoldAttribute;
}
++ (NSDictionary *) descriptionTitleAttribute
+{
+ if (!_titleAttribute)
+ _titleAttribute = [[NSDictionary dictionaryWithObjectsAndKeys:
+ [NSFont systemFontOfSize:[NSFont systemFontSize]], NSFontAttributeName,
+ _descriptionParagraphStyle, NSParagraphStyleAttributeName,
+ nil] retain];
+ return _titleAttribute;
+}
+
++ (NSDictionary *) descriptionShortHeightAttribute
+{
+ if (!_shortHeightAttribute)
+ _shortHeightAttribute = [[NSDictionary dictionaryWithObjectsAndKeys:
+ [NSFont systemFontOfSize:2.0], NSFontAttributeName,
+ nil] retain];
+ return _shortHeightAttribute;
+}
+
+
+@end
+
+#pragma mark -
+
+//------------------------------------------------------------------------------------
+// HBJobGroup
+//------------------------------------------------------------------------------------
+
+// Notification sent from HBJobGroup setStatus whenever the status changes.
+NSString *HBJobGroupStatusNotification = @"HBJobGroupStatusNotification";
+
+@implementation HBJobGroup
+
++ (HBJobGroup *) jobGroup;
+{
+ return [[[HBJobGroup alloc] init] autorelease];
+}
+
+- (id) init
+{
+ if (self = [super init])
+ {
+ fJobs = [[NSMutableArray arrayWithCapacity:0] retain];
+ fDescription = [[NSMutableAttributedString alloc] initWithString: @""];
+ [self setNeedsDescription: NO];
+ fStatus = HBStatusNone;
+ }
+ return self;
+}
+
+- (void) dealloc
+{
+ [fPresetName release];
+ [fJobs release];
+ [super dealloc];
+}
+
+- (unsigned int) count
+{
+ return [fJobs count];
+}
+
+- (void) addJob: (HBJob *)aJob
+{
+ [aJob setJobGroup:self];
+ [fJobs addObject: aJob];
+ [self setNeedsDescription: YES];
+ fLastDescriptionHeight = 0;
+ fLastDescriptionWidth = 0;
+}
+
+- (HBJob *) jobAtIndex: (unsigned)index
+{
+ return [fJobs objectAtIndex: index];
+}
+
+- (unsigned) indexOfJob: (HBJob *)aJob;
+{
+ return [fJobs indexOfObject: aJob];
+}
+
+- (NSEnumerator *) jobEnumerator
+{
+ return [fJobs objectEnumerator];
+}
+
+- (void) setNeedsDescription: (BOOL)flag
+{
+ fNeedsDescription = flag;
+}
+
+- (void) updateDescription
+{
+ fNeedsDescription = NO;
+
+ [fDescription deleteCharactersInRange: NSMakeRange(0, [fDescription length])];
+
+ if ([self count] == 0)
+ {
+ NSAssert(NO, @" jobgroup with no jobs");
+ return;
+ }
+
+ HBJob * job = [self jobAtIndex:0];
+
+ // append the title
+ [fDescription appendAttributedString: [job attributedDescriptionWithIcon: NO
+ withTitle: YES
+ withPassName: NO
+ withFormatInfo: NO
+ withDestination: NO
+ withPictureInfo: NO
+ withVideoInfo: NO
+ withx264Info: NO
+ withAudioInfo: NO
+ withSubtitleInfo: NO]];
+
+ // append the preset name
+ if ([fPresetName length])
+ {
+ [fDescription appendString:@"Preset: " withAttributes:[HBJob descriptionDetailBoldAttribute]];
+ [fDescription appendString:fPresetName withAttributes:[HBJob descriptionDetailAttribute]];
+ [fDescription appendString:@"\n" withAttributes:[HBJob descriptionDetailAttribute]];
+ }
+
+ // append the format and destinaton
+ [fDescription appendAttributedString: [job attributedDescriptionWithIcon: NO
+ withTitle: NO
+ withPassName: NO
+ withFormatInfo: YES
+ withDestination: YES
+ withPictureInfo: NO
+ withVideoInfo: NO
+ withx264Info: NO
+ withAudioInfo: NO
+ withSubtitleInfo: NO]];
+
+
+ static NSAttributedString * carriageReturn = [[NSAttributedString alloc] initWithString:@"\n"];
+
+ NSEnumerator * e = [self jobEnumerator];
+ while ( (job = [e nextObject]) )
+ {
+ int pass = job->pass;
+ [fDescription appendAttributedString:carriageReturn];
+ [fDescription appendAttributedString:
+ [job attributedDescriptionWithIcon: YES
+ withTitle: NO
+ withPassName: YES
+ withFormatInfo: NO
+ withDestination: NO
+ withPictureInfo: pass != -1
+ withVideoInfo: pass != -1
+ withx264Info: pass != -1
+ withAudioInfo: pass == 0 || pass == 2
+ withSubtitleInfo: YES]];
+ }
+
+}
+
+- (NSMutableAttributedString *) attributedDescription
+{
+ if (fNeedsDescription)
+ [self updateDescription];
+ return fDescription;
+}
+
+- (float) heightOfDescriptionForWidth:(float)width
+{
+ // Try to return the cached value if no changes have happened since the last time
+ if ((width == fLastDescriptionWidth) && (fLastDescriptionHeight != 0) && !fNeedsDescription)
+ return fLastDescriptionHeight;
+
+ if (fNeedsDescription)
+ [self updateDescription];
+
+ // Calculate the height
+ NSRect bounds = [fDescription boundingRectWithSize:NSMakeSize(width, 10000) options:NSStringDrawingUsesLineFragmentOrigin];
+ fLastDescriptionHeight = bounds.size.height + 6.0; // add some border to bottom
+ fLastDescriptionWidth = width;
+ return fLastDescriptionHeight;
+
+/* supposedly another way to do this, in case boundingRectWithSize isn't working
+ NSTextView* tmpView = [[NSTextView alloc] initWithFrame:NSMakeRect(0, 0, width, 1)];
+ [[tmpView textStorage] setAttributedString:aString];
+ [tmpView setHorizontallyResizable:NO];
+ [tmpView setVerticallyResizable:YES];
+// [[tmpView textContainer] setHeightTracksTextView: YES];
+// [[tmpView textContainer] setContainerSize: NSMakeSize(width, 10000)];
+ [tmpView sizeToFit];
+ float height = [tmpView frame].size.height;
+ [tmpView release];
+ return height;
+*/
+}
+
+- (float) lastDescriptionHeight
+{
+ return fLastDescriptionHeight;
+}
+
+- (void) setStatus: (HBQueueJobGroupStatus)status
+{
+ // Create a dictionary with the old status
+ NSDictionary * userInfo = [NSDictionary dictionaryWithObject:[NSNumber numberWithInt:self->fStatus] forKey:@"HBOldJobGroupStatus"];
+
+ self->fStatus = status;
+
+ // Send notification with old status
+ [[NSNotificationCenter defaultCenter] postNotificationName:HBJobGroupStatusNotification object:self userInfo:userInfo];
+}
+
+- (HBQueueJobGroupStatus) status
+{
+ return self->fStatus;
+}
+
+- (void) setPresetName: (NSString *)name
+{
+ [name retain];
+ [fPresetName release];
+ fPresetName = name;
+}
+
+- (NSString *) presetName
+{
+ return fPresetName;
+}
+
+- (NSString *) name
+{
+ HBJob * firstJob = [self jobAtIndex:0];
+ return firstJob ? firstJob->titleName : nil;
+}
+
+- (NSString *) destinationPath
+{
+ HBJob * firstJob = [self jobAtIndex:0];
+ return firstJob ? firstJob->file : nil;
+}
+
+@end
+
+
#pragma mark -
// Toolbar identifiers
-static NSString* HBQueueToolbar = @"HBQueueToolbar";
-static NSString* HBStartPauseResumeToolbarIdentifier = @"HBStartPauseResumeToolbarIdentifier";
-static NSString* HBShowDetailToolbarIdentifier = @"HBShowDetailToolbarIdentifier";
-static NSString* HBShowGroupsToolbarIdentifier = @"HBShowGroupsToolbarIdentifier";
+static NSString* HBQueueToolbar = @"HBQueueToolbar1";
+static NSString* HBQueueStartCancelToolbarIdentifier = @"HBQueueStartCancelToolbarIdentifier";
+static NSString* HBQueuePauseResumeToolbarIdentifier = @"HBQueuePauseResumeToolbarIdentifier";
+#pragma mark -
@implementation HBQueueController
@"YES", @"QueueShowsJobsAsGroups",
nil]];
- fShowsDetail = [[NSUserDefaults standardUserDefaults] boolForKey:@"QueueShowsDetail"];
- fShowsJobsAsGroups = [[NSUserDefaults standardUserDefaults] boolForKey:@"QueueShowsJobsAsGroups"];
+ fJobGroups = [[NSMutableArray arrayWithCapacity:0] retain];
+ BOOL loadSucceeded = [NSBundle loadNibNamed:@"Queue" owner:self] && fQueueWindow;
+ NSAssert(loadSucceeded, @"Could not open Queue nib");
+ NSAssert(fQueueWindow, @"fQueueWindow not found in Queue nib");
+
+ // Register for HBJobGroup status changes
+ [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(jobGroupStatusNotification:) name:HBJobGroupStatusNotification object:nil];
}
return self;
}
//------------------------------------------------------------------------------------
-// dealloc
+// dealloc
+//------------------------------------------------------------------------------------
+- (void)dealloc
+{
+ // clear the delegate so that windowWillClose is not attempted
+ if ([fQueueWindow delegate] == self)
+ [fQueueWindow setDelegate:nil];
+
+ [fJobGroups release];
+ [fCurrentJobGroup release];
+ [fSavedExpandedItems release];
+ [fSavedSelectedItems release];
+
+ [[NSNotificationCenter defaultCenter] removeObserver:self];
+
+ [super dealloc];
+}
+
+//------------------------------------------------------------------------------------
+// Receive HB handle
+//------------------------------------------------------------------------------------
+- (void)setHandle: (hb_handle_t *)handle
+{
+ fHandle = handle;
+}
+
+//------------------------------------------------------------------------------------
+// Receive HBController
+//------------------------------------------------------------------------------------
+- (void)setHBController: (HBController *)controller
+{
+ fHBController = controller;
+}
+
+#pragma mark -
+#pragma mark - Getting the currently processing job group
+
+//------------------------------------------------------------------------------------
+// Returns the HBJobGroup that is currently being encoded; nil if no encoding is
+// occurring.
//------------------------------------------------------------------------------------
-- (void)dealloc
+- (HBJobGroup *) currentJobGroup;
{
- [fAnimation release];
-
- // clear the delegate so that windowWillClose is not attempted
- if ([fQueueWindow delegate] == self)
- [fQueueWindow setDelegate:nil];
-
- [super dealloc];
+ return fCurrentJobGroup;
}
//------------------------------------------------------------------------------------
-// Receive HB handle
+// Returns the HBJob (pass) that is currently being encoded; nil if no encoding is
+// occurring.
//------------------------------------------------------------------------------------
-- (void)setHandle: (hb_handle_t *)handle
+- (HBJob *) currentJob
{
- fHandle = handle;
+ return fCurrentJob;
}
+#pragma mark -
+
//------------------------------------------------------------------------------------
// Displays and brings the queue window to the front
//------------------------------------------------------------------------------------
- (IBAction) showQueueWindow: (id)sender
{
- if (!fQueueWindow)
- {
- BOOL loadSucceeded = [NSBundle loadNibNamed:@"Queue" owner:self] && fQueueWindow;
- NSAssert(loadSucceeded, @"Could not open Queue nib file");
- }
-
- [self updateQueueUI];
- [self updateCurrentJobUI];
-
[fQueueWindow makeKeyAndOrderFront: self];
-
[[NSUserDefaults standardUserDefaults] setBool:YES forKey:@"QueueWindowIsOpen"];
}
+
//------------------------------------------------------------------------------------
// Show or hide the current job pane (fCurrentJobPane).
//------------------------------------------------------------------------------------
- (void) showCurrentJobPane: (BOOL)showPane
{
- if (showPane != fCurrentJobHidden)
+ if (showPane == fCurrentJobPaneShown)
return;
// Things to keep in mind:
[NSValue valueWithRect:queueFrame], NSViewAnimationEndFrameKey,
nil];
- if (!fAnimation)
- fAnimation = [[NSViewAnimation alloc] initWithViewAnimations:nil];
+ NSViewAnimation * anAnimation = [[[NSViewAnimation alloc] initWithViewAnimations:nil] autorelease];
+ [anAnimation setViewAnimations:[NSArray arrayWithObjects:dict1, dict2, nil]];
+ [anAnimation setDuration:0.25];
+ [anAnimation setAnimationBlockingMode:NSAnimationBlocking]; // prevent user from resizing the window during an animation
+ [anAnimation startAnimation];
+
+ fCurrentJobPaneShown = showPane;
+}
+
+//------------------------------------------------------------------------------------
+// Sets fCurrentJobGroup to a new job group.
+//------------------------------------------------------------------------------------
+- (void) setCurrentJobGroup: (HBJobGroup *)aJobGroup
+{
+ if (aJobGroup)
+ [aJobGroup setStatus: HBStatusWorking];
- [fAnimation setViewAnimations:[NSArray arrayWithObjects:dict1, dict2, nil]];
- [fAnimation setDuration:0.25];
- [fAnimation setAnimationBlockingMode:NSAnimationBlocking]; // prevent user from resizing the window during an animation
- [fAnimation startAnimation];
- fCurrentJobHidden = !showPane;
+ [aJobGroup retain];
+ [fCurrentJobGroup release];
+ fCurrentJobGroup = aJobGroup;
}
+#pragma mark - Finding job groups
+
//------------------------------------------------------------------------------------
-// Enables or disables the display of detail information for each job.
+// Returns the first pending job with a specified destination path or nil if no such
+// job exists.
//------------------------------------------------------------------------------------
-- (void)setShowsDetail: (BOOL)showsDetail
+- (HBJobGroup *) pendingJobGroupWithDestinationPath: (NSString *)path
{
- fShowsDetail = showsDetail;
-
- [[NSUserDefaults standardUserDefaults] setBool:showsDetail forKey:@"QueueShowsDetail"];
- [[NSUserDefaults standardUserDefaults] synchronize];
+ HBJobGroup * aJobGroup;
+ NSEnumerator * groupEnum = [fJobGroups objectEnumerator];
+ while ( (aJobGroup = [groupEnum nextObject]) )
+ {
+ if ([[aJobGroup destinationPath] isEqualToString: path])
+ return aJobGroup;
+ }
+ return nil;
+}
- // clumsy - have to update UI
- [fDetailCheckbox setState:showsDetail ? NSOnState : NSOffState];
-
- [fTaskView setRowHeight:showsDetail ? 110.0 : 17.0];
- if ([fTaskView selectedRow] != -1)
- [fTaskView scrollRowToVisible:[fTaskView selectedRow]];
+//------------------------------------------------------------------------------------
+// Locates and returns a HBJob whose sequence_id matches a specified value.
+//------------------------------------------------------------------------------------
+- (HBJob *) findJobWithID: (int)aJobID
+{
+ HBJobGroup * aJobGroup;
+ NSEnumerator * groupEnum = [fJobGroups objectEnumerator];
+ while ( (aJobGroup = [groupEnum nextObject]) )
+ {
+ HBJob * job;
+ NSEnumerator * jobEnum = [aJobGroup jobEnumerator];
+ while ( (job = [jobEnum nextObject]) )
+ {
+ if (job->sequence_id == aJobID)
+ return job;
+ }
+ }
+ return nil;
}
//------------------------------------------------------------------------------------
-// Enables or disables the grouping of job passes into one item in the UI.
+// Locates and returns a libhb job whose sequence_id matches a specified value.
//------------------------------------------------------------------------------------
-- (void)setShowsJobsAsGroups: (BOOL)showsGroups
+- (hb_job_t *) findLibhbJobWithID: (int)aJobID
{
- fShowsJobsAsGroups = showsGroups;
-
- [[NSUserDefaults standardUserDefaults] setBool:showsGroups forKey:@"QueueShowsJobsAsGroups"];
- [[NSUserDefaults standardUserDefaults] synchronize];
+ hb_job_t * job;
+ int index = 0;
+ while( ( job = hb_job( fHandle, index++ ) ) )
+ {
+ if (job->sequence_id == aJobID)
+ return job;
+ }
+ return nil;
+}
- // clumsy - have to update UI
- [fJobGroupsCheckbox setState:showsGroups ? NSOnState : NSOffState];
-
- [self updateQueueUI];
- if ([fTaskView selectedRow] != -1)
- [fTaskView scrollRowToVisible:[fTaskView selectedRow]];
+#pragma mark -
+#pragma mark Queue Counts
+
+//------------------------------------------------------------------------------------
+// Sets a flag indicating that the values for fPendingCount, fCompletedCount,
+// fCanceledCount, and fWorkingCount need to be recalculated.
+//------------------------------------------------------------------------------------
+- (void) setJobGroupCountsNeedUpdating: (BOOL)flag
+{
+ fJobGroupCountsNeedUpdating = flag;
}
//------------------------------------------------------------------------------------
-// Generates a multi-line text string that includes the job name on the first line
-// followed by details of the job on subsequent lines. If the text is to be drawn as
-// part of a highlighted cell, set isHighlighted to true. The returned string may
-// contain multiple fonts and paragraph formating.
+// Recalculates and stores new values in fPendingCount, fCompletedCount,
+// fCanceledCount, and fWorkingCount.
//------------------------------------------------------------------------------------
-- (NSAttributedString *)attributedDescriptionForJob: (hb_job_t *)job
- withDetail: (BOOL)detail
- withHighlighting: (BOOL)highlighted
+- (void) recalculateJobGroupCounts
{
- NSMutableAttributedString * finalString; // the return value
- NSAttributedString* anAttributedString; // a temp string for building up attributed substrings
- NSMutableString* aMutableString; // a temp string for non-attributed substrings
- hb_title_t * title = job->title;
-
- NSMutableParagraphStyle *ps = [[NSParagraphStyle defaultParagraphStyle] mutableCopy];
- [ps setLineBreakMode:NSLineBreakByClipping];
+ fPendingCount = 0;
+ fCompletedCount = 0;
+ fCanceledCount = 0;
+ fWorkingCount = 0;
+
+ NSEnumerator * groupEnum = [fJobGroups objectEnumerator];
+ HBJobGroup * aJobGroup;
+ while ( (aJobGroup = [groupEnum nextObject]) )
+ {
+ switch ([aJobGroup status])
+ {
+ case HBStatusNone:
+ // We don't track these.
+ break;
+ case HBStatusPending:
+ fPendingCount++;
+ break;
+ case HBStatusCompleted:
+ fCompletedCount++;
+ break;
+ case HBStatusCanceled:
+ fCanceledCount++;
+ break;
+ case HBStatusWorking:
+ fWorkingCount++;
+ break;
+ }
+ }
+ fJobGroupCountsNeedUpdating = NO;
+}
- static NSDictionary* detailAttribute = [[NSDictionary dictionaryWithObjectsAndKeys:
- [NSFont systemFontOfSize:10.0], NSFontAttributeName,
- [NSColor darkGrayColor], NSForegroundColorAttributeName,
- ps, NSParagraphStyleAttributeName,
- nil] retain];
- static NSDictionary* detailHighlightedAttribute = [[NSDictionary dictionaryWithObjectsAndKeys:
- [NSFont systemFontOfSize:10.0], NSFontAttributeName,
- [NSColor whiteColor], NSForegroundColorAttributeName,
- ps, NSParagraphStyleAttributeName,
- nil] retain];
- static NSDictionary* titleAttribute = [[NSDictionary dictionaryWithObject:
- [NSFont systemFontOfSize:[NSFont systemFontSize]] forKey:NSFontAttributeName] retain];
+//------------------------------------------------------------------------------------
+// Returns the number of job groups whose status is HBStatusPending.
+//------------------------------------------------------------------------------------
+- (unsigned int) pendingCount
+{
+ if (fJobGroupCountsNeedUpdating)
+ [self recalculateJobGroupCounts];
+ return fPendingCount;
+}
- finalString = [[[NSMutableAttributedString alloc] init] autorelease];
+//------------------------------------------------------------------------------------
+// Returns the number of job groups whose status is HBStatusCompleted.
+//------------------------------------------------------------------------------------
+- (unsigned int) completedCount
+{
+ if (fJobGroupCountsNeedUpdating)
+ [self recalculateJobGroupCounts];
+ return fCompletedCount;
+}
- // Title, in bold
- // Show the name of the source Note: use title->name instead of title->dvd since
- // name is just the chosen folder, instead of dvd which is the full path
- anAttributedString = [[[NSAttributedString alloc] initWithString:[NSString stringWithUTF8String:title->name] attributes:titleAttribute] autorelease];
- [finalString appendAttributedString:anAttributedString];
+//------------------------------------------------------------------------------------
+// Returns the number of job groups whose status is HBStatusCanceled.
+//------------------------------------------------------------------------------------
+- (unsigned int) canceledCount
+{
+ if (fJobGroupCountsNeedUpdating)
+ [self recalculateJobGroupCounts];
+ return fCanceledCount;
+}
- if (!detail)
- return finalString;
-
- // Other info in plain
- aMutableString = [NSMutableString stringWithCapacity:200];
-
- BOOL jobGroups = [[NSUserDefaults standardUserDefaults] boolForKey:@"QueueShowsJobsAsGroups"];
+//------------------------------------------------------------------------------------
+// Returns the number of job groups whose status is HBStatusWorking.
+//------------------------------------------------------------------------------------
+- (unsigned int) workingCount
+{
+ if (fJobGroupCountsNeedUpdating)
+ [self recalculateJobGroupCounts];
+ return fWorkingCount;
+}
- // The subtitle scan doesn't contain all the stuff we need (like x264opts).
- // So grab the next job in the group for display purposes.
- if (jobGroups && job->pass == -1)
- {
- // When job is the one currently being processed, then the next in its group
- // is the the first job in the queue.
- hb_job_t * nextjob;
- if (job == hb_current_job(fHandle))
- nextjob = hb_job(fHandle, 0);
- else
- nextjob = hb_next_job(fHandle, job);
- if (nextjob) // Overly cautious in case there is no next job!
- job = nextjob;
- }
+#pragma mark -
+#pragma mark UI Updating
- NSString * chapterString = (job->chapter_start == job->chapter_end) ?
- [NSString stringWithFormat:@"Chapter %d", job->chapter_start] :
- [NSString stringWithFormat:@"Chapters %d through %d", job->chapter_start, job->chapter_end];
+//------------------------------------------------------------------------------------
+// Saves the state of the items that are currently expanded and selected. Calling
+// restoreOutlineViewState will restore the state of all items to match what was saved
+// by saveOutlineViewState. Nested calls to saveOutlineViewState are not supported.
+//------------------------------------------------------------------------------------
+- (void) saveOutlineViewState
+{
+ if (!fSavedExpandedItems)
+ fSavedExpandedItems = [[NSMutableIndexSet alloc] init];
+ else
+ [fSavedExpandedItems removeAllIndexes];
+
+ // This code stores the sequence_id of the first job of each job group into an
+ // index set. This is sufficient to identify each group uniquely.
- // Scan pass
- if (job->pass == -1)
+ HBJobGroup * aJobGroup;
+ NSEnumerator * e = [fJobGroups objectEnumerator];
+ while ( (aJobGroup = [e nextObject]) )
{
- [aMutableString appendString:[NSString stringWithFormat:
- @"\nTitle %d, %@, Pass: Scan", title->index, chapterString]];
+ if ([fOutlineView isItemExpanded: aJobGroup])
+ [fSavedExpandedItems addIndex: [aJobGroup jobAtIndex:0]->sequence_id];
}
+
+ // Save the selection also.
- // Normal pass
+ if (!fSavedSelectedItems)
+ fSavedSelectedItems = [[NSMutableIndexSet alloc] init];
else
+ [fSavedSelectedItems removeAllIndexes];
+
+ NSIndexSet * selectedRows = [fOutlineView selectedRowIndexes];
+ int row = [selectedRows firstIndex];
+ while (row != NSNotFound)
{
- if (jobGroups)
- [aMutableString appendString:[NSString stringWithFormat:
- @"\nTitle %d, %@, %d-Pass",
- title->index, chapterString, MIN( 2, job->pass + 1 )]];
- else
- [aMutableString appendString:[NSString stringWithFormat:
- @"\nTitle %d, %@, Pass %d of %d",
- title->index, chapterString, MAX( 1, job->pass ), MIN( 2, job->pass + 1 )]];
-
+ aJobGroup = [fOutlineView itemAtRow: row];
+ [fSavedSelectedItems addIndex: [aJobGroup jobAtIndex:0]->sequence_id];
+ row = [selectedRows indexGreaterThanIndex: row];
+ }
- NSString * jobFormat;
- NSString * jobPictureDetail;
- NSString * jobVideoDetail;
- NSString * jobVideoCodec;
- NSString * jobVideoQuality;
- NSString * jobAudioDetail;
- NSString * jobAudioCodec;
-
- /* Muxer settings (File Format in the gui) */
- if (job->mux == 65536 || job->mux == 131072 || job->mux == 1048576)
- jobFormat = @"MP4"; // HB_MUX_MP4,HB_MUX_PSP,HB_MUX_IPOD
- else if (job->mux == 262144)
- jobFormat = @"AVI"; // HB_MUX_AVI
- else if (job->mux == 524288)
- jobFormat = @"OGM"; // HB_MUX_OGM
- else if (job->mux == 2097152)
- jobFormat = @"MKV"; // HB_MUX_MKV
- else
- jobFormat = @"unknown";
-
- // 2097152
- /* Video Codec settings (Encoder in the gui) */
- if (job->vcodec == 1)
- jobVideoCodec = @"FFmpeg"; // HB_VCODEC_FFMPEG
- else if (job->vcodec == 2)
- jobVideoCodec = @"XviD"; // HB_VCODEC_XVID
- else if (job->vcodec == 4)
- {
- /* Deterimine for sure how we are now setting iPod uuid atom */
- if (job->h264_level) // We are encoding for iPod
- jobVideoCodec = @"x264 (H.264 iPod)"; // HB_VCODEC_X264
- else
- jobVideoCodec = @"x264 (H.264 Main)"; // HB_VCODEC_X264
- }
- else
- jobVideoCodec = @"unknown";
-
- /* Audio Codecs (Second half of Codecs in the gui) */
- if (job->acodec == 256)
- jobAudioCodec = @"AAC"; // HB_ACODEC_FAAC
- else if (job->acodec == 512)
- jobAudioCodec = @"MP3"; // HB_ACODEC_LAME
- else if (job->acodec == 1024)
- jobAudioCodec = @"Vorbis"; // HB_ACODEC_VORBIS
- else if (job->acodec == 2048)
- jobAudioCodec = @"AC3"; // HB_ACODEC_AC3
- else
- jobAudioCodec = @"unknown";
- /* Show Basic File info */
- if (job->chapter_markers == 1)
- [aMutableString appendString:[NSString stringWithFormat:@"\nFormat: %@ Container, %@ Video + %@ Audio, Chapter Markers", jobFormat, jobVideoCodec, jobAudioCodec]];
- else
- [aMutableString appendString:[NSString stringWithFormat:@"\nFormat: %@ Container, %@ Video + %@ Audio", jobFormat, jobVideoCodec, jobAudioCodec]];
-
- /*Picture info*/
- /*integers for picture values deinterlace, crop[4], keep_ratio, grayscale, pixel_ratio, pixel_aspect_width, pixel_aspect_height,
- maxWidth, maxHeight */
- if (job->pixel_ratio == 1)
- {
- int titlewidth = title->width - job->crop[2] - job->crop[3];
- int displayparwidth = titlewidth * job->pixel_aspect_width / job->pixel_aspect_height;
- int displayparheight = title->height - job->crop[0] - job->crop[1];
- jobPictureDetail = [NSString stringWithFormat:@"Picture: %dx%d (%dx%d Anamorphic)", displayparwidth, displayparheight, job->width, displayparheight];
- }
- else
- jobPictureDetail = [NSString stringWithFormat:@"Picture: %dx%d", job->width, job->height];
- if (job->keep_ratio == 1)
- jobPictureDetail = [jobPictureDetail stringByAppendingString:@" Keep Aspect Ratio"];
-
- if (job->grayscale == 1)
- jobPictureDetail = [jobPictureDetail stringByAppendingString:@", Grayscale"];
-
- if (job->deinterlace == 1)
- jobPictureDetail = [jobPictureDetail stringByAppendingString:@", Deinterlace"];
- /* Show Picture info */
- [aMutableString appendString:[NSString stringWithFormat:@"\n%@", jobPictureDetail]];
-
- /* Detailed Video info */
- if (job->vquality <= 0 || job->vquality >= 1)
- jobVideoQuality =[NSString stringWithFormat:@"%d kbps", job->vbitrate];
- else
- {
- NSNumber * vidQuality;
- vidQuality = [NSNumber numberWithInt:job->vquality * 100];
- /* this is screwed up kind of. Needs to be formatted properly */
- if (job->crf == 1)
- jobVideoQuality =[NSString stringWithFormat:@"%@%% CRF", vidQuality];
- else
- jobVideoQuality =[NSString stringWithFormat:@"%@%% CQP", vidQuality];
- }
-
- if (job->vrate_base == 1126125)
- {
- /* NTSC FILM 23.976 */
- jobVideoDetail = [NSString stringWithFormat:@"Video: %@, %@, 23.976 fps", jobVideoCodec, jobVideoQuality];
- }
- else if (job->vrate_base == 900900)
+}
+
+//------------------------------------------------------------------------------------
+// Restores the expanded state of items in the outline view to match those saved by a
+// previous call to saveOutlineViewState.
+//------------------------------------------------------------------------------------
+- (void) restoreOutlineViewState
+{
+ if (fSavedExpandedItems)
+ {
+ HBJobGroup * aJobGroup;
+ NSEnumerator * e = [fJobGroups objectEnumerator];
+ while ( (aJobGroup = [e nextObject]) )
{
- /* NTSC 29.97 */
- jobVideoDetail = [NSString stringWithFormat:@"Video: %@, %@, 29.97 fps", jobVideoCodec, jobVideoQuality];
+ HBJob * job = [aJobGroup jobAtIndex:0];
+ if (job && [fSavedExpandedItems containsIndex: job->sequence_id])
+ [fOutlineView expandItem: aJobGroup];
}
- else
+ }
+
+ if (fSavedSelectedItems)
+ {
+ NSMutableIndexSet * rowsToSelect = [[[NSMutableIndexSet alloc] init] autorelease];
+ HBJobGroup * aJobGroup;
+ NSEnumerator * e = [fJobGroups objectEnumerator];
+ int i = 0;
+ while ( (aJobGroup = [e nextObject]) )
{
- /* Everything else */
- jobVideoDetail = [NSString stringWithFormat:@"Video: %@, %@, %d fps", jobVideoCodec, jobVideoQuality, job->vrate / job->vrate_base];
+ HBJob * job = [aJobGroup jobAtIndex:0];
+ if (job && [fSavedSelectedItems containsIndex: job->sequence_id])
+ [rowsToSelect addIndex: i];
+ i++;
}
-
- /* Add the video detail string to the job filed in the window */
- [aMutableString appendString:[NSString stringWithFormat:@"\n%@", jobVideoDetail]];
-
- /* if there is an x264 option string, lets add it here*/
- /*NOTE: Due to size, lets get this in a tool tip*/
-
- if (job->x264opts)
- [aMutableString appendString:[NSString stringWithFormat:@"\nx264 Options: %@", [NSString stringWithUTF8String:job->x264opts]]];
-
- /* Audio Detail */
- if ([jobAudioCodec isEqualToString: @"AC3"])
- jobAudioDetail = [NSString stringWithFormat:@"Audio: %@, Pass-Through", jobAudioCodec];
+ if ([rowsToSelect count] == 0)
+ [fOutlineView deselectAll: nil];
else
- jobAudioDetail = [NSString stringWithFormat:@"Audio: %@, %d kbps, %d Hz", jobAudioCodec, job->abitrate, job->arate];
-
- /* we now get the audio mixdown info for each of the two gui audio tracks */
- /* lets do it the long way here to get a handle on things.
- Hardcoded for two tracks for gui: audio_mixdowns[i] audio_mixdowns[i] */
- int ai; // counter for each audios [] , macgui only allows for two audio tracks currently
- for( ai = 0; ai < 2; ai++ )
- {
- if (job->audio_mixdowns[ai] == HB_AMIXDOWN_MONO)
- jobAudioDetail = [jobAudioDetail stringByAppendingString:[NSString stringWithFormat:@", Track %d: Mono",ai + 1]];
- if (job->audio_mixdowns[ai] == HB_AMIXDOWN_STEREO)
- jobAudioDetail = [jobAudioDetail stringByAppendingString:[NSString stringWithFormat:@", Track %d: Stereo",ai + 1]];
- if (job->audio_mixdowns[ai] == HB_AMIXDOWN_DOLBY)
- jobAudioDetail = [jobAudioDetail stringByAppendingString:[NSString stringWithFormat:@", Track %d: Dolby Surround",ai + 1]];
- if (job->audio_mixdowns[ai] == HB_AMIXDOWN_DOLBYPLII)
- jobAudioDetail = [jobAudioDetail stringByAppendingString:[NSString stringWithFormat:@", Track %d: Dolby Pro Logic II",ai + 1]];
- if (job->audio_mixdowns[ai] == HB_AMIXDOWN_6CH)
- jobAudioDetail = [jobAudioDetail stringByAppendingString:[NSString stringWithFormat:@", Track %d: 6-channel discreet",ai + 1]];
- }
-
- /* Add the Audio detail string to the job filed in the window */
- [aMutableString appendString:[NSString stringWithFormat: @"\n%@", jobAudioDetail]];
-
- /*Destination Field */
- [aMutableString appendString:[NSString stringWithFormat:@"\nDestination: %@", [NSString stringWithUTF8String:job->file]]];
+ [fOutlineView selectRowIndexes:rowsToSelect byExtendingSelection:NO];
}
-
- anAttributedString = [[[NSAttributedString alloc] initWithString:aMutableString attributes:highlighted ? detailHighlightedAttribute : detailAttribute] autorelease];
- [finalString appendAttributedString:anAttributedString];
+}
-
- return finalString;
+//------------------------------------------------------------------------------------
+// Marks the icon region of a job group in the queue view as needing display.
+//------------------------------------------------------------------------------------
+- (void) updateJobGroupIconInQueue:(HBJobGroup*)aJobGroup
+{
+ int row = [fOutlineView rowForItem: aJobGroup];
+ int col = [fOutlineView columnWithIdentifier: @"icon"];
+ if (row != -1 && col != -1)
+ {
+ NSRect frame = [fOutlineView frameOfCellAtColumn:col row:row];
+ [fOutlineView setNeedsDisplayInRect: frame];
+ }
+}
+
+//------------------------------------------------------------------------------------
+// Marks the entire region of a job group in the queue view as needing display.
+//------------------------------------------------------------------------------------
+- (void) updateJobGroupInQueue:(HBJobGroup*)aJobGroup
+{
+ int row = [fOutlineView rowForItem: aJobGroup];
+ if (row != -1)
+ {
+ NSRect frame = [fOutlineView rectOfRow:row];
+ [fOutlineView setNeedsDisplayInRect: frame];
+ }
+}
+
+//------------------------------------------------------------------------------------
+// If a job is currently processing, its job icon in the queue outline view is
+// animated to its next state.
+//------------------------------------------------------------------------------------
+- (void) animateCurrentJobGroupInQueue:(NSTimer*)theTimer
+{
+ if (fCurrentJobGroup)
+ {
+ fAnimationIndex++;
+ fAnimationIndex %= 6; // there are 6 animation images; see outlineView:objectValueForTableColumn:byItem: below.
+ [self updateJobGroupIconInQueue: fCurrentJobGroup];
+ }
+}
+
+//------------------------------------------------------------------------------------
+// Starts animating the job icon of the currently processing job in the queue outline
+// view.
+//------------------------------------------------------------------------------------
+- (void) startAnimatingCurrentJobGroupInQueue
+{
+ if (!fAnimationTimer)
+ fAnimationTimer = [[NSTimer scheduledTimerWithTimeInterval:1.0/12.0 // 1/12 because there are 6 images in the animation cycle
+ target:self
+ selector:@selector(animateCurrentJobGroupInQueue:)
+ userInfo:nil
+ repeats:YES] retain];
+}
+
+//------------------------------------------------------------------------------------
+// Stops animating the job icon of the currently processing job in the queue outline
+// view.
+//------------------------------------------------------------------------------------
+- (void) stopAnimatingCurrentJobGroupInQueue
+{
+ if (fAnimationTimer && [fAnimationTimer isValid])
+ {
+ [fAnimationTimer invalidate];
+ [fAnimationTimer release];
+ fAnimationTimer = nil;
+ }
}
//------------------------------------------------------------------------------------
// Generate string to display in UI.
//------------------------------------------------------------------------------------
-- (NSString *) progressStatusStringForJob: (hb_job_t *)job state: (hb_state_t *)s
+- (NSString *) progressStatusStringForJob: (HBJob *)job state: (hb_state_t *)s
{
if (s->state == HB_STATE_WORKING)
{
NSString * msg;
if (job->pass == -1)
- msg = NSLocalizedString( @"Analyzing subtitles", nil );
+ msg = NSLocalizedString( @"Deep Scan", nil );
else if (job->pass == 1)
msg = NSLocalizedString( @"Analyzing video", nil );
else if ((job->pass == 0) || (job->pass == 2))
//------------------------------------------------------------------------------------
// Generate string to display in UI.
//------------------------------------------------------------------------------------
-- (NSString *) progressTimeRemainingStringForJob: (hb_job_t *)job state: (hb_state_t *)s
+- (NSString *) progressTimeRemainingStringForJob: (HBJob *)job state: (hb_state_t *)s
{
if (s->state == HB_STATE_WORKING)
{
}
//------------------------------------------------------------------------------------
+// Refresh progress bar (fProgressTextField) from current state.
+//------------------------------------------------------------------------------------
+- (void) updateProgressTextForJob: (HBJob *)job state: (hb_state_t *)s
+{
+ NSString * statusMsg = [self progressStatusStringForJob:job state:s];
+ NSString * timeMsg = [self progressTimeRemainingStringForJob:job state:s];
+ if ([timeMsg length] > 0)
+ statusMsg = [NSString stringWithFormat:@"%@ - %@", statusMsg, timeMsg];
+ [fProgressTextField setStringValue:statusMsg];
+}
+
+//------------------------------------------------------------------------------------
// Refresh progress bar (fProgressBar) from current state.
//------------------------------------------------------------------------------------
- (void) updateProgressBarWithState: (hb_state_t *)s
{
#define p s->param.working
[fProgressBar setIndeterminate:NO];
-
- BOOL jobGroups = [[NSUserDefaults standardUserDefaults] boolForKey:@"QueueShowsJobsAsGroups"];
- float progress_total = jobGroups ?
- 100.0 * ( p.progress + p.job_cur - 1 ) / p.job_count :
- 100.0 * p.progress;
-
+ float progress_total = 100.0 * ( p.progress + p.job_cur - 1 ) / p.job_count;
[fProgressBar setDoubleValue:progress_total];
#undef p
}
else if (s->state == HB_STATE_WORKDONE)
{
[fProgressBar setIndeterminate:NO];
+ [fProgressBar stopAnimation:nil];
[fProgressBar setDoubleValue:0.0];
}
+
+ else
+ [fProgressBar stopAnimation:nil]; // just in case in was animating
}
//------------------------------------------------------------------------------------
-// Refresh start/pause button (fStartPauseButton) from current state.
+// Refresh queue count text field (fQueueCountField).
//------------------------------------------------------------------------------------
-- (void) updateStartPauseButton
+- (void)updateQueueCountField
{
-
-// ************* THIS METHOD CAN DISAPPEAR. THE BUTTON IS NOW HIDDEN AND CAN BE DELETED
- if (!fHandle) return;
-
- hb_state_t s;
- hb_get_state2 (fHandle, &s);
-
- if (s.state == HB_STATE_PAUSED)
- {
- [fStartPauseButton setEnabled:YES];
-// [fStartPauseButton setTitle:NSLocalizedString(@"Resume", nil)];
- [fStartPauseButton setImage:[NSImage imageNamed: @"Play"]];
- }
-
- else if ((s.state == HB_STATE_WORKING) || (s.state == HB_STATE_MUXING))
+ NSString * msg;
+ int jobCount = [fJobGroups count];
+ int pendingCount = [self pendingCount];
+ if (jobCount == 0)
+ msg = NSLocalizedString(@"No encodes", nil);
+ else if ((jobCount == 1) && (pendingCount == 0))
+ msg = NSLocalizedString(@"1 encode", nil);
+ else if (jobCount == pendingCount) // ie, all jobs listed are pending
{
- [fStartPauseButton setEnabled:YES];
-// [fStartPauseButton setTitle:NSLocalizedString(@"Pause", nil)];
- [fStartPauseButton setImage:[NSImage imageNamed: @"Pause"]];
+ if (jobCount == 1)
+ msg = NSLocalizedString(@"1 pending encode", nil);
+ else
+ msg = [NSString stringWithFormat:NSLocalizedString(@"%d pending encodes", nil), pendingCount];
}
+ else // some completed, some pending
+ msg = [NSString stringWithFormat:NSLocalizedString(@"%d encodes (%d pending)", nil), jobCount, pendingCount];
- else if (hb_count(fHandle) > 0)
+ [fQueueCountField setStringValue:msg];
+}
+
+//------------------------------------------------------------------------------------
+// Refresh the UI in the current job pane. Should be called whenever the current job
+// being processed has changed.
+//------------------------------------------------------------------------------------
+- (void)updateCurrentJobDescription
+{
+ if (fCurrentJob)
{
- [fStartPauseButton setEnabled:YES];
-// [fStartPauseButton setTitle:NSLocalizedString(@"Start", nil)];
- [fStartPauseButton setImage:[NSImage imageNamed: @"Play"]];
+ switch (fCurrentJob->pass)
+ {
+ case -1: // Subtitle scan
+ [fJobDescTextField setAttributedStringValue:
+ [fCurrentJob attributedDescriptionWithIcon: NO
+ withTitle: YES
+ withPassName: YES
+ withFormatInfo: NO
+ withDestination: NO
+ withPictureInfo: NO
+ withVideoInfo: NO
+ withx264Info: NO
+ withAudioInfo: NO
+ withSubtitleInfo: YES]];
+ break;
+
+ case 1: // video 1st pass
+ [fJobDescTextField setAttributedStringValue:
+ [fCurrentJob attributedDescriptionWithIcon: NO
+ withTitle: YES
+ withPassName: YES
+ withFormatInfo: NO
+ withDestination: NO
+ withPictureInfo: YES
+ withVideoInfo: YES
+ withx264Info: YES
+ withAudioInfo: NO
+ withSubtitleInfo: NO]];
+ break;
+
+ case 0: // single pass
+ case 2: // video 2nd pass + audio
+ [fJobDescTextField setAttributedStringValue:
+ [fCurrentJob attributedDescriptionWithIcon: NO
+ withTitle: YES
+ withPassName: YES
+ withFormatInfo: NO
+ withDestination: NO
+ withPictureInfo: YES
+ withVideoInfo: YES
+ withx264Info: YES
+ withAudioInfo: YES
+ withSubtitleInfo: YES]];
+ break;
+
+ default: // unknown
+ [fJobDescTextField setAttributedStringValue:
+ [fCurrentJob attributedDescriptionWithIcon: NO
+ withTitle: YES
+ withPassName: YES
+ withFormatInfo: NO
+ withDestination: NO
+ withPictureInfo: YES
+ withVideoInfo: YES
+ withx264Info: YES
+ withAudioInfo: YES
+ withSubtitleInfo: YES]];
+ }
}
-
+
else
+ [fJobDescTextField setStringValue: @"No encodes pending"];
+}
+
+//------------------------------------------------------------------------------------
+// Refresh the UI in the current job pane. Should be called whenever the current job
+// being processed has changed or when progress has changed.
+//------------------------------------------------------------------------------------
+- (void)updateCurrentJobProgress
+{
+ hb_state_t s;
+ hb_get_state2( fHandle, &s );
+ [self updateProgressTextForJob: fCurrentJob state: &s];
+ [self updateProgressBarWithState:&s];
+}
+
+//------------------------------------------------------------------------------------
+// Notifies HBQueuecontroller that the contents of fJobGroups is about to be modified.
+// HBQueuecontroller remembers the state of the UI (selection and expanded items).
+//------------------------------------------------------------------------------------
+- (void) beginEditingJobGroupsArray
+{
+ [self saveOutlineViewState];
+}
+
+//------------------------------------------------------------------------------------
+// Notifies HBQueuecontroller that modifications to fJobGroups as indicated by a prior
+// call to beginEditingJobGroupsArray have been completed. HBQueuecontroller reloads
+// the queue view and restores the state of the UI (selection and expanded items).
+//------------------------------------------------------------------------------------
+- (void) endEditingJobGroupsArray
+{
+ [self setJobGroupCountsNeedUpdating:YES];
+ [fOutlineView noteNumberOfRowsChanged];
+ [fOutlineView reloadData];
+ [self restoreOutlineViewState];
+ [self updateQueueCountField];
+}
+
+#pragma mark -
+#pragma mark Actions
+
+//------------------------------------------------------------------------------------
+// Deletes the selected jobs from HB and the queue UI
+//------------------------------------------------------------------------------------
+- (IBAction)removeSelectedJobGroups: (id)sender
+{
+ if (!fHandle) return;
+
+ NSIndexSet * selectedRows = [fOutlineView selectedRowIndexes];
+ int row = [selectedRows firstIndex];
+ if (row != NSNotFound)
{
- [fStartPauseButton setEnabled:NO];
-// [fStartPauseButton setTitle:NSLocalizedString(@"Start", nil)];
- [fStartPauseButton setImage:[NSImage imageNamed: @"Play"]];
- }
+ [self beginEditingJobGroupsArray];
+ while (row != NSNotFound)
+ {
+ HBJobGroup * jobGroup = [fOutlineView itemAtRow: row];
+ switch ([jobGroup status])
+ {
+ case HBStatusCompleted:
+ case HBStatusCanceled:
+ [fJobGroups removeObject: jobGroup];
+ break;
+ case HBStatusWorking:
+ [self cancelCurrentJob: sender];
+ break;
+ case HBStatusPending:
+ // Remove from libhb
+ HBJob * job;
+ NSEnumerator * e = [jobGroup jobEnumerator];
+ while (job = [e nextObject])
+ {
+ hb_job_t * libhbJob = [self findLibhbJobWithID:job->sequence_id];
+ if (libhbJob)
+ hb_rem( fHandle, libhbJob );
+ }
+ // Remove from our list
+ [fJobGroups removeObject: jobGroup];
+ break;
+ case HBStatusNone:
+ break;
+ }
+
+ row = [selectedRows indexGreaterThanIndex: row];
+ }
+ [self endEditingJobGroupsArray];
+ }
}
//------------------------------------------------------------------------------------
-// Refresh queue count text field (fQueueCountField).
+// Reveals the file icons in the Finder of the selected job groups.
//------------------------------------------------------------------------------------
-- (void)updateQueueCountField
+- (IBAction)revealSelectedJobGroups: (id)sender
{
- NSString * msg;
- int jobCount;
- BOOL jobGroups = [[NSUserDefaults standardUserDefaults] boolForKey:@"QueueShowsJobsAsGroups"];
+ if (!fHandle) return;
- if (jobGroups)
- {
- jobCount = fHandle ? hb_group_count(fHandle) : 0;
- if (jobCount == 1)
- msg = NSLocalizedString(@"1 pending encode", nil);
- else
- msg = [NSString stringWithFormat:NSLocalizedString(@"%d pending encodes", nil), jobCount];
- }
- else
+ NSIndexSet * selectedRows = [fOutlineView selectedRowIndexes];
+ int row = [selectedRows firstIndex];
+ if (row != NSNotFound)
{
- jobCount = fHandle ? hb_count(fHandle) : 0;
- if (jobCount == 1)
- msg = NSLocalizedString(@"1 pending pass", nil);
- else
- msg = [NSString stringWithFormat:NSLocalizedString(@"%d pending passes", nil), jobCount];
-
- }
+ while (row != NSNotFound)
+ {
+ HBJobGroup * jobGroup = [fOutlineView itemAtRow: row];
+ if ([[jobGroup destinationPath] length])
+ [[NSWorkspace sharedWorkspace] selectFile:[jobGroup destinationPath] inFileViewerRootedAtPath:nil];
+
+ row = [selectedRows indexGreaterThanIndex: row];
+ }
+ }
+}
- [fQueueCountField setStringValue:msg];
+//------------------------------------------------------------------------------------
+// Calls HBController Cancel: which displays an alert asking user if they want to
+// cancel encoding of current job. cancelCurrentJob: returns immediately after posting
+// the alert. Later, when the user acknowledges the alert, HBController will call
+// libhb to cancel the job.
+//------------------------------------------------------------------------------------
+- (IBAction)cancelCurrentJob: (id)sender
+{
+ [fHBController Cancel:sender];
}
//------------------------------------------------------------------------------------
-// Refresh the UI in the current job pane. Should be called whenever the current job
-// being processed has changed or when progress has changed.
+// Starts or cancels the processing of jobs depending on the current state
//------------------------------------------------------------------------------------
-- (void)updateCurrentJobUI
+- (IBAction)toggleStartCancel: (id)sender
{
- hb_state_t s;
- hb_job_t * job = nil;
+ if (!fHandle) return;
- if (fHandle)
- {
- hb_get_state( fHandle, &s );
- job = hb_current_job(fHandle);
- }
-
- if (job)
- {
- [fJobDescTextField setAttributedStringValue:[self attributedDescriptionForJob:job withDetail:YES withHighlighting:NO]];
-
- [self showCurrentJobPane:YES];
- [fJobIconView setImage: fShowsJobsAsGroups ? [NSImage imageNamed:@"JobLarge"] : [NSImage imageNamed:@"JobPassLarge"] ];
-
- NSString * statusMsg = [self progressStatusStringForJob:job state:&s];
- NSString * timeMsg = [self progressTimeRemainingStringForJob:job state:&s];
- if ([timeMsg length] > 0)
- statusMsg = [NSString stringWithFormat:@"%@ - %@", statusMsg, timeMsg];
- [fProgressTextField setStringValue:statusMsg];
- [self updateProgressBarWithState:&s];
- }
- else
- {
- [fJobDescTextField setStringValue:NSLocalizedString(@"No job processing", nil)];
+ hb_state_t s;
+ hb_get_state2 (fHandle, &s);
- [self showCurrentJobPane:NO];
- [fProgressBar stopAnimation:nil]; // just in case in was animating
- }
+ if ((s.state == HB_STATE_PAUSED) || (s.state == HB_STATE_WORKING) || (s.state == HB_STATE_MUXING))
+ [fHBController Cancel: fQueuePane]; // sender == fQueuePane so that warning alert shows up on queue window
- // Gross hack. Also update start/pause button. Have to do it here since we don't
- // have any other periodic chance to update the button.
- [self updateStartPauseButton];
+ else if ([self pendingCount] > 0)
+ [fHBController doRip];
}
//------------------------------------------------------------------------------------
-// Refresh the UI in the queue pane. Should be called whenever the content of HB's job
-// list has changed so that HBQueueController can sync up.
+// Toggles the pause/resume state of libhb
//------------------------------------------------------------------------------------
-- (void)updateQueueUI
+- (IBAction)togglePauseResume: (id)sender
{
- [fTaskView noteNumberOfRowsChanged];
- [fTaskView reloadData];
+ if (!fHandle) return;
- [self updateQueueCountField];
- [self updateStartPauseButton];
+ hb_state_t s;
+ hb_get_state2 (fHandle, &s);
+
+ if (s.state == HB_STATE_PAUSED)
+ hb_resume (fHandle);
+ else if ((s.state == HB_STATE_WORKING) || (s.state == HB_STATE_MUXING))
+ hb_pause (fHandle);
}
+#pragma mark -
+#pragma mark Synchronizing with libhb
+
//------------------------------------------------------------------------------------
-// Deletes the selected job from HB and the queue UI
+// Queues a job group. The job group's status is set to HBStatusPending.
//------------------------------------------------------------------------------------
-- (IBAction)removeSelectedJob: (id)sender
+- (void) addJobGroup: (HBJobGroup *) aJobGroup
{
- if (!fHandle) return;
+ NSAssert(![fJobGroups containsObject:aJobGroup], @"Duplicate job group");
+ [aJobGroup setStatus:HBStatusPending];
- int row = [sender selectedRow];
- if (row != -1)
- {
- BOOL jobGroups = [[NSUserDefaults standardUserDefaults] boolForKey:@"QueueShowsJobsAsGroups"];
- if (jobGroups)
- hb_rem_group( fHandle, hb_group( fHandle, row ) );
- else
- hb_rem( fHandle, hb_job( fHandle, row ) );
- [self updateQueueUI];
- }
+ [self beginEditingJobGroupsArray];
+ [fJobGroups addObject:aJobGroup];
+ [self endEditingJobGroupsArray];
}
//------------------------------------------------------------------------------------
-// Prompts user if the want to cancel encoding of current job. If so, hb_stop gets
-// called.
+// Notifies HBQueueController that libhb's current job has changed
//------------------------------------------------------------------------------------
-- (IBAction)cancelCurrentJob: (id)sender
+- (void)currentJobChanged: (HBJob *) currentJob
{
- if (!fHandle) return;
-
- hb_job_t * job = hb_current_job(fHandle);
- if (!job) return;
+ [currentJob retain];
+ [fCurrentJob release];
+ fCurrentJob = currentJob;
- // If command key is down, don't prompt
- BOOL hasCmdKeyMask = ([[NSApp currentEvent] modifierFlags] & NSCommandKeyMask) != 0;
- if (hasCmdKeyMask)
- hb_stop(fHandle);
- else
+ // Log info about the preset name. We do this for each job, since libhb logs each
+ // job separately. The preset name is found in the job's job group object.
+ if (fCurrentJob && [fCurrentJob jobGroup] && ([[[fCurrentJob jobGroup] presetName] length] > 0))
+ [fHBController writeToActivityLog: "Using preset: %s", [[[fCurrentJob jobGroup] presetName] UTF8String]];
+
+ // Check to see if this is also a change in Job Group
+
+ HBJobGroup * theJobGroup = [currentJob jobGroup];
+ if ((theJobGroup == nil) || (theJobGroup != fCurrentJobGroup)) // no more job groups or start of a new group
{
- NSString * alertTitle = [NSString stringWithFormat:NSLocalizedString(@"Do you want to stop processing of %@?", nil),
- [NSString stringWithUTF8String:job->title->name]];
+ // Previous job has completed
+ if (fCurrentJobGroup)
+ {
+ // Update the status of the job that just finished. If the user canceled,
+ // the status will have already been set to canceled by libhbWillStop. So
+ // all other cases are assumed to be a successful encode. BTW, libhb
+ // doesn't currently report errors back to the GUI.
+ if ([fCurrentJobGroup status] != HBStatusCanceled)
+ [fCurrentJobGroup setStatus:HBStatusCompleted];
+ }
- NSBeginCriticalAlertSheet(
- alertTitle,
- NSLocalizedString(@"Stop Processing", nil), NSLocalizedString(@"Keep Processing", nil), nil, fQueueWindow, self,
- @selector(cancelCurrentJob:returnCode:contextInfo:), nil, nil,
- NSLocalizedString(@"Your movie will be lost if you don't continue processing.", nil),
- [NSString stringWithUTF8String:job->title->name]);
-
- // cancelCurrentJob:returnCode:contextInfo: will be called when the dialog is dismissed
+ // Set the new group
+ [self setCurrentJobGroup: theJobGroup];
+
+ // Update the UI
+ [self updateCurrentJobDescription];
+ [self updateCurrentJobProgress];
+ [self showCurrentJobPane: fCurrentJobGroup != nil];
+ if (fCurrentJobGroup)
+ [self startAnimatingCurrentJobGroupInQueue];
+ else
+ [self stopAnimatingCurrentJobGroupInQueue];
+ }
+
+ else // start a new job/pass in the same group
+ {
+ // Update the UI
+ [self updateCurrentJobDescription];
+ [self updateCurrentJobProgress];
}
-}
-- (void) cancelCurrentJob: (NSWindow *)sheet returnCode: (int)returnCode contextInfo: (void *)contextInfo
-{
- if (returnCode == NSAlertDefaultReturn)
- hb_stop(fHandle);
}
//------------------------------------------------------------------------------------
-// Enables or disables the display of detail information for each job based on the
-// state of the sender.
+// Notifies HBQueueController that hb_stop is about to be called. This signals us that
+// the current job is going to be canceled and deleted. This is somewhat of a hack to
+// let HBQueueController know when a job group has been cancelled. Otherwise, we'd
+// have no way of knowing if a job was canceled or completed sucessfully.
//------------------------------------------------------------------------------------
-- (IBAction)detailChanged: (id)sender
+- (void)libhbWillStop
{
- if ([sender isMemberOfClass:[NSButton class]])
- {
- BOOL detail = [sender state] == NSOnState;
- [[NSUserDefaults standardUserDefaults] setBool:detail forKey:@"QueueShowsDetail"];
-
- [self setShowsDetail:detail];
- }
+ if (fCurrentJobGroup)
+ [fCurrentJobGroup setStatus: HBStatusCanceled];
}
//------------------------------------------------------------------------------------
-// Enables or disables the display of job groups based on the state of the sender.
+// Notifies HBQueueController that libhb's state has changed
//------------------------------------------------------------------------------------
-- (IBAction)jobGroupsChanged: (id)sender
+- (void)libhbStateChanged: (hb_state_t &)state
{
- if ([sender isMemberOfClass:[NSButton class]])
+ switch( state.state )
{
- BOOL groups = [sender state] == NSOnState;
- [[NSUserDefaults standardUserDefaults] setBool:groups forKey:@"QueueShowsJobsAsGroups"];
+ case HB_STATE_WORKING:
+ {
+ //NSLog(@"job = %x; job_cur = %d; job_count = %d", state.param.working.sequence_id, state.param.working.job_cur, state.param.working.job_count);
+ // First check to see if libhb has moved on to another job. We get no direct
+ // message when this happens, so we have to detect it ourself. The new job could
+ // be either just the next job in the current group, or the start of a new group.
+ if (fCurrentJobID != state.param.working.sequence_id)
+ {
+ fCurrentJobID = state.param.working.sequence_id;
+ HBJob * currentJob = [self findJobWithID:fCurrentJobID];
+ [self currentJobChanged: currentJob];
+ }
+
+ if (fCurrentJob)
+ {
+ [self updateCurrentJobProgress];
+ [self startAnimatingCurrentJobGroupInQueue];
+ }
+ break;
+ }
- [self setShowsJobsAsGroups:groups];
- }
- else if ([sender isMemberOfClass:[NSSegmentedControl class]])
- {
- BOOL groups = [sender selectedSegment] == 0;
- [[NSUserDefaults standardUserDefaults] setBool:groups forKey:@"QueueShowsJobsAsGroups"];
+ case HB_STATE_MUXING:
+ {
+ [self updateCurrentJobProgress];
+ break;
+ }
- [self setShowsJobsAsGroups:groups];
- }
- else if ([sender isMemberOfClass:[NSMatrix class]])
- {
- BOOL groups = [sender selectedColumn] == 0;
- [[NSUserDefaults standardUserDefaults] setBool:groups forKey:@"QueueShowsJobsAsGroups"];
+ case HB_STATE_PAUSED:
+ {
+ [self updateCurrentJobProgress];
+ [self stopAnimatingCurrentJobGroupInQueue];
+ break;
+ }
+
+ case HB_STATE_WORKDONE:
+ {
+ // HB_STATE_WORKDONE means that libhb has finished processing all the jobs
+ // in *its* queue. This message is NOT sent as each individual job is
+ // completed.
+
+ [self currentJobChanged: nil];
+ fCurrentJobID = 0;
+ break;
+ }
- [self setShowsJobsAsGroups:groups];
}
+
}
-//------------------------------------------------------------------------------------
-// Toggles the Shows Detail setting.
-//------------------------------------------------------------------------------------
-- (IBAction)toggleShowsDetail: (id)sender
+#if HB_OUTLINE_METRIC_CONTROLS
+static float spacingWidth = 3.0;
+- (IBAction)imageSpacingChanged: (id)sender;
{
- [self setShowsDetail:!fShowsDetail];
+ spacingWidth = [sender floatValue];
+ [fOutlineView setNeedsDisplay: YES];
}
-
-//------------------------------------------------------------------------------------
-// Toggles the Shows Jobs As Groups setting.
-//------------------------------------------------------------------------------------
-- (IBAction)toggleShowsJobsAsGroups: (id)sender
+- (IBAction)indentChanged: (id)sender
{
- [self setShowsJobsAsGroups:!fShowsJobsAsGroups];
+ [fOutlineView setIndentationPerLevel: [sender floatValue]];
+ [fOutlineView setNeedsDisplay: YES];
}
+#endif
+
+#pragma mark -
//------------------------------------------------------------------------------------
-// Toggles the processing of jobs on or off depending on the current state
+// Receives notification whenever an HBJobGroup's status is changed.
//------------------------------------------------------------------------------------
-- (IBAction)toggleStartPause: (id)sender
+- (void) jobGroupStatusNotification:(NSNotification *)notification
{
- if (!fHandle) return;
-
- hb_state_t s;
- hb_get_state2 (fHandle, &s);
-
- if (s.state == HB_STATE_PAUSED)
- hb_resume (fHandle);
- else if ((s.state == HB_STATE_WORKING) || (s.state == HB_STATE_MUXING))
- hb_pause (fHandle);
- else
- {
- BOOL jobGroups = [[NSUserDefaults standardUserDefaults] boolForKey:@"QueueShowsJobsAsGroups"];
- if (jobGroups)
- {
- if (hb_group_count(fHandle) > 0)
- hb_start (fHandle);
- }
- else if (hb_count(fHandle) > 0)
- hb_start (fHandle);
- }
+ [self setJobGroupCountsNeedUpdating: YES];
+// HBQueueJobGroupStatus oldStatus = (HBQueueJobGroupStatus) [[[notification userInfo] objectForKey:@"HBOldJobGroupStatus"] intValue];
+ HBJobGroup * jobGroup = [notification object];
+ if (jobGroup)
+ [self updateJobGroupInQueue:jobGroup];
+ [self updateQueueCountField];
}
+
#pragma mark -
#pragma mark Toolbar
NSToolbarItem *toolbarItem = nil;
- if ([itemIdentifier isEqual: HBStartPauseResumeToolbarIdentifier])
+ if ([itemIdentifier isEqual: HBQueueStartCancelToolbarIdentifier])
{
toolbarItem = [[[NSToolbarItem alloc] initWithItemIdentifier: itemIdentifier] autorelease];
-
- // Set the text label to be displayed in the toolbar and customization palette
- [toolbarItem setLabel: @"Start"];
- [toolbarItem setPaletteLabel: @"Start/Pause"];
-
- // Set up a reasonable tooltip, and image
- [toolbarItem setToolTip: @"Start Encoding"];
- [toolbarItem setImage: [NSImage imageNamed: @"Play"]];
-
- // Tell the item what message to send when it is clicked
- [toolbarItem setTarget: self];
- [toolbarItem setAction: @selector(toggleStartPause:)];
- }
-
- else if ([itemIdentifier isEqual: HBShowDetailToolbarIdentifier])
- {
- toolbarItem = [[[NSToolbarItem alloc] initWithItemIdentifier: itemIdentifier] autorelease];
-
+
// Set the text label to be displayed in the toolbar and customization palette
- [toolbarItem setLabel: @"Show Detail"];
- [toolbarItem setPaletteLabel: @"Show Detail"];
-
- // Set up a reasonable tooltip, and image
- [toolbarItem setToolTip: @"Show Detail"];
- [toolbarItem setImage: [NSImage imageNamed: @"Info"]];
-
- // Tell the item what message to send when it is clicked
- [toolbarItem setTarget: self];
- [toolbarItem setAction: @selector(toggleShowsDetail:)];
- }
+ [toolbarItem setLabel: @"Start"];
+ [toolbarItem setPaletteLabel: @"Start/Cancel"];
+
+ // Set up a reasonable tooltip, and image
+ [toolbarItem setToolTip: @"Start Encoding"];
+ [toolbarItem setImage: [NSImage imageNamed: @"Play"]];
+
+ // Tell the item what message to send when it is clicked
+ [toolbarItem setTarget: self];
+ [toolbarItem setAction: @selector(toggleStartCancel:)];
+ }
- else if ([itemIdentifier isEqual: HBShowGroupsToolbarIdentifier])
+ if ([itemIdentifier isEqual: HBQueuePauseResumeToolbarIdentifier])
{
toolbarItem = [[[NSToolbarItem alloc] initWithItemIdentifier: itemIdentifier] autorelease];
-
+
// Set the text label to be displayed in the toolbar and customization palette
- [toolbarItem setLabel: @"View"];
- [toolbarItem setPaletteLabel: @"View"];
-
- // Set up a reasonable tooltip, and image
- [toolbarItem setToolTip: @"View"];
-// [toolbarItem setImage: [NSImage imageNamed: @"Disc"]];
+ [toolbarItem setLabel: @"Pause"];
+ [toolbarItem setPaletteLabel: @"Pause/Resume"];
+ // Set up a reasonable tooltip, and image
+ [toolbarItem setToolTip: @"Pause Encoding"];
+ [toolbarItem setImage: [NSImage imageNamed: @"Pause"]];
- NSButtonCell * buttonCell = [[[NSButtonCell alloc] initImageCell:nil] autorelease];
- [buttonCell setBezelStyle:NSShadowlessSquareBezelStyle];//NSShadowlessSquareBezelStyle
- [buttonCell setButtonType:NSToggleButton];
- [buttonCell setBordered:NO];
- [buttonCell setImagePosition:NSImageOnly];
-
- NSMatrix * matrix = [[[NSMatrix alloc] initWithFrame:NSMakeRect(0,0,54,25)
- mode:NSRadioModeMatrix
- prototype:buttonCell
- numberOfRows:1
- numberOfColumns:2] autorelease];
- [matrix setCellSize:NSMakeSize(27, 25)];
- [matrix setIntercellSpacing:NSMakeSize(0, 0)];
- [matrix selectCellAtRow:0 column:(fShowsJobsAsGroups ? 0 : 1)];
-
- buttonCell = [matrix cellAtRow:0 column:0];
- [buttonCell setTitle:@""];
- [buttonCell setImage:[NSImage imageNamed: @"Encodes"]];
- [buttonCell setAlternateImage:[NSImage imageNamed: @"EncodesPressed"]];
- buttonCell = [matrix cellAtRow:0 column:1];
- [buttonCell setTitle:@""];
- [buttonCell setImage:[NSImage imageNamed: @"Passes"]];
- [buttonCell setAlternateImage:[NSImage imageNamed: @"PassesPressed"]];
- [toolbarItem setMinSize: [matrix frame].size];
- [toolbarItem setMaxSize: [matrix frame].size];
- [toolbarItem setView: matrix];
-
-/*
- NSSegmentedControl * segControl = [[[NSSegmentedControl alloc] initWithFrame:NSMakeRect(0,0,20,20)] autorelease];
- [[segControl cell] setControlSize:NSSmallControlSize];
- [segControl setSegmentCount:2];
- [segControl setLabel:@"Encodes" forSegment:0];
- [segControl setLabel:@"Passes" forSegment:1];
- [segControl setImage:[NSImage imageNamed:@"Delete"] forSegment:0];
- [segControl setImage:[NSImage imageNamed:@"Delete"] forSegment:1];
- [segControl setSelectedSegment: (fShowsJobsAsGroups ? 0 : 1)];
- [segControl sizeToFit];
- [toolbarItem setMinSize: [segControl frame].size];
- [toolbarItem setMaxSize: [segControl frame].size];
- [toolbarItem setView: segControl];
-*/
-
-/*
- NSButton * button = [[[NSButton alloc] initWithFrame:NSMakeRect(0,0,20,20)] autorelease];
- [button setButtonType:NSSwitchButton];
- [button setTitle:@""];
- [button setState: fShowsJobsAsGroups ? NSOnState : NSOffState];
- [toolbarItem setMinSize: NSMakeSize(20,20)];
- [toolbarItem setMaxSize: NSMakeSize(20,20)];
- [toolbarItem setView: button];
-*/
-
- // Tell the item what message to send when it is clicked
- [toolbarItem setTarget: self];
- [toolbarItem setAction: @selector(jobGroupsChanged:)];
- }
+ // Tell the item what message to send when it is clicked
+ [toolbarItem setTarget: self];
+ [toolbarItem setAction: @selector(togglePauseResume:)];
+ }
return toolbarItem;
}
// toolbar by default.
return [NSArray arrayWithObjects:
- HBStartPauseResumeToolbarIdentifier,
- NSToolbarSeparatorItemIdentifier,
- HBShowGroupsToolbarIdentifier,
- HBShowDetailToolbarIdentifier,
+ HBQueueStartCancelToolbarIdentifier,
+ HBQueuePauseResumeToolbarIdentifier,
nil];
}
// separator. So, every allowed item must be explicitly listed.
return [NSArray arrayWithObjects:
- HBStartPauseResumeToolbarIdentifier,
- HBShowGroupsToolbarIdentifier,
- HBShowDetailToolbarIdentifier,
- NSToolbarCustomizeToolbarItemIdentifier,
- NSToolbarFlexibleSpaceItemIdentifier,
+ HBQueueStartCancelToolbarIdentifier,
+ HBQueuePauseResumeToolbarIdentifier,
+ NSToolbarCustomizeToolbarItemIdentifier,
+ NSToolbarFlexibleSpaceItemIdentifier,
NSToolbarSpaceItemIdentifier,
- NSToolbarSeparatorItemIdentifier,
+ NSToolbarSeparatorItemIdentifier,
nil];
}
hb_state_t s;
hb_get_state2 (fHandle, &s);
- if ([[toolbarItem itemIdentifier] isEqual: HBStartPauseResumeToolbarIdentifier])
+ if ([[toolbarItem itemIdentifier] isEqual: HBQueueStartCancelToolbarIdentifier])
{
- if (s.state == HB_STATE_PAUSED)
- {
- enable = YES;
- [toolbarItem setImage:[NSImage imageNamed: @"Play"]];
- [toolbarItem setLabel: @"Resume"];
- [toolbarItem setPaletteLabel: @"Resume"];
- [toolbarItem setToolTip: @"Resume Encoding"];
- }
-
- else if ((s.state == HB_STATE_WORKING) || (s.state == HB_STATE_MUXING))
+ if ((s.state == HB_STATE_PAUSED) || (s.state == HB_STATE_WORKING) || (s.state == HB_STATE_MUXING))
{
enable = YES;
- [toolbarItem setImage:[NSImage imageNamed: @"Pause"]];
- [toolbarItem setLabel: @"Pause"];
- [toolbarItem setPaletteLabel: @"Pause"];
- [toolbarItem setToolTip: @"Pause Encoding"];
+ [toolbarItem setImage:[NSImage imageNamed: @"Stop"]];
+ [toolbarItem setLabel: @"Stop"];
+ [toolbarItem setToolTip: @"Stop Encoding"];
}
- else if (hb_count(fHandle) > 0)
+ else if ([self pendingCount] > 0)
{
enable = YES;
[toolbarItem setImage:[NSImage imageNamed: @"Play"]];
- [toolbarItem setLabel: @"Start"];
- [toolbarItem setPaletteLabel: @"Start"];
- [toolbarItem setToolTip: @"Start Encoding"];
+ [toolbarItem setLabel: @"Start"];
+ [toolbarItem setToolTip: @"Start Encoding"];
}
else
{
enable = NO;
[toolbarItem setImage:[NSImage imageNamed: @"Play"]];
- [toolbarItem setLabel: @"Start"];
- [toolbarItem setPaletteLabel: @"Start"];
- [toolbarItem setToolTip: @"Start Encoding"];
- }
- }
-
-/* not used because HBShowGroupsToolbarIdentifier is now a custom view
- else if ([[toolbarItem itemIdentifier] isEqual: HBShowGroupsToolbarIdentifier])
- {
- enable = hb_count(fHandle) > 0;
- if (fShowsJobsAsGroups)
- {
- [toolbarItem setLabel: @"View Passes"];
- [toolbarItem setPaletteLabel: @"View Passes"];
- [toolbarItem setToolTip: @"Displays items in the queue as individual passes"];
- }
- else
- {
- [toolbarItem setLabel: @"View Encodes"];
- [toolbarItem setPaletteLabel: @"View Encodes"];
- [toolbarItem setToolTip: @"Displays items in the queue as encodes"];
+ [toolbarItem setLabel: @"Start"];
+ [toolbarItem setToolTip: @"Start Encoding"];
}
}
-*/
- else if ([[toolbarItem itemIdentifier] isEqual: HBShowDetailToolbarIdentifier])
+ if ([[toolbarItem itemIdentifier] isEqual: HBQueuePauseResumeToolbarIdentifier])
{
- enable = hb_count(fHandle) > 0;
- if (fShowsDetail)
+ if (s.state == HB_STATE_PAUSED)
+ {
+ enable = YES;
+ [toolbarItem setImage:[NSImage imageNamed: @"Play"]];
+ [toolbarItem setLabel: @"Resume"];
+ [toolbarItem setToolTip: @"Resume Encoding"];
+ }
+
+ else if ((s.state == HB_STATE_WORKING) || (s.state == HB_STATE_MUXING))
{
- [toolbarItem setLabel: @"Hide Detail"];
- [toolbarItem setPaletteLabel: @"Hide Detail"];
- [toolbarItem setToolTip: @"Displays detailed information in the queue"];
+ enable = YES;
+ [toolbarItem setImage:[NSImage imageNamed: @"Pause"]];
+ [toolbarItem setLabel: @"Pause"];
+ [toolbarItem setToolTip: @"Pause Encoding"];
}
else
{
- [toolbarItem setLabel: @"Show Detail"];
- [toolbarItem setPaletteLabel: @"Show Detail"];
- [toolbarItem setToolTip: @"Displays detailed information in the queue"];
+ enable = NO;
+ [toolbarItem setImage:[NSImage imageNamed: @"Pause"]];
+ [toolbarItem setLabel: @"Pause"];
+ [toolbarItem setToolTip: @"Pause Encoding"];
}
}
-
- return enable;
+
+ return enable;
}
#pragma mark -
[fQueueWindow center];
[fQueueWindow setFrameAutosaveName: @"Queue"];
[fQueueWindow setExcludedFromWindowsMenu:YES];
-
+
+#if HB_QUEUE_DRAGGING
+ [fOutlineView registerForDraggedTypes: [NSArray arrayWithObject:HBQueuePboardType] ];
+ [fOutlineView setDraggingSourceOperationMask:NSDragOperationEvery forLocal:YES];
+ [fOutlineView setVerticalMotionCanBeginDrag: YES];
+#endif
+
+ // Don't allow autoresizing of main column, else the "delete" column will get
+ // pushed out of view.
+ [fOutlineView setAutoresizesOutlineColumn: NO];
+
+#if HB_OUTLINE_METRIC_CONTROLS
+ [fIndentation setHidden: NO];
+ [fSpacing setHidden: NO];
+ [fIndentation setIntValue:[fOutlineView indentationPerLevel]]; // debug
+ [fSpacing setIntValue:3]; // debug
+#endif
+
// Show/hide UI elements
- [self setShowsDetail:fShowsDetail];
- [self setShowsJobsAsGroups:fShowsJobsAsGroups];
+ fCurrentJobPaneShown = YES; // it's shown in the nib
[self showCurrentJobPane:NO];
+
+ [self updateQueueCountField];
}
}
#pragma mark -
-#pragma mark NSTableView delegate
-//------------------------------------------------------------------------------------
-// NSTableView delegate
-//------------------------------------------------------------------------------------
-- (int)numberOfRowsInTableView: (NSTableView *)aTableView
+- (void)moveObjectsInArray:(NSMutableArray *)array fromIndexes:(NSIndexSet *)indexSet toIndex:(unsigned)insertIndex
{
- BOOL jobGroups = [[NSUserDefaults standardUserDefaults] boolForKey:@"QueueShowsJobsAsGroups"];
- if (jobGroups)
- return hb_group_count(fHandle);
- else
- return hb_count(fHandle);
+ unsigned index = [indexSet lastIndex];
+ unsigned aboveInsertIndexCount = 0;
+
+ while (index != NSNotFound)
+ {
+ unsigned removeIndex;
+
+ if (index >= insertIndex)
+ {
+ removeIndex = index + aboveInsertIndexCount;
+ aboveInsertIndexCount++;
+ }
+ else
+ {
+ removeIndex = index;
+ insertIndex--;
+ }
+
+ id object = [[array objectAtIndex:removeIndex] retain];
+ [array removeObjectAtIndex:removeIndex];
+ [array insertObject:object atIndex:insertIndex];
+ [object release];
+
+ index = [indexSet indexLessThanIndex:index];
+ }
}
-//------------------------------------------------------------------------------------
-// NSTableView delegate
-//------------------------------------------------------------------------------------
-- (id)tableView: (NSTableView *)aTableView
- objectValueForTableColumn: (NSTableColumn *)aTableColumn
- row: (int)rowIndex
+#pragma mark -
+#pragma mark NSOutlineView delegate
+
+- (id)outlineView:(NSOutlineView *)outlineView child:(int)index ofItem:(id)item
{
- if (!fHandle)
- return @""; // fatal error!
-
- hb_job_t * job;
+ if (item == nil)
+ return [fJobGroups objectAtIndex:index];
+
+ // We are only one level deep, so we can't be asked about children
+ NSAssert (NO, @"HBQueueController outlineView:child:ofItem: can't handle nested items.");
+ return nil;
+}
+
+- (BOOL)outlineView:(NSOutlineView *)outlineView isItemExpandable:(id)item
+{
+ // Our outline view has no levels, but we can still expand every item. Doing so
+ // just makes the row taller. See heightOfRowByItem below.
+ return YES;
+}
- BOOL jobGroups = [[NSUserDefaults standardUserDefaults] boolForKey:@"QueueShowsJobsAsGroups"];
- if (jobGroups)
- job = hb_group(fHandle, rowIndex);
+- (BOOL)outlineView:(NSOutlineView *)outlineView shouldExpandItem:(id)item
+{
+ // Our outline view has no levels, but we can still expand every item. Doing so
+ // just makes the row taller. See heightOfRowByItem below.
+#if HB_QUEUE_DRAGGING
+ // Don't autoexpand while dragging, since we can't drop into the items
+ return ![(HBQueueOutlineView*)outlineView isDragging];
+#else
+ return YES;
+#endif
+}
+
+- (int)outlineView:(NSOutlineView *)outlineView numberOfChildrenOfItem:(id)item
+{
+ // Our outline view has no levels, so number of children will be zero for all
+ // top-level items.
+ if (item == nil)
+ return [fJobGroups count];
else
- job = hb_job(fHandle, rowIndex);
+ return 0;
+}
+
+- (void)outlineViewItemDidCollapse:(NSNotification *)notification
+{
+ id item = [[notification userInfo] objectForKey:@"NSObject"];
+ int row = [fOutlineView rowForItem:item];
+ [fOutlineView noteHeightOfRowsWithIndexesChanged:[NSIndexSet indexSetWithIndexesInRange:NSMakeRange(row,1)]];
+}
- if (!job)
- return @""; // fatal error!
+- (void)outlineViewItemDidExpand:(NSNotification *)notification
+{
+ id item = [[notification userInfo] objectForKey:@"NSObject"];
+ int row = [fOutlineView rowForItem:item];
+ [fOutlineView noteHeightOfRowsWithIndexesChanged:[NSIndexSet indexSetWithIndexesInRange:NSMakeRange(row,1)]];
+}
- if ([[aTableColumn identifier] isEqualToString:@"desc"])
+- (float)outlineView:(NSOutlineView *)outlineView heightOfRowByItem:(id)item
+{
+ if ([outlineView isItemExpanded: item])
{
- BOOL highlighted = [aTableView isRowSelected:rowIndex] && [[aTableView window] isKeyWindow] && ([[aTableView window] firstResponder] == aTableView);
- return [self attributedDescriptionForJob:job withDetail:fShowsDetail withHighlighting:highlighted];
+ // Short-circuit here if in a live resize primarily to fix a bug but also to
+ // increase resposivness during a resize. There's a bug in NSTableView that
+ // causes row heights to get messed up if you try to change them during a live
+ // resize. So if in a live resize, simply return the previously calculated
+ // height. The row heights will get fixed up after the resize because we have
+ // implemented viewDidEndLiveResize to force all of them to be recalculated.
+ if ([outlineView inLiveResize] && [item lastDescriptionHeight] > 0)
+ return [item lastDescriptionHeight];
+
+ float width = [[outlineView tableColumnWithIdentifier: @"desc"] width];
+ // Column width is NOT what is ultimately used. I can't quite figure out what
+ // width to use for calculating text metrics. No matter how I tweak this value,
+ // there are a few conditions in which the drawn text extends below the bounds
+ // of the row cell. In previous versions, which ran under Tiger, I was
+ // reducing width by 47 pixles.
+ width -= 2; // (?) for intercell spacing
+
+ float height = [item heightOfDescriptionForWidth: width];
+ return height;
}
-
- else if ([[aTableColumn identifier] isEqualToString:@"delete"])
- return @"";
-
- else if ([[aTableColumn identifier] isEqualToString:@"icon"])
- return fShowsJobsAsGroups ? [NSImage imageNamed:@"JobSmall"] : [NSImage imageNamed:@"JobPassSmall"];
+ else
+ return HB_ROW_HEIGHT_TITLE_ONLY;
+}
- return @"";
+- (id)outlineView:(NSOutlineView *)outlineView objectValueForTableColumn:(NSTableColumn *)tableColumn byItem:(id)item
+{
+ // nb: The "desc" column is currently an HBImageAndTextCell. However, we are longer
+ // using the image portion of the cell so we could switch back to a regular NSTextFieldCell.
+
+ if ([[tableColumn identifier] isEqualToString:@"desc"])
+ return [item attributedDescription];
+ else if ([[tableColumn identifier] isEqualToString:@"icon"])
+ {
+ switch ([(HBJobGroup*)item status])
+ {
+ case HBStatusCanceled:
+ return [NSImage imageNamed:@"EncodeCanceled"];
+ break;
+ case HBStatusCompleted:
+ return [NSImage imageNamed:@"EncodeComplete"];
+ break;
+ case HBStatusWorking:
+ return [NSImage imageNamed: [NSString stringWithFormat: @"EncodeWorking%d", fAnimationIndex]];
+ break;
+ default:
+ return [NSImage imageNamed:@"JobSmall"];
+ break;
+ }
+ }
+ else
+ return @"";
}
-//------------------------------------------------------------------------------------
-// NSTableView delegate
-//------------------------------------------------------------------------------------
-- (void)tableView: (NSTableView *)aTableView
- willDisplayCell: (id)aCell
- forTableColumn: (NSTableColumn *)aTableColumn
- row: (int)rowIndex
+- (void)outlineView:(NSOutlineView *)outlineView willDisplayCell:(id)cell forTableColumn:(NSTableColumn *)tableColumn item:(id)item
{
- if ([[aTableColumn identifier] isEqualToString:@"delete"])
+ if ([[tableColumn identifier] isEqualToString:@"desc"])
+ {
+#if HB_OUTLINE_METRIC_CONTROLS
+ NSSize theSize = [cell imageSpacing];
+ theSize.width = spacingWidth;
+ [cell setImageSpacing: theSize];
+#endif
+
+ // nb: The "desc" column is currently an HBImageAndTextCell. However, we are longer
+ // using the image portion of the cell so we could switch back to a regular NSTextFieldCell.
+
+ // Set the image here since the value returned from outlineView:objectValueForTableColumn: didn't specify the image part
+ [cell setImage:nil];
+ }
+
+ else if ([[tableColumn identifier] isEqualToString:@"action"])
{
- BOOL highlighted = [aTableView isRowSelected:rowIndex] && [[aTableView window] isKeyWindow] && ([[aTableView window] firstResponder] == aTableView);
- if (highlighted)
+ [cell setEnabled: YES];
+ BOOL highlighted = [outlineView isRowSelected:[outlineView rowForItem: item]] && [[outlineView window] isKeyWindow] && ([[outlineView window] firstResponder] == outlineView);
+ if ([(HBJobGroup*)item status] == HBStatusCompleted)
{
- [aCell setImage:[NSImage imageNamed:@"DeleteHighlight"]];
- [aCell setAlternateImage:[NSImage imageNamed:@"DeleteHighlightPressed"]];
+ [cell setAction: @selector(revealSelectedJobGroups:)];
+ if (highlighted)
+ {
+ [cell setImage:[NSImage imageNamed:@"RevealHighlight"]];
+ [cell setAlternateImage:[NSImage imageNamed:@"RevealHighlightPressed"]];
+ }
+ else
+ [cell setImage:[NSImage imageNamed:@"Reveal"]];
}
else
{
- [aCell setImage:[NSImage imageNamed:@"Delete"]];
+ [cell setAction: @selector(removeSelectedJobGroups:)];
+ if (highlighted)
+ {
+ [cell setImage:[NSImage imageNamed:@"DeleteHighlight"]];
+ [cell setAlternateImage:[NSImage imageNamed:@"DeleteHighlightPressed"]];
+ }
+ else
+ [cell setImage:[NSImage imageNamed:@"Delete"]];
}
}
}
+- (void)outlineView:(NSOutlineView *)outlineView willDisplayOutlineCell:(id)cell forTableColumn:(NSTableColumn *)tableColumn item:(id)item
+{
+ // By default, the discolsure image gets centered vertically in the cell. We want
+ // always at the top.
+ if ([outlineView isItemExpanded: item])
+ [cell setImagePosition: NSImageAbove];
+ else
+ [cell setImagePosition: NSImageOnly];
+}
+
+#pragma mark -
+#pragma mark NSOutlineView delegate (dragging related)
+
+//------------------------------------------------------------------------------------
+// NSTableView delegate
+//------------------------------------------------------------------------------------
+
+#if HB_QUEUE_DRAGGING
+- (BOOL)outlineView:(NSOutlineView *)outlineView writeItems:(NSArray *)items toPasteboard:(NSPasteboard *)pboard
+{
+ // Dragging is only allowed of the pending items.
+ NSEnumerator * e = [items objectEnumerator];
+ HBJobGroup * group;
+ while ( (group = [e nextObject]) )
+ {
+ if ([group status] != HBStatusPending)
+ return NO;
+ }
+
+ // Don't retain since this is just holding temporaral drag information, and it is
+ //only used during a drag! We could put this in the pboard actually.
+ fDraggedNodes = items;
+
+ // Provide data for our custom type, and simple NSStrings.
+ [pboard declareTypes:[NSArray arrayWithObjects: HBQueuePboardType, nil] owner:self];
+
+ // the actual data doesn't matter since DragDropSimplePboardType drags aren't recognized by anyone but us!.
+ [pboard setData:[NSData data] forType:HBQueuePboardType];
+
+ return YES;
+}
+#endif
+
+#if HB_QUEUE_DRAGGING
+- (NSDragOperation)outlineView:(NSOutlineView *)outlineView validateDrop:(id <NSDraggingInfo>)info proposedItem:(id)item proposedChildIndex:(int)index
+{
+ // Don't allow dropping ONTO an item since they can't really contain any children.
+ BOOL isOnDropTypeProposal = index == NSOutlineViewDropOnItemIndex;
+ if (isOnDropTypeProposal)
+ return NSDragOperationNone;
+
+ // Don't allow dropping INTO an item since they can't really contain any children.
+ if (item != nil)
+ {
+ index = [fOutlineView rowForItem: item] + 1;
+ item = nil;
+ }
+
+ // Prevent dragging into the completed or current job.
+ int firstPendingIndex = [fCompleted count];
+ if (fCurrentJobGroup)
+ firstPendingIndex++;
+ index = MAX (index, firstPendingIndex);
+
+ [outlineView setDropItem:item dropChildIndex:index];
+ return NSDragOperationGeneric;
+}
+#endif
+
+#if HB_QUEUE_DRAGGING
+- (BOOL)outlineView:(NSOutlineView *)outlineView acceptDrop:(id <NSDraggingInfo>)info item:(id)item childIndex:(int)index
+{
+ NSMutableIndexSet *moveItems = [NSMutableIndexSet indexSet];
+
+ id obj;
+ NSEnumerator *enumerator = [fDraggedNodes objectEnumerator];
+ while (obj = [enumerator nextObject])
+ {
+ [moveItems addIndex:[fJobGroups indexOfObject:obj]];
+ }
+
+ // Rearrange the data and view
+ [self saveOutlineViewState];
+ [self moveObjectsInArray:fJobGroups fromIndexes:moveItems toIndex: index];
+ [fOutlineView reloadData];
+ [self restoreOutlineViewState];
+
+ return YES;
+}
+#endif
+
+
@end