#include "Controller.h"
#import "HBImageAndTextCell.h"
-// UNI_QUEUE turns on the feature where the first item in the queue NSTableView is the
-// current job followed by the jobs in hblib's queue. In this scheme, fCurrentJobPane
-// disappers.
-#define HB_UNI_QUEUE 0 // <--- NOT COMPLETELY FUNCTIONAL YET
-
#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)
+{
+ return jobGroupID<<16 | sequenceNum;
+}
+
+bool IsFirstPass(int jobID)
+{
+ return LoWord(jobID) == 0;
+}
//------------------------------------------------------------------------------------
// NSMutableAttributedString (HBAdditions)
[super viewDidEndLiveResize];
}
-@end
-
-//------------------------------------------------------------------------------------
-#pragma mark -
-#pragma mark Job group functions
-//------------------------------------------------------------------------------------
-// These could be part of hblib if we think hblib should have knowledge of groups.
-// For now, I see groups as a metaphor that HBQueueController provides.
-
-/**
- * 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)
+#if HB_QUEUE_DRAGGING
+- (NSImage *)dragImageForRowsWithIndexes:(NSIndexSet *)dragRows tableColumns:(NSArray *)tableColumns event:(NSEvent*)dragEvent offset:(NSPointPointer)dragImageOffset
{
- hb_job_t * job;
- int count = 0;
- int index = 0;
- while( ( job = hb_job( h, index++ ) ) )
- {
- if (job->sequence_id == 0)
- count++;
- }
- return count;
+ // 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
-/**
- * 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)
+#if HB_QUEUE_DRAGGING
+- (void) mouseDown:(NSEvent *)theEvent
{
- hb_job_t * job;
- int count = 0;
- int index = 0;
- while( ( job = hb_job( h, index++ ) ) )
- {
- if (job->sequence_id == 0)
- {
- if (count == i)
- return job;
- count++;
- }
- }
- return NULL;
+ // 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
-/**
- * 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 )
+#if HB_QUEUE_DRAGGING
+- (BOOL) isDragging;
{
- // Find job in list
- hb_job_t * j;
- int index = 0;
- while( ( j = hb_job( h, index ) ) )
- {
- if (j == job)
- {
- // 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;
- }
- else
- index++;
- }
+ return fIsDragging;
}
+#endif
-/**
- * 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 )
-{
- hb_job_t * j = NULL;
- int index = 0;
- while( ( j = hb_job( h, index++ ) ) )
- {
- if (j == job)
- return hb_job( h, index );
- }
- return NULL;
-}
+@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*) jobWithJob: (hb_job_t *) job
++ (HBJob*) jobWithLibhbJob: (hb_job_t *) job
{
- return [[[HBJob alloc] initWithJob:job] autorelease];
+ return [[[HBJob alloc] initWithLibhbJob:job] autorelease];
}
-- (id) initWithJob: (hb_job_t *) job
+- (id) initWithLibhbJob: (hb_job_t *) job
{
if (self = [super init])
{
- // job is not owned by HBJob. It does not get dealloacted when HBJob is released.
- hbJob = job;
+ 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];
+ memcpy(audio_mixdowns, job->audio_mixdowns, sizeof(audio_mixdowns));
+ acodec = job->acodec;
+ abitrate = job->abitrate;
+ arate = job->arate;
+ 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 self;
+ return self;
+}
+
+- (void) dealloc
+{
+ // jobGroup is a weak reference and does not need to be deleted
+ [x264opts release];
+ [file release];
+ [titleName release];
+ [subtitleLang release];
+ [super dealloc];
+}
+
+- (HBJobGroup *) jobGroup
+{
+ return jobGroup;
}
-- (hb_job_t*) job
+- (void) setJobGroup: (HBJobGroup *)aJobGroup
{
- return hbJob;
+ // This is a weak reference. We don't retain or release it.
+ jobGroup = aJobGroup;
}
//------------------------------------------------------------------------------------
// Generate string to display in UI.
//------------------------------------------------------------------------------------
-- (NSMutableAttributedString *) attributedDescriptionWithHBHandle: (hb_handle_t *)handle
- withIcon: (BOOL)withIcon
+- (NSMutableAttributedString *) attributedDescriptionWithIcon: (BOOL)withIcon
withTitle: (BOOL)withTitle
withPassName: (BOOL)withPassName
withFormatInfo: (BOOL)withFormatInfo
{
NSMutableAttributedString * finalString = [[[NSMutableAttributedString alloc] initWithString: @""] autorelease];
- hb_title_t * title = hbJob->title;
-
// Attributes
- static NSMutableParagraphStyle * ps = NULL;
- if (!ps)
- {
- ps = [[[NSParagraphStyle defaultParagraphStyle] mutableCopy] retain];
- [ps setHeadIndent: 40.0];
- [ps setParagraphSpacing: 1.0];
- [ps setTabStops:[NSArray array]]; // clear all tabs
- [ps addTabStop: [[[NSTextTab alloc] initWithType: NSLeftTabStopType location: 20.0] autorelease]];
- }
-
- static NSDictionary* detailAttribute = [[NSDictionary dictionaryWithObjectsAndKeys:
- [NSFont systemFontOfSize:10.0], NSFontAttributeName,
- ps, NSParagraphStyleAttributeName,
- nil] retain];
- static NSDictionary* detailBoldAttribute = [[NSDictionary dictionaryWithObjectsAndKeys:
- [NSFont boldSystemFontOfSize:10.0], NSFontAttributeName,
- ps, NSParagraphStyleAttributeName,
- nil] retain];
- static NSDictionary* titleAttribute = [[NSDictionary dictionaryWithObjectsAndKeys:
- [NSFont systemFontOfSize:[NSFont systemFontSize]], NSFontAttributeName,
- ps, NSParagraphStyleAttributeName,
- nil] retain];
- static NSDictionary* shortHeightAttribute = [[NSDictionary dictionaryWithObjectsAndKeys:
- [NSFont systemFontOfSize:2.0], NSFontAttributeName,
- nil] retain];
+ 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)
// 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:[NSString stringWithUTF8String:title->name] withAttributes:titleAttribute];
+ [finalString appendString:titleName withAttributes:titleAttr];
NSString * summaryInfo;
- NSString * chapterString = (hbJob->chapter_start == hbJob->chapter_end) ?
- [NSString stringWithFormat:@"Chapter %d", hbJob->chapter_start] :
- [NSString stringWithFormat:@"Chapters %d through %d", hbJob->chapter_start, hbJob->chapter_end];
+ NSString * chapterString = (chapter_start == chapter_end) ?
+ [NSString stringWithFormat:@"Chapter %d", chapter_start] :
+ [NSString stringWithFormat:@"Chapters %d through %d", chapter_start, chapter_end];
- BOOL hasIndepthScan = (hbJob->pass == -1);
+ BOOL hasIndepthScan = (pass == -1);
int numVideoPasses = 0;
// To determine number of video passes, we need to skip past the subtitle scan.
{
// 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 (hbJob == hb_current_job(handle))
- nextjob = hb_job(handle, 0);
- else
- nextjob = hb_next_job(handle, hbJob);
+ 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, hbJob->pass + 1 );
+ numVideoPasses = MIN( 2, pass + 1 );
if (hasIndepthScan && numVideoPasses == 1)
- summaryInfo = [NSString stringWithFormat: @" (Title %d, %@, Deep Scan, Single Video Pass)", title->index, chapterString];
+ 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)", title->index, chapterString, numVideoPasses];
+ 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)", title->index, chapterString];
+ summaryInfo = [NSString stringWithFormat: @" (Title %d, %@, Single Video Pass)", titleIndex, chapterString];
else
- summaryInfo = [NSString stringWithFormat: @" (Title %d, %@, %d Video Passes)", title->index, chapterString, numVideoPasses];
+ summaryInfo = [NSString stringWithFormat: @" (Title %d, %@, %d Video Passes)", titleIndex, chapterString, numVideoPasses];
- [finalString appendString:[NSString stringWithFormat:@"%@\n", summaryInfo] withAttributes:detailAttribute];
+ [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:shortHeightAttribute];
+ [finalString appendString:@"\n" withAttributes:shortHeightAttr];
}
// End of title stuff
if (withIcon)
{
NSString * imageName;
- switch (hbJob->pass)
+ switch (pass)
{
case -1: imageName = @"JobPassSubtitleSmall"; break;
case 0: imageName = @"JobPassFirstSmall"; break;
}
NSString * jobPassName;
- if (hbJob->pass == -1)
+ if (pass == -1)
jobPassName = NSLocalizedString (@"Deep Scan", nil);
else
{
- int passNum = MAX( 1, hbJob->pass );
+ int passNum = MAX( 1, pass );
if (passNum == 0)
jobPassName = NSLocalizedString (@"1st Pass", nil);
else if (passNum == 1)
else
jobPassName = [NSString stringWithFormat: NSLocalizedString(@"Pass %d", nil), passNum];
}
- [finalString appendString:[NSString stringWithFormat:@"%@\n", jobPassName] withAttributes:detailBoldAttribute];
+ [finalString appendString:[NSString stringWithFormat:@"%@\n", jobPassName] withAttributes:detailBoldAttr];
}
// Video Codec needed by FormatInfo and withVideoInfo
if (withFormatInfo || withVideoInfo)
{
// 2097152
- /* Video Codec settings (Encoder in the gui) */
- if (hbJob->vcodec == HB_VCODEC_FFMPEG)
+ // Video Codec settings (Encoder in the gui)
+ if (vcodec == HB_VCODEC_FFMPEG)
jobVideoCodec = @"FFmpeg"; // HB_VCODEC_FFMPEG
- else if (hbJob->vcodec == HB_VCODEC_XVID)
+ else if (vcodec == HB_VCODEC_XVID)
jobVideoCodec = @"XviD"; // HB_VCODEC_XVID
- else if (hbJob->vcodec == HB_VCODEC_X264)
+ else if (vcodec == HB_VCODEC_X264)
{
- /* Deterimine for sure how we are now setting iPod uuid atom */
- if (hbJob->h264_level) // We are encoding for iPod
+ // 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
NSString * jobAudioCodec = nil;
if (withFormatInfo || withAudioInfo)
{
- if (hbJob->acodec == 256)
+ if (acodec == 256)
jobAudioCodec = @"AAC"; // HB_ACODEC_FAAC
- else if (hbJob->acodec == 512)
+ else if (acodec == 512)
jobAudioCodec = @"MP3"; // HB_ACODEC_LAME
- else if (hbJob->acodec == 1024)
+ else if (acodec == 1024)
jobAudioCodec = @"Vorbis"; // HB_ACODEC_VORBIS
- else if (hbJob->acodec == 2048)
+ else if (acodec == 2048)
jobAudioCodec = @"AC3"; // HB_ACODEC_AC3
}
if (jobAudioCodec == nil)
{
NSString * jobFormatInfo;
// Muxer settings (File Format in the gui)
- if (hbJob->mux == 65536 || hbJob->mux == 131072 || hbJob->mux == 1048576)
+ if (mux == 65536 || mux == 131072 || mux == 1048576)
jobFormatInfo = @"MP4"; // HB_MUX_MP4,HB_MUX_PSP,HB_MUX_IPOD
- else if (hbJob->mux == 262144)
+ else if (mux == 262144)
jobFormatInfo = @"AVI"; // HB_MUX_AVI
- else if (hbJob->mux == 524288)
+ else if (mux == 524288)
jobFormatInfo = @"OGM"; // HB_MUX_OGM
- else if (hbJob->mux == 2097152)
+ else if (mux == 2097152)
jobFormatInfo = @"MKV"; // HB_MUX_MKV
else
jobFormatInfo = @"unknown";
- if (hbJob->chapter_markers == 1)
+ if (chapter_markers == 1)
jobFormatInfo = [NSString stringWithFormat:@"%@ Container, %@ Video + %@ Audio, Chapter Markers\n", jobFormatInfo, jobVideoCodec, jobAudioCodec];
else
jobFormatInfo = [NSString stringWithFormat:@"%@ Container, %@ Video + %@ Audio\n", jobFormatInfo, jobVideoCodec, jobAudioCodec];
- [finalString appendString: @"Format: " withAttributes:detailBoldAttribute];
- [finalString appendString: jobFormatInfo withAttributes:detailAttribute];
+ [finalString appendString: @"Format: " withAttributes:detailBoldAttr];
+ [finalString appendString: jobFormatInfo withAttributes:detailAttr];
}
if (withDestination)
{
- [finalString appendString: @"Destination: " withAttributes:detailBoldAttribute];
- [finalString appendString:[NSString stringWithFormat:@"%@\n", [NSString stringWithUTF8String:hbJob->file]] withAttributes:detailAttribute];
+ [finalString appendString: @"Destination: " withAttributes:detailBoldAttr];
+ [finalString appendString:[NSString stringWithFormat:@"%@\n", file] withAttributes:detailAttr];
}
if (withPictureInfo)
{
NSString * jobPictureInfo;
- /*integers for picture values deinterlace, crop[4], keep_ratio, grayscale, pixel_ratio, pixel_aspect_width, pixel_aspect_height,
- maxWidth, maxHeight */
- if (hbJob->pixel_ratio == 1)
- {
- int titlewidth = title->width - hbJob->crop[2] - hbJob->crop[3];
- int displayparwidth = titlewidth * hbJob->pixel_aspect_width / hbJob->pixel_aspect_height;
- int displayparheight = title->height - hbJob->crop[0] - hbJob->crop[1];
- jobPictureInfo = [NSString stringWithFormat:@"%dx%d (%dx%d Anamorphic)", displayparwidth, displayparheight, hbJob->width, displayparheight];
- }
+ 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:@"%dx%d", hbJob->width, hbJob->height];
- if (hbJob->keep_ratio == 1)
+ jobPictureInfo = [NSString stringWithFormat:@"%d x %d", output_width, output_height];
+ if (keep_ratio == 1)
jobPictureInfo = [jobPictureInfo stringByAppendingString:@" Keep Aspect Ratio"];
- if (hbJob->grayscale == 1)
+ if (grayscale == 1)
jobPictureInfo = [jobPictureInfo stringByAppendingString:@", Grayscale"];
- if (hbJob->deinterlace == 1)
+ if (deinterlace == 1)
jobPictureInfo = [jobPictureInfo stringByAppendingString:@", Deinterlace"];
if (withIcon) // implies indent the info
- [finalString appendString: @"\t" withAttributes:detailBoldAttribute];
- [finalString appendString: @"Picture: " withAttributes:detailBoldAttribute];
- [finalString appendString:[NSString stringWithFormat:@"%@\n", jobPictureInfo] withAttributes:detailAttribute];
+ [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 (hbJob->vquality <= 0 || hbJob->vquality >= 1)
- jobVideoQuality = [NSString stringWithFormat:@"%d kbps", hbJob->vbitrate];
+ if (vquality <= 0 || vquality >= 1)
+ jobVideoQuality = [NSString stringWithFormat:@"%d kbps", vbitrate];
else
{
NSNumber * vidQuality;
- vidQuality = [NSNumber numberWithInt:hbJob->vquality * 100];
+ vidQuality = [NSNumber numberWithInt:vquality * 100];
// this is screwed up kind of. Needs to be formatted properly.
- if (hbJob->crf == 1)
+ if (crf == 1)
jobVideoQuality = [NSString stringWithFormat:@"%@%% CRF", vidQuality];
else
jobVideoQuality = [NSString stringWithFormat:@"%@%% CQP", vidQuality];
}
- if (hbJob->vrate_base == 1126125)
+ if (vrate_base == 1126125)
{
- /* NTSC FILM 23.976 */
+ // NTSC FILM 23.976
jobVideoDetail = [NSString stringWithFormat:@"%@, %@, 23.976 fps", jobVideoCodec, jobVideoQuality];
}
- else if (hbJob->vrate_base == 900900)
+ else if (vrate_base == 900900)
{
- /* NTSC 29.97 */
+ // NTSC 29.97
jobVideoDetail = [NSString stringWithFormat:@"%@, %@, 29.97 fps", jobVideoCodec, jobVideoQuality];
}
else
{
- /* Everything else */
- jobVideoDetail = [NSString stringWithFormat:@"%@, %@, %d fps", jobVideoCodec, jobVideoQuality, hbJob->vrate / hbJob->vrate_base];
+ // Everything else
+ jobVideoDetail = [NSString stringWithFormat:@"%@, %@, %d fps", jobVideoCodec, jobVideoQuality, vrate / vrate_base];
}
if (withIcon) // implies indent the info
- [finalString appendString: @"\t" withAttributes:detailBoldAttribute];
- [finalString appendString: @"Video: " withAttributes:detailBoldAttribute];
- [finalString appendString:[NSString stringWithFormat:@"%@\n", jobVideoDetail] withAttributes:detailAttribute];
+ [finalString appendString: @"\t" withAttributes:detailBoldAttr];
+ [finalString appendString: @"Video: " withAttributes:detailBoldAttr];
+ [finalString appendString:[NSString stringWithFormat:@"%@\n", jobVideoDetail] withAttributes:detailAttr];
}
if (withx264Info)
{
- if (hbJob->vcodec == HB_VCODEC_X264 && hbJob->x264opts)
+ if (vcodec == HB_VCODEC_X264 && x264opts)
{
if (withIcon) // implies indent the info
- [finalString appendString: @"\t" withAttributes:detailBoldAttribute];
- [finalString appendString: @"x264 Options: " withAttributes:detailBoldAttribute];
- [finalString appendString:[NSString stringWithFormat:@"%@\n", [NSString stringWithUTF8String:hbJob->x264opts]] withAttributes:detailAttribute];
+ [finalString appendString: @"\t" withAttributes:detailBoldAttr];
+ [finalString appendString: @"x264 Options: " withAttributes:detailBoldAttr];
+ [finalString appendString:[NSString stringWithFormat:@"%@\n", x264opts] withAttributes:detailAttr];
}
}
if ([jobAudioCodec isEqualToString: @"AC3"])
jobAudioInfo = [NSString stringWithFormat:@"%@, Pass-Through", jobAudioCodec];
else
- jobAudioInfo = [NSString stringWithFormat:@"%@, %d kbps, %d Hz", jobAudioCodec, hbJob->abitrate, hbJob->arate];
+ jobAudioInfo = [NSString stringWithFormat:@"%@, %d kbps, %d Hz", jobAudioCodec, abitrate, 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] */
+ // 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 (hbJob->audio_mixdowns[ai] == HB_AMIXDOWN_MONO)
+ if (audio_mixdowns[ai] == HB_AMIXDOWN_MONO)
jobAudioInfo = [jobAudioInfo stringByAppendingString:[NSString stringWithFormat:@", Track %d: Mono", ai + 1]];
- if (hbJob->audio_mixdowns[ai] == HB_AMIXDOWN_STEREO)
+ if (audio_mixdowns[ai] == HB_AMIXDOWN_STEREO)
jobAudioInfo = [jobAudioInfo stringByAppendingString:[NSString stringWithFormat:@", Track %d: Stereo", ai + 1]];
- if (hbJob->audio_mixdowns[ai] == HB_AMIXDOWN_DOLBY)
+ if (audio_mixdowns[ai] == HB_AMIXDOWN_DOLBY)
jobAudioInfo = [jobAudioInfo stringByAppendingString:[NSString stringWithFormat:@", Track %d: Dolby Surround", ai + 1]];
- if (hbJob->audio_mixdowns[ai] == HB_AMIXDOWN_DOLBYPLII)
+ if (audio_mixdowns[ai] == HB_AMIXDOWN_DOLBYPLII)
jobAudioInfo = [jobAudioInfo stringByAppendingString:[NSString stringWithFormat:@", Track %d: Dolby Pro Logic II", ai + 1]];
- if (hbJob->audio_mixdowns[ai] == HB_AMIXDOWN_6CH)
- jobAudioInfo = [jobAudioInfo stringByAppendingString:[NSString stringWithFormat:@", Track %d: 6-channel discreet", ai + 1]];
+ if (audio_mixdowns[ai] == HB_AMIXDOWN_AC3)
+ jobAudioInfo = [jobAudioInfo stringByAppendingString:[NSString stringWithFormat:@", Pass-Through", ai + 1]];
+
}
if (withIcon) // implies indent the info
- [finalString appendString: @"\t" withAttributes:detailBoldAttribute];
- [finalString appendString: @"Audio: " withAttributes:detailBoldAttribute];
- [finalString appendString:[NSString stringWithFormat:@"%@\n", jobAudioInfo] withAttributes:detailAttribute];
+ [finalString appendString: @"\t" withAttributes:detailBoldAttr];
+ [finalString appendString: @"Audio: " withAttributes:detailBoldAttr];
+ [finalString appendString:[NSString stringWithFormat:@"%@\n", jobAudioInfo] withAttributes:detailAttr];
}
if (withSubtitleInfo)
{
- // hbJob->subtitle can == -1 in two cases:
+ // subtitle scan == -1 in two cases:
// autoselect: when pass == -1
// none: when pass != -1
- if ((hbJob->subtitle == -1) && (hbJob->pass == -1))
+ if ((subtitle == -1) && (pass == -1))
{
if (withIcon) // implies indent the info
- [finalString appendString: @"\t" withAttributes:detailBoldAttribute];
- [finalString appendString: @"Subtitles: " withAttributes:detailBoldAttribute];
- [finalString appendString: @"Autoselect " withAttributes:detailAttribute];
+ [finalString appendString: @"\t" withAttributes:detailBoldAttr];
+ [finalString appendString: @"Subtitles: " withAttributes:detailBoldAttr];
+ [finalString appendString: @"Autoselect " withAttributes:detailAttr];
}
- else if (hbJob->subtitle >= 0)
+ else if (subtitle >= 0)
{
- hb_subtitle_t * subtitle = (hb_subtitle_t *) hb_list_item( title->list_subtitle, 0 );
- if (subtitle)
+ if (subtitleLang)
{
if (withIcon) // implies indent the info
- [finalString appendString: @"\t" withAttributes:detailBoldAttribute];
- [finalString appendString: @"Subtitles: " withAttributes:detailBoldAttribute];
- [finalString appendString: [NSString stringWithCString: subtitle->lang] withAttributes:detailAttribute];
+ [finalString appendString: @"\t" withAttributes:detailBoldAttr];
+ [finalString appendString: @"Subtitles: " withAttributes:detailBoldAttr];
+ [finalString appendString: subtitleLang withAttributes:detailAttr];
}
}
}
return finalString;
}
++ (NSMutableParagraphStyle *) descriptionParagraphStyle
+{
+ if (!_descriptionParagraphStyle)
+ {
+ _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 _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;
fJobs = [[NSMutableArray arrayWithCapacity:0] retain];
fDescription = [[NSMutableAttributedString alloc] initWithString: @""];
[self setNeedsDescription: NO];
+ fStatus = HBStatusNone;
}
return self;
}
- (void) dealloc
{
+ [fPresetName release];
[fJobs release];
[super dealloc];
}
- (void) addJob: (HBJob *)aJob
{
+ [aJob setJobGroup:self];
[fJobs addObject: aJob];
[self setNeedsDescription: YES];
fLastDescriptionHeight = 0;
fNeedsDescription = flag;
}
-- (void) updateDescriptionWithHBHandle: (hb_handle_t *)handle
+- (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];
- [fDescription appendAttributedString: [job attributedDescriptionWithHBHandle: handle
- withIcon: NO
+ // 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
withAudioInfo: NO
withSubtitleInfo: NO]];
+
static NSAttributedString * carriageReturn = [[NSAttributedString alloc] initWithString:@"\n"];
NSEnumerator * e = [self jobEnumerator];
while ( (job = [e nextObject]) )
{
- int pass = [job job]->pass;
+ int pass = job->pass;
[fDescription appendAttributedString:carriageReturn];
[fDescription appendAttributedString:
- [job attributedDescriptionWithHBHandle: handle
- withIcon: YES
+ [job attributedDescriptionWithIcon: YES
withTitle: NO
withPassName: YES
withFormatInfo: NO
withSubtitleInfo: YES]];
}
- fNeedsDescription = NO;
}
-- (NSMutableAttributedString *) attributedDescriptionWithHBHandle: (hb_handle_t *)handle
+- (NSMutableAttributedString *) attributedDescription
{
if (fNeedsDescription)
- [self updateDescriptionWithHBHandle: handle];
+ [self updateDescription];
return fDescription;
}
-- (float) heightOfDescriptionForWidth:(float)width withHBHandle: (hb_handle_t *)handle
+- (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 updateDescriptionWithHBHandle: handle];
+ [self updateDescription];
// Calculate the height
NSRect bounds = [fDescription boundingRectWithSize:NSMakeSize(width, 10000) options:NSStringDrawingUsesLineFragmentOrigin];
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
static NSString* HBQueueStartCancelToolbarIdentifier = @"HBQueueStartCancelToolbarIdentifier";
static NSString* HBQueuePauseResumeToolbarIdentifier = @"HBQueuePauseResumeToolbarIdentifier";
+#pragma mark -
@implementation HBQueueController
nil]];
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;
}
//------------------------------------------------------------------------------------
- (void)dealloc
{
- [fAnimation release];
-
// 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];
}
fHBController = controller;
}
+#pragma mark -
+#pragma mark - Getting the currently processing job group
+
//------------------------------------------------------------------------------------
-// Displays and brings the queue window to the front
+// Returns the HBJobGroup that is currently being encoded; nil if no encoding is
+// occurring.
//------------------------------------------------------------------------------------
-- (IBAction) showQueueWindow: (id)sender
+- (HBJobGroup *) currentJobGroup;
{
- if (!fQueueWindow)
- {
- BOOL loadSucceeded = [NSBundle loadNibNamed:@"Queue" owner:self] && fQueueWindow;
- NSAssert(loadSucceeded, @"Could not open Queue nib file");
- }
+ return fCurrentJobGroup;
+}
+
+//------------------------------------------------------------------------------------
+// Returns the HBJob (pass) that is currently being encoded; nil if no encoding is
+// occurring.
+//------------------------------------------------------------------------------------
+- (HBJob *) currentJob
+{
+ return fCurrentJob;
+}
- [self updateQueueUI];
- [self updateCurrentJobUI];
+#pragma mark -
+//------------------------------------------------------------------------------------
+// Displays and brings the queue window to the front
+//------------------------------------------------------------------------------------
+- (IBAction) showQueueWindow: (id)sender
+{
[fQueueWindow makeKeyAndOrderFront: self];
-
[[NSUserDefaults standardUserDefaults] setBool:YES forKey:@"QueueWindowIsOpen"];
}
+
//------------------------------------------------------------------------------------
// Show or hide the current job pane (fCurrentJobPane).
//------------------------------------------------------------------------------------
[NSValue valueWithRect:queueFrame], NSViewAnimationEndFrameKey,
nil];
- if (!fAnimation)
- fAnimation = [[NSViewAnimation alloc] initWithViewAnimations:nil];
-
- [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];
+ 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;
}
//------------------------------------------------------------------------------------
-// Rebuilds the contents of fJobGroups which is a hierarchy of HBJobGroup and HBJobs.
+// Sets fCurrentJobGroup to a new job group.
//------------------------------------------------------------------------------------
-- (void)rebuildJobGroups
+- (void) setCurrentJobGroup: (HBJobGroup *)aJobGroup
{
- [fJobGroups autorelease];
- fJobGroups = [[NSMutableArray arrayWithCapacity:0] retain];
+ if (aJobGroup)
+ [aJobGroup setStatus: HBStatusWorking];
+
+ [aJobGroup retain];
+ [fCurrentJobGroup release];
+ fCurrentJobGroup = aJobGroup;
+}
- HBJobGroup * aJobGroup = [HBJobGroup jobGroup];
+#pragma mark - Finding job groups
- hb_job_t * nextJob = hb_group( fHandle, 0 );
- while( nextJob )
+//------------------------------------------------------------------------------------
+// Returns the first pending job with a specified destination path or nil if no such
+// job exists.
+//------------------------------------------------------------------------------------
+- (HBJobGroup *) pendingJobGroupWithDestinationPath: (NSString *)path
+{
+ HBJobGroup * aJobGroup;
+ NSEnumerator * groupEnum = [fJobGroups objectEnumerator];
+ while ( (aJobGroup = [groupEnum nextObject]) )
+ {
+ if ([[aJobGroup destinationPath] isEqualToString: path])
+ return aJobGroup;
+ }
+ return nil;
+}
+
+//------------------------------------------------------------------------------------
+// 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]) )
{
- if (nextJob->sequence_id == 0)
+ HBJob * job;
+ NSEnumerator * jobEnum = [aJobGroup jobEnumerator];
+ while ( (job = [jobEnum nextObject]) )
{
- // Encountered a new group. Add the current one to fJobGroups and then start a new one.
- if ([aJobGroup count] > 0)
- {
- [fJobGroups addObject: aJobGroup];
- aJobGroup = [HBJobGroup jobGroup];
- }
+ if (job->sequence_id == aJobID)
+ return job;
}
- [aJobGroup addJob: [HBJob jobWithJob:nextJob]];
- nextJob = hb_next_job (fHandle, nextJob);
}
- if ([aJobGroup count] > 0)
+ return nil;
+}
+
+//------------------------------------------------------------------------------------
+// Locates and returns a libhb job whose sequence_id matches a specified value.
+//------------------------------------------------------------------------------------
+- (hb_job_t *) findLibhbJobWithID: (int)aJobID
+{
+ hb_job_t * job;
+ int index = 0;
+ while( ( job = hb_job( fHandle, index++ ) ) )
+ {
+ if (job->sequence_id == aJobID)
+ return job;
+ }
+ return nil;
+}
+
+#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;
+}
+
+//------------------------------------------------------------------------------------
+// Recalculates and stores new values in fPendingCount, fCompletedCount,
+// fCanceledCount, and fWorkingCount.
+//------------------------------------------------------------------------------------
+- (void) recalculateJobGroupCounts
+{
+ fPendingCount = 0;
+ fCompletedCount = 0;
+ fCanceledCount = 0;
+ fWorkingCount = 0;
+
+ NSEnumerator * groupEnum = [fJobGroups objectEnumerator];
+ HBJobGroup * aJobGroup;
+ while ( (aJobGroup = [groupEnum nextObject]) )
{
- [fJobGroups addObject:aJobGroup];
+ 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;
}
//------------------------------------------------------------------------------------
-// Saves the state of the items that are currently expanded. Calling restoreOutlineViewState
-// will restore the state of all items to match what was saved by saveOutlineViewState.
+// Returns the number of job groups whose status is HBStatusPending.
+//------------------------------------------------------------------------------------
+- (unsigned int) pendingCount
+{
+ if (fJobGroupCountsNeedUpdating)
+ [self recalculateJobGroupCounts];
+ return fPendingCount;
+}
+
+//------------------------------------------------------------------------------------
+// Returns the number of job groups whose status is HBStatusCompleted.
+//------------------------------------------------------------------------------------
+- (unsigned int) completedCount
+{
+ if (fJobGroupCountsNeedUpdating)
+ [self recalculateJobGroupCounts];
+ return fCompletedCount;
+}
+
+//------------------------------------------------------------------------------------
+// Returns the number of job groups whose status is HBStatusCanceled.
+//------------------------------------------------------------------------------------
+- (unsigned int) canceledCount
+{
+ if (fJobGroupCountsNeedUpdating)
+ [self recalculateJobGroupCounts];
+ return fCanceledCount;
+}
+
+//------------------------------------------------------------------------------------
+// Returns the number of job groups whose status is HBStatusWorking.
+//------------------------------------------------------------------------------------
+- (unsigned int) workingCount
+{
+ if (fJobGroupCountsNeedUpdating)
+ [self recalculateJobGroupCounts];
+ return fWorkingCount;
+}
+
+#pragma mark -
+#pragma mark UI Updating
+
+//------------------------------------------------------------------------------------
+// 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
{
else
[fSavedExpandedItems removeAllIndexes];
- // NB: This code is stuffing the address of each job into an index set. While it
- // works 99.9% of the time, it's not the ideal solution. We need unique ids in
- // each job, possibly using the existing sequence_id field. Could use the high
- // word as a unique encode id and the low word the sequence number.
+ // 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.
HBJobGroup * aJobGroup;
NSEnumerator * e = [fJobGroups objectEnumerator];
while ( (aJobGroup = [e nextObject]) )
{
if ([fOutlineView isItemExpanded: aJobGroup])
- [fSavedExpandedItems addIndex: (unsigned int)[[aJobGroup jobAtIndex:0] job]];
+ [fSavedExpandedItems addIndex: [aJobGroup jobAtIndex:0]->sequence_id];
}
- // Save the selection also. This is really UGLY code. Since I have to rebuild the
- // entire outline hierachy every time hblib changes its job list, there's no easy
- // way for me to remember the selection state other than saving off the first
- // hb_job_t item in each selected group. This is done by saving the object's
- // address. This could go away if I'd save a unique id in each job object.
-
- int selection = [fOutlineView selectedRow];
- if (selection == -1)
- fSavedSelectedItem = 0;
+ // Save the selection also.
+
+ if (!fSavedSelectedItems)
+ fSavedSelectedItems = [[NSMutableIndexSet alloc] init];
else
+ [fSavedSelectedItems removeAllIndexes];
+
+ NSIndexSet * selectedRows = [fOutlineView selectedRowIndexes];
+ int row = [selectedRows firstIndex];
+ while (row != NSNotFound)
{
- HBJobGroup * jobGroup = [fOutlineView itemAtRow: selection];
- fSavedSelectedItem = (unsigned int)[[jobGroup jobAtIndex:0] job];
+ aJobGroup = [fOutlineView itemAtRow: row];
+ [fSavedSelectedItems addIndex: [aJobGroup jobAtIndex:0]->sequence_id];
+ row = [selectedRows indexGreaterThanIndex: row];
}
-
+
}
//------------------------------------------------------------------------------------
NSEnumerator * e = [fJobGroups objectEnumerator];
while ( (aJobGroup = [e nextObject]) )
{
- hb_job_t * j = [[aJobGroup jobAtIndex:0] job];
- if ([fSavedExpandedItems containsIndex: (unsigned int)j])
+ HBJob * job = [aJobGroup jobAtIndex:0];
+ if (job && [fSavedExpandedItems containsIndex: job->sequence_id])
[fOutlineView expandItem: aJobGroup];
}
}
- if (fSavedSelectedItem)
+ if (fSavedSelectedItems)
{
- // Ugh. Have to cycle through each row looking for the previously selected job.
- // See the explanation in saveExpandedItems about the logic here.
-
- // Find out what hb_job_t was selected
- hb_job_t * j = (hb_job_t *)fSavedSelectedItem;
-
- int rowToSelect = -1;
- for (int i = 0; i < [fOutlineView numberOfRows]; i++)
+ NSMutableIndexSet * rowsToSelect = [[[NSMutableIndexSet alloc] init] autorelease];
+ HBJobGroup * aJobGroup;
+ NSEnumerator * e = [fJobGroups objectEnumerator];
+ int i = 0;
+ while ( (aJobGroup = [e nextObject]) )
{
- HBJobGroup * jobGroup = [fOutlineView itemAtRow: i];
- // Test to see if the group's first job is a match
- if ([[jobGroup jobAtIndex:0] job] == j)
- {
- rowToSelect = i;
- break;
- }
+ HBJob * job = [aJobGroup jobAtIndex:0];
+ if (job && [fSavedSelectedItems containsIndex: job->sequence_id])
+ [rowsToSelect addIndex: i];
+ i++;
}
- if (rowToSelect == -1)
+ if ([rowsToSelect count] == 0)
[fOutlineView deselectAll: nil];
else
- [fOutlineView selectRow:rowToSelect byExtendingSelection:NO];
+ [fOutlineView selectRowIndexes:rowsToSelect byExtendingSelection:NO];
+ }
+}
+
+//------------------------------------------------------------------------------------
+// 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)
{
//------------------------------------------------------------------------------------
// 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
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
}
//------------------------------------------------------------------------------------
- (void)updateQueueCountField
{
NSString * msg;
- int jobCount;
-
- 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];
+ 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
+ {
+ 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];
[fQueueCountField setStringValue:msg];
}
//------------------------------------------------------------------------------------
// Refresh the UI in the current job pane. Should be called whenever the current job
-// being processed has changed or when progress has changed.
+// being processed has changed.
//------------------------------------------------------------------------------------
-- (void)updateCurrentJobUI
+- (void)updateCurrentJobDescription
{
- hb_state_t s;
- hb_job_t * job = nil;
-
- if (fHandle)
- {
- hb_get_state2( fHandle, &s );
- job = hb_current_job(fHandle);
- }
-
- if (job)
+ if (fCurrentJob)
{
- if (fLastKnownCurrentJob != job)
+ switch (fCurrentJob->pass)
{
- HBJob * currentJob = [HBJob jobWithJob: job];
-
- switch (job->pass)
- {
- case -1: // Subtitle scan
- [fJobDescTextField setAttributedStringValue:
- [currentJob attributedDescriptionWithHBHandle:fHandle
- withIcon: 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:
- [currentJob attributedDescriptionWithHBHandle:fHandle
- withIcon: 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:
- [currentJob attributedDescriptionWithHBHandle:fHandle
- withIcon: NO
- withTitle: YES
- withPassName: YES
- withFormatInfo: NO
- withDestination: NO
- withPictureInfo: YES
- withVideoInfo: YES
- withx264Info: YES
- withAudioInfo: YES
- withSubtitleInfo: YES]];
- break;
+ 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;
- default: // unknown
- [fJobDescTextField setAttributedStringValue:
- [currentJob attributedDescriptionWithHBHandle:fHandle
- withIcon: NO
- withTitle: YES
- withPassName: YES
- withFormatInfo: NO
- withDestination: NO
- withPictureInfo: YES
- withVideoInfo: YES
- withx264Info: YES
- withAudioInfo: YES
- withSubtitleInfo: YES]];
- }
-
- [self showCurrentJobPane:YES];
- [fJobIconView setImage: [NSImage imageNamed:@"JobLarge"]];
+ 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]];
}
-
- 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)];
+ [fJobDescTextField setStringValue: @"No encodes pending"];
+}
- [self showCurrentJobPane:NO];
- [fProgressBar stopAnimation:nil]; // just in case in was animating
- }
-
- fLastKnownCurrentJob = job;
+//------------------------------------------------------------------------------------
+// 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];
}
//------------------------------------------------------------------------------------
-// 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.
+// Notifies HBQueuecontroller that the contents of fJobGroups is about to be modified.
+// HBQueuecontroller remembers the state of the UI (selection and expanded items).
//------------------------------------------------------------------------------------
-- (void)updateQueueUI
+- (void) beginEditingJobGroupsArray
{
[self saveOutlineViewState];
- [self rebuildJobGroups];
+}
+
+//------------------------------------------------------------------------------------
+// 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 job from HB and the queue UI
+// Deletes the selected jobs from HB and the queue UI
//------------------------------------------------------------------------------------
-- (IBAction)removeSelectedJob: (id)sender
+- (IBAction)removeSelectedJobGroups: (id)sender
{
if (!fHandle) return;
- int row = [sender selectedRow];
- if (row != -1)
+ NSIndexSet * selectedRows = [fOutlineView selectedRowIndexes];
+ int row = [selectedRows firstIndex];
+ if (row != NSNotFound)
{
-#if HB_UNI_QUEUE
- if (row == 0)
+ [self beginEditingJobGroupsArray];
+ while (row != NSNotFound)
{
- [self cancelCurrentJob:sender];
+ 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];
}
- else
+ [self endEditingJobGroupsArray];
+ }
+}
+
+//------------------------------------------------------------------------------------
+// Reveals the file icons in the Finder of the selected job groups.
+//------------------------------------------------------------------------------------
+- (IBAction)revealSelectedJobGroups: (id)sender
+{
+ if (!fHandle) return;
+
+ NSIndexSet * selectedRows = [fOutlineView selectedRowIndexes];
+ int row = [selectedRows firstIndex];
+ if (row != NSNotFound)
+ {
+ while (row != NSNotFound)
{
- row--;
- hb_rem_group( fHandle, hb_group( fHandle, row ) );
+ HBJobGroup * jobGroup = [fOutlineView itemAtRow: row];
+ if ([[jobGroup destinationPath] length])
+ [[NSWorkspace sharedWorkspace] selectFile:[jobGroup destinationPath] inFileViewerRootedAtPath:nil];
+
+ row = [selectedRows indexGreaterThanIndex: row];
}
-#else
- HBJobGroup * jobGroup = [fOutlineView itemAtRow: row];
- hb_job_t * job = [[jobGroup jobAtIndex: 0] job];
- hb_rem_group( fHandle, job );
-#endif
- [self updateQueueUI];
- }
+ }
}
//------------------------------------------------------------------------------------
-// Prompts user if the want to cancel encoding of current job. If so, doCancelCurrentJob
-// gets called.
+// 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
{
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
- else if (hb_group_count(fHandle) > 0)
+ else if ([self pendingCount] > 0)
[fHBController doRip];
}
//------------------------------------------------------------------------------------
-// Toggles the pause/resume state of hblib
+// Toggles the pause/resume state of libhb
//------------------------------------------------------------------------------------
- (IBAction)togglePauseResume: (id)sender
{
hb_pause (fHandle);
}
+#pragma mark -
+#pragma mark Synchronizing with libhb
+
+//------------------------------------------------------------------------------------
+// Queues a job group. The job group's status is set to HBStatusPending.
+//------------------------------------------------------------------------------------
+- (void) addJobGroup: (HBJobGroup *) aJobGroup
+{
+ NSAssert(![fJobGroups containsObject:aJobGroup], @"Duplicate job group");
+ [aJobGroup setStatus:HBStatusPending];
+
+ [self beginEditingJobGroupsArray];
+ [fJobGroups addObject:aJobGroup];
+ [self endEditingJobGroupsArray];
+}
+
+//------------------------------------------------------------------------------------
+// Notifies HBQueueController that libhb's current job has changed
+//------------------------------------------------------------------------------------
+- (void)currentJobChanged: (HBJob *) currentJob
+{
+ [currentJob retain];
+ [fCurrentJob release];
+ fCurrentJob = currentJob;
+
+ // 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
+ {
+ // 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];
+ }
+
+ // 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];
+ }
+
+}
+
+//------------------------------------------------------------------------------------
+// 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.
+//------------------------------------------------------------------------------------
+- (void)libhbWillStop
+{
+ if (fCurrentJobGroup)
+ [fCurrentJobGroup setStatus: HBStatusCanceled];
+}
+
+//------------------------------------------------------------------------------------
+// Notifies HBQueueController that libhb's state has changed
+//------------------------------------------------------------------------------------
+- (void)libhbStateChanged: (hb_state_t &)state
+{
+ switch( state.state )
+ {
+ 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;
+ }
+
+ case HB_STATE_MUXING:
+ {
+ [self updateCurrentJobProgress];
+ break;
+ }
+
+ 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;
+ }
+
+ }
+
+}
+
#if HB_OUTLINE_METRIC_CONTROLS
static float spacingWidth = 3.0;
- (IBAction)imageSpacingChanged: (id)sender;
}
#endif
+#pragma mark -
+
+//------------------------------------------------------------------------------------
+// Receives notification whenever an HBJobGroup's status is changed.
+//------------------------------------------------------------------------------------
+- (void) jobGroupStatusNotification:(NSNotification *)notification
+{
+ [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
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/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:)];
- }
+ [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:)];
+ }
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: @"Pause"];
- [toolbarItem setPaletteLabel: @"Pause/Resume"];
-
- // Set up a reasonable tooltip, and image
- [toolbarItem setToolTip: @"Pause Encoding"];
- [toolbarItem setImage: [NSImage imageNamed: @"Pause"]];
-
- // Tell the item what message to send when it is clicked
- [toolbarItem setTarget: self];
- [toolbarItem setAction: @selector(togglePauseResume:)];
- }
+ [toolbarItem setLabel: @"Pause"];
+ [toolbarItem setPaletteLabel: @"Pause/Resume"];
+
+ // Set up a reasonable tooltip, and image
+ [toolbarItem setToolTip: @"Pause Encoding"];
+ [toolbarItem setImage: [NSImage imageNamed: @"Pause"]];
+
+ // Tell the item what message to send when it is clicked
+ [toolbarItem setTarget: self];
+ [toolbarItem setAction: @selector(togglePauseResume:)];
+ }
return toolbarItem;
}
return [NSArray arrayWithObjects:
HBQueueStartCancelToolbarIdentifier,
HBQueuePauseResumeToolbarIdentifier,
- NSToolbarCustomizeToolbarItemIdentifier,
- NSToolbarFlexibleSpaceItemIdentifier,
+ NSToolbarCustomizeToolbarItemIdentifier,
+ NSToolbarFlexibleSpaceItemIdentifier,
NSToolbarSpaceItemIdentifier,
- NSToolbarSeparatorItemIdentifier,
+ NSToolbarSeparatorItemIdentifier,
nil];
}
{
enable = YES;
[toolbarItem setImage:[NSImage imageNamed: @"Stop"]];
- [toolbarItem setLabel: @"Stop"];
- [toolbarItem setToolTip: @"Stop Encoding"];
+ [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 setToolTip: @"Start Encoding"];
+ [toolbarItem setLabel: @"Start"];
+ [toolbarItem setToolTip: @"Start Encoding"];
}
else
{
enable = NO;
[toolbarItem setImage:[NSImage imageNamed: @"Play"]];
- [toolbarItem setLabel: @"Start"];
- [toolbarItem setToolTip: @"Start Encoding"];
+ [toolbarItem setLabel: @"Start"];
+ [toolbarItem setToolTip: @"Start Encoding"];
}
- }
+ }
if ([[toolbarItem itemIdentifier] isEqual: HBQueuePauseResumeToolbarIdentifier])
{
{
enable = YES;
[toolbarItem setImage:[NSImage imageNamed: @"Play"]];
- [toolbarItem setLabel: @"Resume"];
- [toolbarItem setToolTip: @"Resume Encoding"];
+ [toolbarItem setLabel: @"Resume"];
+ [toolbarItem setToolTip: @"Resume Encoding"];
}
else if ((s.state == HB_STATE_WORKING) || (s.state == HB_STATE_MUXING))
{
enable = YES;
[toolbarItem setImage:[NSImage imageNamed: @"Pause"]];
- [toolbarItem setLabel: @"Pause"];
- [toolbarItem setToolTip: @"Pause Encoding"];
+ [toolbarItem setLabel: @"Pause"];
+ [toolbarItem setToolTip: @"Pause Encoding"];
}
else
{
enable = NO;
[toolbarItem setImage:[NSImage imageNamed: @"Pause"]];
- [toolbarItem setLabel: @"Pause"];
- [toolbarItem setToolTip: @"Pause Encoding"];
+ [toolbarItem setLabel: @"Pause"];
+ [toolbarItem setToolTip: @"Pause Encoding"];
}
- }
+ }
- return enable;
+ return enable;
}
#pragma mark -
// Don't allow autoresizing of main column, else the "delete" column will get
// pushed out of view.
[fOutlineView setAutoresizesOutlineColumn: NO];
- [fOutlineView setIndentationPerLevel:21];
#if HB_OUTLINE_METRIC_CONTROLS
[fIndentation setHidden: NO];
// Show/hide UI elements
fCurrentJobPaneShown = YES; // it's shown in the nib
[self showCurrentJobPane:NO];
+
+ [self updateQueueCountField];
}
}
#pragma mark -
+
+- (void)moveObjectsInArray:(NSMutableArray *)array fromIndexes:(NSIndexSet *)indexSet toIndex:(unsigned)insertIndex
+{
+ 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];
+ }
+}
+
+#pragma mark -
#pragma mark NSOutlineView delegate
- (id)outlineView:(NSOutlineView *)outlineView child:(int)index ofItem:(id)item
return YES;
}
+- (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
return [item lastDescriptionHeight];
float width = [[outlineView tableColumnWithIdentifier: @"desc"] width];
- // Column width is NOT what is ultimately used
- width -= 47; // 26 pixels for disclosure triangle, 20 for icon, 1 for intercell spacing
+ // 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 withHBHandle: fHandle];
+ float height = [item heightOfDescriptionForWidth: width];
return height;
}
else
- (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 attributedDescriptionWithHBHandle: fHandle];
+ 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 @"";
}
[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:[NSImage imageNamed:@"JobSmall"]];
+ [cell setImage:nil];
}
- else if ([[tableColumn identifier] isEqualToString:@"delete"])
+ else if ([[tableColumn identifier] isEqualToString:@"action"])
{
- // The Delete action can only be applied for group items, not indivdual jobs.
[cell setEnabled: YES];
BOOL highlighted = [outlineView isRowSelected:[outlineView rowForItem: item]] && [[outlineView window] isKeyWindow] && ([[outlineView window] firstResponder] == outlineView);
- if (highlighted)
+ if ([(HBJobGroup*)item status] == HBStatusCompleted)
{
- [cell setImage:[NSImage imageNamed:@"DeleteHighlight"]];
- [cell 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
- [cell 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"]];
+ }
}
}
#if HB_QUEUE_DRAGGING
- (BOOL)outlineView:(NSOutlineView *)outlineView writeItems:(NSArray *)items toPasteboard:(NSPasteboard *)pboard
{
- fDraggedNodes = items; // 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.
-
+ // 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];
#if HB_QUEUE_DRAGGING
- (NSDragOperation)outlineView:(NSOutlineView *)outlineView validateDrop:(id <NSDraggingInfo>)info proposedItem:(id)item proposedChildIndex:(int)index
{
- // Add code here to validate the drop
- BOOL isOnDropTypeProposal = index == NSOutlineViewDropOnItemIndex;
+ // 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
{
-// NSPasteboard* pboard = [info draggingPasteboard];
-// NSData* rowData = [pboard dataForType:HBQueuePboardType];
-// NSIndexSet* rowIndexes = [NSKeyedUnarchiver unarchiveObjectWithData:rowData];
-// int dragRow = [rowIndexes firstIndex];
-
- // Move the specified row to its new location...
- HBJob * draggedJob = [fDraggedNodes objectAtIndex: 0];
- NSLog(@"dragged job = %@", draggedJob);
- NSLog(@"drag from location = %d", [fJobGroups indexOfObject: draggedJob]);
- NSLog(@"drag to location = %d", index);
- NSLog(@"acceptDrop");
+ 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